Docker
A production-ready Dockerfile using multi-stage build with uv:
FROM python:3.14-alpine AS build
# Install build dependenciesRUN apk add build-base libffi-dev
# Install uvCOPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/
WORKDIR /appCOPY . .
# Install dependencies (no dev dependencies, no cache for smaller image)RUN uv sync --no-dev --no-cache
FROM python:3.14-alpine AS runtime
WORKDIR /app
# Set up the virtual environmentENV VIRTUAL_ENV=/app/.venv \ PATH="/app/.venv/bin:$PATH"
# Copy the application and virtual environment from build stageCOPY --from=build /app /app
# Expose the port uvicorn will run onEXPOSE 8000
# Run the applicationCMD ["uvicorn", "--host", "0.0.0.0", "myapp.app:app"]Key points:
- Multi-stage build keeps the final image small by excluding build tools
- uv installs dependencies faster than pip
--no-devexcludes development dependencies--host 0.0.0.0binds to all interfaces (required in containers)
Adapting for Your Project
# If using poetry instead of uvRUN pip install poetry && poetry install --no-dev
# Update CMD to match your app moduleCMD ["uvicorn", "--host", "0.0.0.0", "your_package.app:app"]Building and Running
docker build -t myapp .Run with environment variables:
docker run -p 8000:8000 \ -e PYVIEW_SECRET="$(openssl rand -base64 32)" \ myappVisit http://localhost:8000 to see your app.
Docker Compose
For local development with Docker:
services: web: build: . ports: - "8000:8000" environment: - PYVIEW_SECRET=dev-secret-change-in-production volumes: # Mount source for development (optional) - ./src:/app/src:roRun with:
docker compose upHealth Checks
Add a health check endpoint to your app:
from starlette.routing import Routefrom starlette.responses import PlainTextResponse
async def health(request): return PlainTextResponse("ok")
# Add to your routesapp.routes.append(Route("/health", health))Then in your Dockerfile:
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:8000/health || exit 1Or in docker-compose.yml:
services: web: # ... healthcheck: test: ["CMD", "wget", "--spider", "-q", "http://localhost:8000/health"] interval: 30s timeout: 3s retries: 3Production Tips
Workers
CMD ["uvicorn", "--host", "0.0.0.0", "--workers", "4", "myapp.app:app"]PyView state is per-process. With multiple workers, you need sticky sessions so WebSocket connections reach the same worker that handled the initial request.
Security
- Never commit secrets to Dockerfiles
- Use environment variables or Docker secrets for
PYVIEW_SECRET - Run as non-root:
RUN adduser -D appuserUSER appuserNext Steps
- Fly.io — Deploy your Docker container to Fly.io