Overview#
A implementation of the RealWorld specification, a blogging platform similar to Medium, built with FastAPI. The project integrates a full observability stack, hardened container images, and automated CI/CD pipelines. It serves as a reference for building scalable, maintainable, and observable Python backend services.
- Source code: github.com/luizcarloscf/fastapi-realworld-example
- Tools: Python, FastAPI, SQLModel, Alembic, PostgreSQL, Docker, GitHub Actions, OpenTelemetry, Jaeger, Prometheus, OpenSearch, Grafana
sequenceDiagram
participant User
participant Conduit Backend
participant PostgreSQL
participant Otel Collector
participant Jaeger
participant OpenSearch
participant Prometheus
participant Grafana
User->>Conduit Backend: Requests
Conduit Backend->>PostgreSQL: Interacts with database
Conduit Backend->>User: Responses
Conduit Backend->>Otel Collector: Exposes metrics, traces, and logs
Otel Collector->>Jaeger: Stores traces
Otel Collector->>OpenSearch: Stores logs
Otel Collector->>Prometheus: Stores metrics
Jaeger->>Grafana: Provides traces
OpenSearch->>Grafana: Provides logs
Prometheus->>Grafana: Provides metrics
Grafana->>User: Displays monitoring dashboard
Backend#
The API is built with FastAPI following a layered architecture: routes, service, models, schemas, and core configuration. Data persistence uses PostgreSQL with SQLModel as the ORM, combining SQLAlchemy 2.0 async engine with Pydantic v2 validation, and Alembic for schema migrations.
- Framework: FastAPI with async request handling
- ORM: SQLModel (SQLAlchemy 2.0 + Pydantic v2)
- Migrations: Alembic
- Database: PostgreSQL
Authentication is implemented with JWT, and the codebase enforces static typing throughout using mypy, with flake8 and isort for linting and import ordering enforced via pre-commit hooks.
Observability#
The entire observability stack is instrumented through OpenTelemetry, with the collector routing signals to dedicated backends:
- Traces: Jaeger for distributed tracing across requests
- Metrics: Prometheus for time series collection exposed via
/metrics - Logs: OpenSearch for structured log aggregation and querying
- Dashboards: Grafana for unified visualization of all signals
This setup mirrors a real production environment, allowing end to end traceability from HTTP request to database query.
graph TB
subgraph tdf[Telemetry Data Flow]
subgraph subgraph_padding [ ]
style subgraph_padding fill:none,stroke:none;
subgraph od[Conduit]
ms(FastAPI Backend)
end
ms -.->|"OTLP
gRPC"| oc-grpc
subgraph oc[OTel Collector]
style oc fill:#97aef3,color:black;
oc-grpc[/"OTLP Receiver
listening on
grpc://localhost:4317"/]
oc-proc(Processors)
oc-spanmetrics[/"Span Metrics Connector"/]
oc-prom[/"OTLP HTTP Exporter"/]
oc-otlp[/"OTLP Exporter"/]
oc-opensearch[/"OpenSearch Exporter"/]
oc-grpc --> oc-proc
oc-proc --> oc-prom
oc-proc --> oc-otlp
oc-proc --> oc-opensearch
oc-proc --> oc-spanmetrics
oc-spanmetrics --> oc-prom
end
oc-prom -->|"localhost:9090/api/v1/otlp"| pr-sc
oc-otlp -->|gRPC| ja-col
oc-opensearch -->|HTTP| os-http
subgraph pr[Prometheus]
style pr fill:#e75128,color:black;
pr-sc[/"Prometheus OTLP Write Receiver"/]
pr-tsdb[(Prometheus TSDB)]
pr-http[/"Prometheus HTTP
listening on
localhost:9090"/]
pr-sc --> pr-tsdb
pr-tsdb --> pr-http
end
pr-b{{"Browser
Prometheus UI"}}
pr-http ---->|"localhost:9090/graph"| pr-b
subgraph ja[Jaeger]
style ja fill:#60d0e4,color:black;
ja-col[/"Jaeger Collector
listening on
grpc://jaeger:4317"/]
ja-db[(Jaeger DB)]
ja-http[/"Jaeger HTTP
listening on
localhost:16686"/]
ja-col --> ja-db
ja-db --> ja-http
end
subgraph os[OpenSearch]
style os fill:#005eb8,color:black;
os-http[/"OpenSearch
listening on
localhost:9200"/]
os-db[(OpenSearch Index)]
os-http ---> os-db
end
subgraph gr[Grafana]
style gr fill:#f8b91e,color:black;
gr-srv["Grafana Server"]
gr-http[/"Grafana HTTP
listening on
localhost:3000"/]
gr-srv --> gr-http
end
pr-http --> |"localhost:9090/api"| gr-srv
ja-http --> |"localhost:16686/api"| gr-srv
os-http --> |"localhost:9200/api"| gr-srv
ja-b{{"Browser
Jaeger UI"}}
ja-http ---->|"localhost:16686/search"| ja-b
gr-b{{"Browser
Grafana UI"}}
gr-http -->|"localhost:3000/dashboard"| gr-b
end
end
DevOps#
The full stack, including application, database, and observability infrastructure, is orchestrated with Docker Compose for a single command local deployment. The project also explores different container image strategies to address security and image size concerns:
- Standard: conventional single layer image
- Multistage: optimized multistage build separating dependencies from runtime
- Distroless: minimal runtime without shell or package manager
- Chainguard: hardened baseline with minimal attack surface
CI/CD is handled by GitHub Actions, automating test runs and publishing Docker images to Docker Hub on each release. Dependency updates are managed automatically via Dependabot.

