graph TB
subgraph stack["Full stack (make stack-up)"]
app["app\n(FastAPI, port 8000)"]
db["db\n(PostgreSQL + pgvector, port 5432)"]
valkey["valkey\n(Valkey/Redis, port 6379)"]
prometheus["prometheus\n(port 9090)"]
grafana["grafana\n(port 3000)"]
cadvisor["cadvisor\n(container metrics, port 8080)"]
end
app --> db
app -.->|"optional cache\n(set VALKEY_HOST=valkey)"| valkey
prometheus -->|"scrapes /metrics"| app
prometheus -->|"scrapes container stats"| cadvisor
grafana --> prometheus
Valkey is always started but only used by the app when VALKEY_HOST=valkey is set in your .env file. Without it the app falls back to an in-memory cache.
make docker-up ENV=development # start
make docker-down ENV=development # stop
make docker-logs ENV=development # tail logsmake stack-up ENV=development # start everything
make stack-down ENV=development # stop everything
make stack-logs ENV=development # tail all service logsmake docker-build ENV=productionThis runs scripts/build-docker.sh which builds and tags the image for the specified environment.
After make docker-up, run migrations against the containerised database:
make migrate ENV=developmentThis sources the correct .env file and runs alembic upgrade head from your local machine, connecting to the containerised PostgreSQL.
Each environment needs a .env.<env> file:
cp .env.example .env.development
cp .env.example .env.staging
cp .env.example .env.productionThe docker-up and stack-up commands pass the env file to Docker Compose via --env-file. Make sure POSTGRES_HOST=db in your Docker env files (not localhost) — the service name within the Compose network is db.
After make stack-up, Grafana is available at http://localhost:3000.
Default credentials: admin / admin
Pre-configured dashboards (in grafana/):
- API performance (request rate, latency, error rate)
- Rate limiting statistics
- Database connection pool health
- System resource usage