Skip to main content

docker made easy

Project description

dockeasy

Install

pip install dockeasy

just import * from the top-level module and you’re good to go:

from dockeasy import *

Dockerfiles in Python

Build a Dockerfile by chaining methods. Every call returns a new Dockerfile — safe, immutable, and composable.

df = (Dockerfile()
    .from_('python:3.12-slim')
    .workdir('/app')
    .copy('requirements.txt', '.')
    .run('pip install -r requirements.txt')
    .copy('.', '.')
    .expose(8000)
    .cmd(['python', 'main.py']))

print(df)
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["python", "main.py"]

Multi-stage builds

Call .from_() again to start a new stage. Use from_= in .copy() to pull artifacts across stages.

df = (Dockerfile()
    .from_('golang:1.22-alpine', as_='builder')
    .workdir('/src')
    .copy('.', '.')
    .run('go build -o /app .')
    .from_('gcr.io/distroless/static')
    .copy('/app', '/app', from_='builder')
    .cmd(['/app']))

print(df)
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY . .
RUN go build -o /app .
FROM gcr.io/distroless/static
COPY --from=builder /app /app
CMD ["/app"]

Build cache mounts

.run_mount() adds --mount=type=cache for blazing-fast rebuilds with pip, uv, or apt.

df = (Dockerfile()
    .from_('python:3.12-slim')
    .workdir('/app')
    .copy('requirements.txt', '.')
    .run_mount('pip install -r requirements.txt', target='/root/.cache/pip')
    .copy('.', '.')
    .cmd(['python', 'main.py']))

print(df)
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt
COPY . .
CMD ["python", "main.py"]

Framework Builders

One-liners that generate production-ready Dockerfiles for common stacks.

Python / uv app

python_app() builds a multistage Dockerfile: uv compiles deps in a build stage, only the .venv is copied to the slim runtime.

python_app()
FROM ghcr.io/astral-sh/uv:python3.13-bookworm AS builder
WORKDIR /app
ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy
COPY pyproject.toml .
COPY uv.lock .
RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen --no-dev --no-install-project
COPY . .
RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen --no-dev
FROM python:3.13-slim
WORKDIR /app
COPY --from=builder /app /app
ENV PATH=/app/.venv/bin:$PATH
EXPOSE 8000
CMD ["python", "main.py"]
# With system packages, persistent volume dir, and a healthcheck
python_app(pkgs=['libpq-dev', 'curl'], vols=['/app/data'], healthcheck='/health')
FROM ghcr.io/astral-sh/uv:python3.13-bookworm AS builder
WORKDIR /app
ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy
COPY pyproject.toml .
COPY uv.lock .
RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen --no-dev --no-install-project
COPY . .
RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen --no-dev
FROM python:3.13-slim
WORKDIR /app
RUN apt-get update && apt-get install -y libpq-dev curl && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app /app
ENV PATH=/app/.venv/bin:$PATH
RUN mkdir -p /app/data
HEALTHCHECK --interval=30s --timeout=5s --retries=3 CMD curl -f http://localhost:8000/health
EXPOSE 8000
CMD ["python", "main.py"]

FastHTML app

fasthtml_app()
FROM ghcr.io/astral-sh/uv:python3.13-bookworm AS builder
WORKDIR /app
ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy
COPY pyproject.toml .
COPY uv.lock .
RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen --no-dev --no-install-project
COPY . .
RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen --no-dev
FROM python:3.13-slim
WORKDIR /app
COPY --from=builder /app /app
ENV PATH=/app/.venv/bin:$PATH
EXPOSE 5001
CMD ["python", "app.py"]

Go app

go_app()
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod .
COPY go.sum .
RUN --mount=type=cache,target=/go/pkg/mod go mod download
COPY . .
ENV CGO_ENABLED=0
RUN go build -ldflags="-s -w" -o /app .
FROM gcr.io/distroless/static
COPY --from=builder /app /app
EXPOSE 8080
CMD ["/app"]

Rust app

rust_app()
FROM rust:1-slim-bookworm AS builder
WORKDIR /src
COPY . .
RUN --mount=type=cache,target=/usr/local/cargo/registry cargo build --release
FROM gcr.io/distroless/static
COPY --from=builder /src/target/release/app /app
EXPOSE 8080
CMD ["/app"]

Node.js app

# Two-stage: build → serve static output
node_app(static=True)
FROM node:20-slim AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-slim
WORKDIR /app
RUN npm install -g serve
COPY --from=builder /app/dist .
EXPOSE 3000
CMD ["serve", "-s", ".", "-l", "3000"]

Auto-detect your project

detect_app() sniffs the project directory and picks the right builder automatically.

def tmp(files):
    d = Path(tempfile.mkdtemp())
    for f, c in files.items(): (d/f).write_text(c)
    return d

cases = [
    ({'go.mod': 'module x'},                              'go.mod     → go_app'),
    ({'Cargo.toml': '[package]'},                         'Cargo.toml → rust_app'),
    ({'package.json': '{}', 'pyproject.toml': '[p]'},     'package.json + pyproject.toml → fastapi_react'),
    ({'pyproject.toml': '[project]'},                     'pyproject.toml → python_app (port 8000)'),
]

