Skip to main content

No project description provided

Project description

Dokker

Manage Docker Compose projects programmatically from Python — async-first, with synchronous APIs.

Dokker lets you drive a docker compose project from Python: pull, start, inspect, health-check, run commands inside services, stream logs, and tear everything down again. It is built around asyncio but exposes a fully synchronous API as well, so it fits equally well in async applications and in plain pytest test suites.

Its primary use case is writing integration tests for Docker Compose stacks, where you want to spin up a real set of containers, wait until they are healthy, exercise them, assert on their logs and exit codes, and have them reliably torn down afterwards.


Why dokker?

Other tools cover similar ground (e.g. python-on-whales, testcontainers). Dokker's distinguishing focus is asynchronous interaction with a running compose project:

  • Watch logs while you act. A LogWatcher collects a service's logs in the background while your code makes requests against it — so you can assert that an HTTP call actually produced the log line you expected.
  • Structured failure feedback. When a command in a container exits non-zero, dokker raises a CommandError that carries the exact exit code and the container's stdout/stderr separately, so you can tell why it failed instead of scraping one concatenated blob.
  • Reliable, bounded teardown. Lifecycle (pull / up / health / stop / down / tear-down) is driven by explicit flags, with grace-period and wall-clock timeouts so an unresponsive container can't hang your test session.
  • Async core, sync surface. Every operation has both an await deployment.aup() form and a blocking deployment.up() form (powered by koil).

Installation

pip install dokker

Dokker requires Python ≥ 3.11 and a working docker compose CLI on your PATH.


Core concepts

Deployment

The central object. A Deployment wraps a compose project and is used as a (sync or async) context manager. Entering it runs the configured on-enter steps (initialize, inspect, pull, up, health-check); exiting runs the on-exit steps (stop, down, tear-down). Within the block you can call up(), down(), stop(), restart(), pull(), inspect(), check_health(), run() and create_watcher().

Builders

You rarely construct a Deployment by hand. Instead you pick a builder that presets sensible lifecycle defaults for a given use case:

Builder Use case On enter On exit
local(...) Drive a stack you start/stop yourself during a session. initialize + inspect stop (no down)
testing(...) Full integration test: bring everything up, wait for health, clean up completely. pull + up + health-check stop + down + tear-down (removes volumes & orphans)
monitoring(...) Observe/inspect a stack that is already running in production; never calls the compose CLI to change it. inspect + health-check nothing
mirror(...) Copy a local project into a temp dir and run it there, isolated from the source. (copy) nothing

All builders accept a compose file path (or list of paths) and an optional list of HealthChecks.

HealthCheck

Describes how to know a service is ready — typically an HTTP URL that should return 200, with retries and a timeout. Health checks run automatically on enter for the testing/monitoring builders, or on demand via deployment.check_health().

run() and exit codes

deployment.run(service, command) runs a one-off command in a service (docker compose run) and returns a LogRoll with .returncode, .stdout and .stderr. By default a non-zero exit raises a CommandError; you can opt out with raise_on_error=False, or declare an expected failure code with expected_exit_code=....

LogWatcher

deployment.create_watcher(service) returns a context manager that streams a service's logs in the background. Inside the with block you interact with the service; afterwards watcher.collected_logs holds the captured (source, line) pairs. The watcher always cleans up its streaming subprocess, even if the block raises.


Quickstart (sync)

Given a docker-compose.yaml:

services:
  echo:
    image: hashicorp/http-echo
    command: ["-text", "Hello from dokker!"]
    ports:
      - "5678:5678"
import requests
from dokker import local, HealthCheck

deployment = local(
    "docker-compose.yaml",
    health_checks=[
        HealthCheck(service="echo", url="http://localhost:5678", max_retries=5, timeout=2),
    ],
)

with deployment:
    deployment.up()             # start the stack
    deployment.check_health()   # block until the echo service answers 200

    # Watch the echo service's logs while we hit it
    watcher = deployment.create_watcher("echo")
    with watcher:
        print(requests.get("http://localhost:5678").text)

    print(watcher.collected_logs)
    # -> the captured server logs, including the request we just made

# on exit, `local` stops the stack for you

Integration tests with pytest

The testing builder is purpose-built for this: it pulls images, brings the stack up, waits for health on enter, and fully tears it down on exit.

import pytest
import requests
from dokker import testing, HealthCheck, Deployment


@pytest.fixture(scope="session")
def deployment():
    with testing(
        "docker-compose.yaml",
        health_checks=[HealthCheck(service="echo", url="http://localhost:5678")],
        shutdown_timeout=1,  # SIGKILL containers that ignore SIGTERM after 1s
    ) as deployment:
        yield deployment


def test_echo_responds(deployment: Deployment):
    port = deployment.spec.services["echo"].get_port_for_internal(5678).published
    assert requests.get(f"http://localhost:{port}").status_code == 200

Running commands and asserting on exit codes

from dokker import CommandError

# A successful command returns its output and a zero exit code
logs = deployment.run("worker", "echo hello")
assert "hello" in logs.stdout
assert logs.returncode == 0

# A non-zero exit raises a CommandError carrying the code and the container's stderr
try:
    deployment.run("worker", "sh -c 'echo boom >&2; exit 7'")
except CommandError as error:
    assert error.returncode == 7
    assert any("boom" in line for line in error.stderr)

# Inspect the failure without raising...
logs = deployment.run("worker", "false", raise_on_error=False)
assert logs.returncode == 1

# ...or declare that a non-zero exit is the expected outcome
logs = deployment.run("worker", "false", expected_exit_code=1)

Async usage

Every method has an a-prefixed async counterpart, and the deployment is also an async context manager:

import asyncio
from dokker import local

async def main():
    deployment = local("docker-compose.yaml")

    async with deployment:
        await deployment.aup()              # start (detached)

        async with deployment.create_watcher("echo"):
            await deployment.arestart("echo")   # restart while watching its logs

asyncio.run(main())

Development

This is an open-source project and contributions are welcome. The API is only partially stable, so feel free to suggest changes or improvements.

uv sync                       # install dependencies
uv run pytest                 # run the unit tests
uv run pytest -m integration  # run the docker-backed integration tests

Integration tests require a running Docker daemon and use small public images (hashicorp/http-echo, redis:7-alpine, alpine) so they run anywhere.

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

dokker-2.5.0.tar.gz (23.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dokker-2.5.0-py3-none-any.whl (29.0 kB view details)

Uploaded Python 3

File details

Details for the file dokker-2.5.0.tar.gz.

File metadata

  • Download URL: dokker-2.5.0.tar.gz
  • Upload date:
  • Size: 23.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for dokker-2.5.0.tar.gz
Algorithm Hash digest
SHA256 cc2a677d4ad36a087f98a4d4992468a1334dc9d3feffa2c871bbcabaac90dc59
MD5 9e6896950c5dae8a580ecdff238ee210
BLAKE2b-256 50eb95a55ae2346a4f71f164bc3fcccce2bb7c4c5655270e5c4d9f579f2d1b2e

See more details on using hashes here.

File details

Details for the file dokker-2.5.0-py3-none-any.whl.

File metadata

  • Download URL: dokker-2.5.0-py3-none-any.whl
  • Upload date:
  • Size: 29.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for dokker-2.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e9b89b4284494f5c050a47714954ab81a3156f7277d72fb2e3c83d5f88599aba
MD5 00011b6cb7506e58ea5fdfc873e8797d
BLAKE2b-256 23a754e5720169b9c6356f46f8d9eecee39f9a40b262ab04142a3def4a924aa7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page