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 |
| 6.0 | — | — | ✓ | ✓ | Requires Python >=3.12 |
| 6.1 | — | — | ✓ | ✓ | Tested in CI (Python 3.13) |
Django 6.0 and 6.1 require Python 3.12 or newer, so they are unavailable on the 3.10/3.11 rows.
CI's test matrix is a pytest matrix (this is a pytest plugin, and
Django is only a dev dependency), plus one dedicated job that pins
Django~=6.1.0 on Python 3.13 — see the django61 tox env. 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, and touches just
a couple of long-stable 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
Reuse names encode the git branch, so every branch and worktree parks its
own Postgres + Redis pair — and reuse mode disables Ryuk, so nothing ever
expires them. Sweep them up with pytest --testcontainers-clean (see
pytest-testcontainers' README for the shared-host caveat).
Teardown
Containers are named <project>-tc-<service>-… whether or not reuse is on,
so they are greppable in docker ps and reachable by
pytest --testcontainers-clean.
Ephemeral (non-reuse) containers are removed by pytest_unconfigure, by a
SIGTERM/SIGHUP handler, and by atexit — whichever fires first. The
Ryuk reaper is shut down explicitly at the end of the session instead of
being left to idle out. Both mechanisms live in pytest-testcontainers;
see its README for the details and for what still falls through on
SIGKILL.
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 |
Troubleshooting
Docker daemon is not reachable but docker ps works
You're almost certainly on a non-default Docker context — OrbStack,
colima, or a Docker Desktop install where /var/run/docker.sock is missing
or a dangling symlink. docker.from_env() (used by docker-py and by
testcontainers internally) honors DOCKER_HOST but, unlike the docker
CLI, ignores the active docker context. The plugin now resolves the active
context's endpoint for you and exports DOCKER_HOST before any container is
started, so it talks to the same daemon the CLI does.
If it still fails, point it at the daemon explicitly — an explicit
DOCKER_HOST always wins:
docker context inspect -f '{{.Endpoints.docker.Host}}' # see the endpoint
export DOCKER_HOST=unix://$HOME/.orbstack/run/docker.sock # e.g. OrbStack
export DOCKER_HOST=unix://$HOME/.colima/default/docker.sock # e.g. colima
License
MIT — see LICENSE.
Release files for pytest-testcontainers-django 0.3.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pytest_testcontainers_django-0.3.2.tar.gz | 35.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pytest_testcontainers_django-0.3.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 60.3 kB
Release files / pytest_testcontainers_django-0.3.2.tar.gz
| Download URL | pytest_testcontainers_django-0.3.2.tar.gz |
|---|---|
| Size | 35.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
20c18926638e5f82df1841482ce198eb7c9ecd3c4b64fb5593a5b9dc3d3cebf0
|
|
BLAKE2b-256 checksum How to use checksums |
b526c4d4a2e2e25edaab9e586e6955ecd970c9767b0c8b945e5e6ef5824e1971
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.
Transparency logRelease files / pytest_testcontainers_django-0.3.2-py3-none-any.whl
| Download URL | pytest_testcontainers_django-0.3.2-py3-none-any.whl |
|---|---|
| Size | 25.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
78fa9e6db6d108cef2c5c2d77b9093c7681112d706c159fe93ff3bff75ea2c98
|
|
BLAKE2b-256 checksum How to use checksums |
8c1f9ee74c50606fa1d2aa325b5740bf590d16349a41b8a6df121ff5b7dbf09a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.
Transparency log