Bridge between pytest-testcontainers and pytest-django: starts the DB container before Django imports settings.
Project description
pytest-testcontainers-django
Bridge between pytest-testcontainers and pytest-django:
starts a Postgres (and optionally Redis) container before Django imports
its settings, so your tests run against a real, ephemeral DB without any
docker-compose orchestration — and without "Connection refused" against
port 5432 because Django read os.environ too early.
Why this package exists
Django evaluates DATABASES at module-import time. pytest-django
imports settings during its pytest_load_initial_conftests hook. Any
fixture-based testcontainer setup runs after that — so by the time the
container has a port, Django has already opened a connection (or failed
to) against whatever your .env had at pytest startup.
The only correct hook for "start a container, write its port to
os.environ, before Django imports settings" is
pytest_load_initial_conftests itself, registered with
@pytest.hookimpl(tryfirst=True). That single detail is the core IP of
this package; the rest is plumbing — xdist worker propagation, dotenv
suppression, init-script mounting, TEST TEMPLATE wiring, cleanup
ordering.
See SPEC.md for the full design rationale (especially §6 on
the timing dance).
Features
pytest_load_initial_conftests(tryfirst=True)hook that runs before pytest-django imports yoursettings.py.- Zero-config defaults — works out of the box for projects whose
settings.pyreadsDJANGO_DB_HOST/DJANGO_DB_PORT/ etc. fromos.environ. - Declarative configuration in
[tool.pytest-testcontainers-django]or programmatic configuration viaregister(DjangoContainerConfig(...))fromconftest.py. - Postgres init-script mounting (
/docker-entrypoint-initdb.d/) with automaticDATABASES['TEST']['TEMPLATE']defaulting — sopytest --create-dbfinishes in seconds. - Optional Redis container with the same timing-safe injection pattern.
- pytest-xdist support — workers inherit the controller's environment, no port-fight.
--no-testcontainers/PYTEST_TESTCONTAINERS_DISABLE=1to delegate to docker-compose;PYTEST_TESTCONTAINERS_REUSE=1for fast local iteration.- atexit safety net for abrupt-exit paths that skip
pytest_unconfigure. - Optional integration with
django-pg-baselinefor managed baseline SQL artifacts.
Supported versions
Python
| Python | 3.10 | 3.11 | 3.12 | 3.13 |
|---|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
Django
Authoritative upstream: https://docs.djangoproject.com/en/dev/faq/install/#what-python-version-can-i-use-with-django
| Django | 3.10 | 3.11 | 3.12 | 3.13 | Status |
|---|---|---|---|---|---|
| 4.2 LTS | ✓ | ✓ | ✓ | — | EOL Apr 2026 (still works) |
| 5.2 LTS | ✓ | ✓ | ✓ | ✓ | Active LTS |
EOL Django releases (4.2, 5.0, 5.1) are not actively tested but should still work — this package only consumes pytest-django's hook surface, not Django internals. Open an issue if you need an LTS-only reassurance.
Install
Using uv (recommended)
uv add pytest-testcontainers-django
Using pip
pip install pytest-testcontainers-django
You also need a working Docker daemon on the host running pytest. No extra system libraries are required — the package is pure Python.
Quick start
For most projects, configuration lives in pyproject.toml. Zero
conftest.py needed:
[tool.pytest-testcontainers-django]
postgres_image = "postgres:16"
postgres_user = "myapp"
postgres_password = "myapp"
postgres_database = "myapp"
# Env-var names this plugin writes into os.environ.
# These are the same names your settings.py reads.
db_host_env = "DJANGO_DB_HOST"
db_port_env = "DJANGO_DB_PORT"
db_name_env = "DJANGO_DB_NAME"
db_user_env = "DJANGO_DB_USER"
db_password_env = "DJANGO_DB_PASSWORD"
db_test_template_env = "DJANGO_DB_TEST_TEMPLATE"
skip_dotenv_env = "DJANGO_SKIP_DOTENV"
Your settings.py reads these env vars exactly as you'd expect:
import environ
import os
env = environ.Env()
# Skip .env loading when the plugin already populated os.environ —
# otherwise read_env(overwrite=True) would clobber our injected port.
if not os.environ.get("DJANGO_SKIP_DOTENV"):
environ.Env.read_env(".env", overwrite=True)
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": env("DJANGO_DB_NAME"),
"USER": env("DJANGO_DB_USER"),
"PASSWORD": env("DJANGO_DB_PASSWORD"),
"HOST": env("DJANGO_DB_HOST"),
"PORT": env("DJANGO_DB_PORT"),
},
}
# Wire the TEST TEMPLATE env var (optional but recommended when you
# load init scripts — see "Init scripts / baseline" below).
_test_template = env("DJANGO_DB_TEST_TEMPLATE", default="")
if _test_template:
DATABASES["default"]["TEST"] = {"TEMPLATE": _test_template}
That's it — pytest will start a Postgres container, inject the port,
let pytest-django import settings, and tear the container down at exit.
Init scripts / baseline
Mount SQL files into the Postgres container's
/docker-entrypoint-initdb.d/ so they're replayed once on cluster init —
significantly faster than running psql -f from the host:
[tool.pytest-testcontainers-django]
postgres_database = "myapp"
postgres_init_scripts = [
"tests/fixtures/baseline.sql",
"tests/fixtures/extensions.sql",
]
# postgres_template defaults to postgres_database when init_scripts is
# set, so the test DB will be created via fast in-server CREATE DATABASE
# test_<X> WITH TEMPLATE myapp instead of replaying migrations.
Combine with the DATABASES['default']['TEST']['TEMPLATE'] snippet
above to make pytest --create-db finish in seconds.
Optional Redis
testcontainers's RedisContainer imports the redis Python client at
module load, so install it alongside this package when you enable Redis:
uv add 'pytest-testcontainers-django[redis]'
# or
pip install 'pytest-testcontainers-django[redis]'
[tool.pytest-testcontainers-django]
redis_enabled = true
redis_image = "redis:7-alpine"
redis_host_env = "DJANGO_REDIS_HOST"
redis_port_env = "DJANGO_REDIS_PORT"
Your settings reads DJANGO_REDIS_HOST / DJANGO_REDIS_PORT and folds
them into a redis://... URL however your stack prefers.
Programmatic configuration
For projects that need conditional configuration or want to wire in
django-pg-baseline for
baseline-managed seed data, register from conftest.py:
# conftest.py at the project root
from pathlib import Path
from pytest_testcontainers_django import (
DjangoContainerConfig,
PostgresService,
RedisService,
register,
)
register(
DjangoContainerConfig(
postgres=PostgresService(
image="postgres:16",
user="myapp",
password="myapp",
database="myapp",
init_scripts=[Path("tests/fixtures/baseline.sql")],
template="myapp",
),
redis=RedisService(),
)
)
register() overrides any pyproject.toml table. This works because
the plugin force-imports the rootdir conftest.py from inside its
tryfirst hook, so top-level register(...) calls run before
configuration is read.
Disable / reuse
# Delegate to docker-compose / pre-existing services:
pytest --no-testcontainers
PYTEST_TESTCONTAINERS_DISABLE=1 pytest
# Keep containers alive between runs for fast local iteration:
PYTEST_TESTCONTAINERS_REUSE=1 pytest
pytest-xdist
Workers inherit the controller's environment on fork, so they don't
start new containers — they only re-set the *_SKIP_DOTENV flag so
django-environ doesn't re-read .env on settings re-import.
Coexistence with other testcontainers
Django projects that need additional services (Elasticsearch, MinIO,
Kafka, etc.) declare plain pytest fixtures using
pytest-testcontainers's maker functions directly — no special
integration with this package needed:
# conftest.py
import pytest
from pytest_testcontainers import make_container
@pytest.fixture(scope="session")
def minio():
with make_container("minio/minio:latest", ports={"9000/tcp": None}) as c:
yield c
Late resolution is fine for non-DB services — their host:port is read
at connection time, not import time. Only Django's DATABASES has
the import-time-read race that this package solves.
Configuration reference
| Pyproject key | Default | Purpose |
|---|---|---|
postgres_image |
postgres:16 |
Image used for the DB container |
postgres_user |
postgres |
POSTGRES_USER |
postgres_password |
postgres |
POSTGRES_PASSWORD |
postgres_database |
postgres |
POSTGRES_DB |
postgres_internal_port |
5432 |
Image's internal port |
postgres_template |
(= postgres_database when init scripts set, else unset) |
Value injected as DATABASES['TEST']['TEMPLATE'] |
postgres_init_scripts |
[] |
Paths mounted into /docker-entrypoint-initdb.d/ |
postgres_env |
{} |
Image-specific env (e.g. tuning flags) |
db_host_env |
DJANGO_DB_HOST |
Env var written with the resolved host |
db_port_env |
DJANGO_DB_PORT |
Env var written with the resolved port |
db_name_env |
DJANGO_DB_NAME |
Env var written with postgres_database |
db_user_env |
DJANGO_DB_USER |
Env var written with postgres_user |
db_password_env |
DJANGO_DB_PASSWORD |
Env var written with postgres_password |
db_test_template_env |
DJANGO_DB_TEST_TEMPLATE |
Env var written with postgres_template |
skip_dotenv_env |
DJANGO_SKIP_DOTENV |
Env var your settings checks before reading .env |
disable_env |
PYTEST_TESTCONTAINERS_DISABLE |
Env var that disables the plugin |
reuse_env |
PYTEST_TESTCONTAINERS_REUSE |
Env var that enables reuse mode |
redis_enabled |
false |
Enable Redis |
redis_image |
redis:7-alpine |
Image used for Redis |
redis_internal_port |
6379 |
|
redis_host_env |
DJANGO_REDIS_HOST |
|
redis_port_env |
DJANGO_REDIS_PORT |
|
use_django_pg_baseline |
false |
Auto-prepend django-pg-baseline's artifact |
License
MIT — see LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pytest_testcontainers_django-0.2.0.tar.gz.
File metadata
- Download URL: pytest_testcontainers_django-0.2.0.tar.gz
- Upload date:
- Size: 21.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
13f7fad58aa4644a7e7f7797e153d942dafbd874bc79891930fcd8a9eacb2268
|
|
| MD5 |
2d09d75a35c663f305d34cf302cc786b
|
|
| BLAKE2b-256 |
35cbd54042633a970c2eac59651692a808e693e79d0166ef41f2dfe36c4afb66
|
File details
Details for the file pytest_testcontainers_django-0.2.0-py3-none-any.whl.
File metadata
- Download URL: pytest_testcontainers_django-0.2.0-py3-none-any.whl
- Upload date:
- Size: 20.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
11a818ace8eb4b848dd081dbab75d99592a6c57f646ae9d03d826ea4d57f3687
|
|
| MD5 |
56bf1bd8964259d996398b2e2f441b38
|
|
| BLAKE2b-256 |
757dfac78fbe126141de57e0c1ce1ae0f16561c33ff4f7d637c78de50b7d53de
|