for files, label in cases:
    df = detect_app(tmp(files))
    print(label)
go.mod     → go_app
Cargo.toml → rust_app
package.json + pyproject.toml → fastapi_react
pyproject.toml → python_app (port 8000)

Docker Compose

The Compose builder mirrors the Dockerfile API — chain .svc(), .network(), and .volume() calls, then render or save.

dc = (Compose()
    .svc('web',  image='nginx', ports={80: 80}, networks=['backend'])
      # we are passing a dummy dockerfile. point the python_app builder to your project dir for real use
    .svc('api',  build=python_app().save(), ports={8000: 8000},
         env={'DATABASE_URL': 'postgresql://db/app'},
         depends_on=['db'], networks=['backend'])
    .svc('db',   image='postgres:16',
         env={'POSTGRES_PASSWORD': 'secret'},
         volumes={'pgdata': '/var/lib/postgresql/data'})
    .network('backend')
    .volume('pgdata')).save()
dc
services:
  web:
    image: nginx
    ports:
    - 80:80
    networks:
    - backend
  api:
    depends_on:
    - db
    ports:
    - 8000:8000
    build: .
    environment:
    - DATABASE_URL=postgresql://db/app
    networks:
    - backend
  db:
    image: postgres:16
    environment:
    - POSTGRES_PASSWORD=secret
    volumes:
    - pgdata:/var/lib/postgresql/data
networks:
  backend: null
volumes:
  pgdata: null
dc.save('docker-compose.yml')  # writes the file
# Save to disk, bring it up, tear it down. This will fail as-is
dc.up()                        # docker compose up -d
dc.down(v=True)                # docker compose down -v

Running containers

Build, run, test — all from Python.

tmp = tempfile.mkdtemp()
df = (Dockerfile()
    .from_('alpine')
    .cmd(['echo', 'hello from docker']))

tag = df.build(tag='myapp:latest', path=tmp, no_creds=True)
print(f'Built: {tag}')

out = drun(tag, remove=True)
print(out.strip())

rmi(tag, force=True)
print('Cleaned up.')
Built: myapp:latest
hello from docker
Cleaned up.
# Start a named, detached container with port mapping
cid = drun('nginx', detach=True, ports={'80/tcp': 8080}, name='my-nginx', check=True)

print(containers())          # ['my-nginx']
print(logs('my-nginx', n=5)) # last 5 log lines

stop('my-nginx')
rm('my-nginx')
['my-nginx']
;; 2026/03/22 18:20:16 [notice] 1#1: OS: Linux 6.8.0-90-generic
2026/03/22 18:20:16 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1024:524288
2026/03/22 18:20:16 [notice] 1#1: start worker processes
2026/03/22 18:20:16 [notice] 1#1: start worker process 29
2026/03/22 18:20:16 [notice] 1#1: start worker process 30

Smoke-test an image

test() runs a command inside the image and returns True if it exits 0 — handy for CI.

assert test('python:3.12-slim', ['python', '-c', 'import sys; sys.exit(0)'])
assert not test('python:3.12-slim', ['python', '-c', 'raise SystemExit(1)'])

Config & Secrets

Store plain config (IPs, paths) and sensitive values (tokens, passwords) cleanly.

env_set('VPS_IP',    '1.2.3.4')
env_set('APP_NAME',  'my-app')

print(env_get('VPS_IP'))
print(env_get('APP_NAME'))
1.2.3.4
my-app
# Stored in OS keychain on macOS; env var fallback on Linux/CI
secret_set('FOO', 'bar')
print(secret_get('FOO'))
bar
# Read multiple secrets at once — missing keys are silently skipped
secret_set('DATABASE_URL', 'postgresql://...')
cfg = secrets('FOO', 'DATABASE_URL', 'MISSING_KEY')
cfg
{'FOO': 'bar', 'DATABASE_URL': 'postgresql://...'}

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

dockeasy-0.0.1.tar.gz (118.0 kB view details)

Uploaded Source

Built Distribution

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

dockeasy-0.0.1-py3-none-any.whl (17.3 kB view details)

Uploaded Python 3

File details

Details for the file dockeasy-0.0.1.tar.gz.

File metadata

  • Download URL: dockeasy-0.0.1.tar.gz
  • Upload date:
  • Size: 118.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for dockeasy-0.0.1.tar.gz
Algorithm Hash digest
SHA256 74c15fc13a8485129bb1b88542dfd2477835afd8caac704f23006d8bc0303ab7
MD5 553ac2adcec80e3a828a64007d3e771e
BLAKE2b-256 c746373f8ecb46bb92cd82075f879c12f2cadb0ce497f0cf6b7b6643cc19f418

See more details on using hashes here.

File details

Details for the file dockeasy-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: dockeasy-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 17.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for dockeasy-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f9e7f9db2f3683bec2b376fbb4e40e09268a06b5e8128b7428d96708d5b1a883
MD5 366494b26c8974b7e0c1f197200e690b
BLAKE2b-256 595d8b5ecb34db7edf672716795d4aa50c1e283345260ebb1e5eec8246779b2b

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