A production-ready FastAPI microservice template with batteries included. Clone it, rename the service, and start building — the observability, health probing, and Kubernetes plumbing are already wired up.
- Structured JSON logging via structlog with automatic correlation ID propagation through all log lines
- Prometheus metrics middleware: request counts, latencies, and active request gauge out of the box
- Liveness and readiness probes with separate semantics (see Architecture Decisions below)
- Correlation ID and Request ID headers propagated end-to-end
- Async SQLAlchemy 2.0 session management with connection pool tuning
- Type-safe configuration via pydantic-settings — misconfiguration fails at startup, not at runtime
- Kubernetes manifests: Deployment, Service, HorizontalPodAutoscaler
- GitHub Actions CI with coverage reporting
git clone git@github.com:loguntsovae/python-service-kit.git my-service
cd my-service
cp .env.example .env
pip install -e ".[test]"
uvicorn app.main:app --reloadThe service starts on http://localhost:8000.
GET /health— liveness probeGET /ready— readiness probeGET /metrics— Prometheus metrics
pytest tests/ -v --cov=app --cov-report=term-missingstructlog outputs newline-delimited JSON by default and uses Python's contextvars to carry the request context (request_id, correlation_id, method, path) into every log line emitted during a request — including lines from deep inside business logic. No need to thread a logger or context object through function arguments.
/health answers the question "is the process alive?" — it always returns 200 as long as the Python process is running. Kubernetes restarts pods whose liveness probe fails.
/ready answers "can this pod serve traffic?" — it checks real dependencies (database, etc.). Kubernetes stops routing requests to pods whose readiness probe fails without restarting them. This prevents cascading failures when a downstream dependency goes down: pods become temporarily unready rather than crash-looping.
The container runs as UID 1000. Kubernetes security policies commonly require runAsNonRoot: true, and running as root inside a container unnecessarily widens the blast radius of a container escape. The Dockerfile creates a dedicated app user and switches to it before the CMD.
pydantic-settings reads environment variables (and optionally a .env file) and validates every field's type before the application starts. If DATABASE_URL is missing or DB_POOL_SIZE is not an integer, the process exits immediately with a descriptive error rather than failing 10 minutes into a request. .env.example documents every supported variable.
app/
config.py — Settings loaded from environment
logging.py — structlog configuration
metrics.py — Prometheus counters, histograms, gauge
middleware.py — ObservabilityMiddleware (metrics + correlation IDs)
health.py — /health and /ready endpoints
db.py — Async SQLAlchemy engine and session factory
main.py — FastAPI app factory and lifespan handler
tests/
conftest.py — ASGI test client fixture
test_health.py — Health probe tests
k8s/
deployment.yaml
service.yaml
hpa.yaml