Containerization

How to build and run Stario apps in containers using Docker or Podman. Both use OCI-compatible images, so the same Containerfile (or Dockerfile) works with either runtime.

Multi-Stage Build

Separate dependency installation from your source code to maximize layer caching. Stario requires Python 3.14+.

# Stage 1: Builder - install dependencies
FROM python:3.14-slim-bookworm AS builder

WORKDIR /app
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/

# Install build deps for C extensions (brotli)
RUN apt-get update && \
    apt-get install -y --no-install-recommends gcc build-essential && \
    rm -rf /var/lib/apt/lists/*

# Install Python dependencies (cached layer)
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --frozen --no-install-project --no-dev

# Copy source and sync the project
COPY . .
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --frozen --no-dev

# Stage 2: Runtime - no build tools
FROM python:3.14-slim-bookworm

ENV PYTHONUNBUFFERED=1
WORKDIR /app

COPY --from=builder /app /app
ENV PATH="/app/.venv/bin:$PATH"

CMD ["python", "main.py"]

Build and run

# Docker
docker build -t myapp .
docker run -p 8000:8000 myapp

# Podman
podman build -t myapp .
podman run -p 8000:8000 myapp

Tip: Using uv in Docker gives you dramatically faster builds (up to 10x vs pip), proper lockfile support, and smart layer caching with --mount=type=cache.

Binding: Ports vs Unix Sockets

Port binding

The simplest approach - bind the container's port to the host:

# main.py
await app.serve(host="0.0.0.0", port=8000, workers=4)
docker run -p 8000:8000 myapp
podman run -p 8000:8000 myapp

Use host="0.0.0.0" inside the container so the server listens on all interfaces, not just loopback.

Unix sockets

For production behind a reverse proxy, Unix sockets avoid TCP overhead and port management. Mount a host directory into the container and have the app write its socket there:

# main.py
await app.serve(unix_socket="/sockets/app.sock")
mkdir -p /tmp/sockets

# Docker
docker run -v /tmp/sockets:/sockets myapp

# Podman
podman run -v /tmp/sockets:/sockets:z myapp

The reverse proxy (Caddy, Nginx) then connects to /tmp/sockets/app.sock on the host. See the Reverse Proxy (Caddy) how-to for a complete setup.

Note: The :z flag on Podman volumes is needed for SELinux-enabled hosts to allow the container to write to the mounted directory.

Docker Compose

services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - WORKERS=4
      - HOST=0.0.0.0

Or with Unix sockets:

services:
  web:
    build: .
    volumes:
      - sockets:/sockets
    environment:
      - WORKERS=4

volumes:
  sockets:

Environment-Based Configuration

A common pattern is to switch between TCP (development) and Unix socket (production) based on the environment:

import asyncio
import os
import sys
from stario import Stario, RichTracer, JsonTracer

async def main():
    if sys.stdout.isatty():
        tracer = RichTracer()
        host, port, workers, unix_socket = "127.0.0.1", 8000, 1, None
    else:
        tracer = JsonTracer()
        host, port, workers = "0.0.0.0", 8000, 4
        unix_socket = os.environ.get("SOCKET_PATH", "/sockets/app.sock")

    with tracer:
        app = Stario(tracer)
        app.get("/", home)
        await app.serve(
            host=host, port=port,
            workers=workers, unix_socket=unix_socket,
        )

if __name__ == "__main__":
    asyncio.run(main())

Resource Limits

Set memory and CPU limits to prevent runaway containers:

# Docker
docker run --memory 512m --cpus 1 -p 8000:8000 myapp

# Podman
podman run --memory 512m --cpus 1 -p 8000:8000 myapp

Production Tips

  • Use --restart on-failure:3 to automatically recover from crashes.
  • Pin your base image to a specific Python version (e.g., python:3.14-slim-bookworm) for reproducible builds.
  • Use .dockerignore to exclude .git, __pycache__, docs, and dev files from the build context.
  • Layer ordering matters - copy pyproject.toml and uv.lock before your source code so dependency installation is cached.