pytest-in-docker
Teleport your pytest tests into Docker containers.
Install
pip install pytest-in-docker
Or with uv:
uv add pytest-in-docker
Quick Start
Decorate any test function. It runs inside a Docker container:
from pytest_in_docker import in_container
import platform
@in_container("python:alpine")
def test_runs_on_alpine():
info = platform.freedesktop_os_release() # platform is available in the container.
assert info["ID"] == "alpine"
Then run pytest as usual:
pytest
The function is serialized with cloudpickle, sent to a fresh python:alpine container, and the result is reported back to your terminal.
Usage
The marker API integrates with all standard pytest features — fixtures, parametrize, and reporting work as expected.
Build from a Dockerfile
Point to a directory containing a Dockerfile and provide a tag. The image is built before the test runs:
import subprocess
@pytest.mark.in_container(path="./docker", tag="my-test-image:latest")
def test_custom_image():
result = subprocess.run(["cat", "/etc/os-release"], capture_output=True, text=True)
assert "alpine" in result.stdout.lower()
This works with the marker too:
@pytest.mark.in_container(path="./docker", tag="my-test-image:latest")
def test_custom_image_with_marker():
...
Test Across Multiple Images
Combine @pytest.mark.parametrize with the marker to run the same test across
different containers. Use image as the parameter name — the plugin picks it up
automatically:
import pytest
import platform
@pytest.mark.parametrize(
("image", "expected_id"),
[
("python:alpine", "alpine"),
("python:slim", "debian"),
],
)
@pytest.mark.in_container()
def test_across_distros(image: str, expected_id: str):
info = platform.freedesktop_os_release()
assert info["ID"].lower() == expected_id
When @pytest.mark.in_container() is called with no arguments, it reads the image
parameter from @pytest.mark.parametrize. This lets you build a compatibility matrix
with zero boilerplate.
Custom Container Factory
When you need to customise the container beyond what the other modes offer — environment variables, volumes, extra ports — pass a factory:
from contextlib import contextmanager
import os
from typing import Iterator
from testcontainers.core.container import DockerContainer
from pytest_in_docker import in_container
@contextmanager
def my_container(port: int) -> Iterator[DockerContainer]:
with (
DockerContainer("python:alpine")
.with_command("sleep infinity")
.with_exposed_ports(port)
.with_env("APP_ENV", "test") as container
):
container.start()
yield container
@pytest.mark.in_container(factory=my_container)
def test_env_is_set():
assert os.environ["APP_ENV"] == "test"
A factory is a callable that accepts a port: int argument and returns a context
manager yielding an already-started DockerContainer. The framework passes the
communication port automatically — the factory just needs to expose it and run sleep infinity.
Timeouts
Tests running inside containers default to a 30-second timeout. If pytest-timeout is installed, its timeout ini setting and @pytest.mark.timeout marker are respected automatically:
import pytest
@pytest.mark.timeout(60)
@pytest.mark.in_container("python:alpine")
def test_slow_operation():
...
How It Works
When a decorated test runs:
Host (pytest) Docker Container
───────────── ────────────────
1. Spin up container ──────> python:alpine starts
2. Install deps ──────> pip install cloudpickle rpyc pytest
3. Start RPyC server ──────> listening on port 51337
4. Serialize test (cloudpickle)
5. Send bytes over RPyC ──────> deserialize + execute
<────── result (pass/fail/exception) ──────
6. Container stops
How serialization works: cloudpickle serializes your test function — including closures, lambdas, and locally-defined helpers — into bytes on the host. Those bytes are sent to the container over RPyC, deserialized with the standard pickle module, and executed natively.
This means:
- Your test code runs natively inside the container — not through
docker execor shell commands - Full Python semantics: imports, exceptions, and return values all work naturally
- Closures and lambdas serialize correctly — use helper functions, captured variables, and dynamic code freely
- pytest assertion introspection still works on the host side for reporting
License
Metadata
Release files for pytest-in-docker 0.2.1
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_in_docker-0.2.1.tar.gz | 3.8 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pytest_in_docker-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 3.8 MB
Release files / pytest_in_docker-0.2.1.tar.gz
| Download URL | pytest_in_docker-0.2.1.tar.gz |
|---|---|
| Size | 3.8 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5f25ceb12eb98a495c1f9f4764a73dd7e86b1213e6f1324f8f9c222ae34a00b5
|
|
BLAKE2b-256 checksum How to use checksums |
f6cedfb5a8cb7dfb317b5cf27bc03c91b9f5b9d0537da893369a78dc8219d0ac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 Feb 9, 2026.
Transparency logRelease files / pytest_in_docker-0.2.1-py3-none-any.whl
| Download URL | pytest_in_docker-0.2.1-py3-none-any.whl |
|---|---|
| Size | 12.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a903c3d903985fb6d446eb184a58e6fea725d49f916c18ce2762375b037c85e1
|
|
BLAKE2b-256 checksum How to use checksums |
2f14180cf34e1ccb81aef0e683b1d2db3c1e292cf886eb56316b229c6f7e6f3d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 Feb 9, 2026.
Transparency log