Seamlessly run pytest tests inside docker containers
Project description
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
Project details
Release history Release notifications | RSS feed
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_in_docker-0.2.1.tar.gz.
File metadata
- Download URL: pytest_in_docker-0.2.1.tar.gz
- Upload date:
- Size: 3.8 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5f25ceb12eb98a495c1f9f4764a73dd7e86b1213e6f1324f8f9c222ae34a00b5
|
|
| MD5 |
c55f994a8c69de9612fe0a5a0e4baeec
|
|
| BLAKE2b-256 |
f6cedfb5a8cb7dfb317b5cf27bc03c91b9f5b9d0537da893369a78dc8219d0ac
|
Provenance
The following attestation bundles were made for pytest_in_docker-0.2.1.tar.gz:
Publisher:
publish.yml on mesa-dot-dev/pytest-in-docker
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_in_docker-0.2.1.tar.gz -
Subject digest:
5f25ceb12eb98a495c1f9f4764a73dd7e86b1213e6f1324f8f9c222ae34a00b5 - Sigstore transparency entry: 934524297
- Sigstore integration time:
-
Permalink:
mesa-dot-dev/pytest-in-docker@6297df822349a3696a23bfcca1d15ea2313598a4 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/mesa-dot-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6297df822349a3696a23bfcca1d15ea2313598a4 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pytest_in_docker-0.2.1-py3-none-any.whl.
File metadata
- Download URL: pytest_in_docker-0.2.1-py3-none-any.whl
- Upload date:
- Size: 12.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a903c3d903985fb6d446eb184a58e6fea725d49f916c18ce2762375b037c85e1
|
|
| MD5 |
f076f01c3caa80827c5154630b9b4217
|
|
| BLAKE2b-256 |
2f14180cf34e1ccb81aef0e683b1d2db3c1e292cf886eb56316b229c6f7e6f3d
|
Provenance
The following attestation bundles were made for pytest_in_docker-0.2.1-py3-none-any.whl:
Publisher:
publish.yml on mesa-dot-dev/pytest-in-docker
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_in_docker-0.2.1-py3-none-any.whl -
Subject digest:
a903c3d903985fb6d446eb184a58e6fea725d49f916c18ce2762375b037c85e1 - Sigstore transparency entry: 934524332
- Sigstore integration time:
-
Permalink:
mesa-dot-dev/pytest-in-docker@6297df822349a3696a23bfcca1d15ea2313598a4 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/mesa-dot-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6297df822349a3696a23bfcca1d15ea2313598a4 -
Trigger Event:
release
-
Statement type: