Skip to main content
pytest-in-docker

pytest-in-docker

Teleport your pytest tests into Docker containers.

PyPI CI Python License Discord


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 exec or 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

MIT

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)

Source distribution for pytest-in-docker 0.2.1
File Size Uploaded
pytest_in_docker-0.2.1.tar.gz 3.8 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for pytest-in-docker 0.2.1
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page