A hands-on multi-container backend application built to understand how FastAPI, PostgreSQL, Redis, Nginx, and pgAdmin work together using Docker Compose.
- ๐ Project Overview
- ๐ฏ Objectives
- โจ Features
- ๐๏ธ Architecture
- ๐ Request Flow
- ๐งฉ Services
- ๐งฐ Technology Stack
- ๐ Project Structure
- โ๏ธ Prerequisites
- ๐ Getting Started
- ๐ Environment Variables
- ๐ณ Docker Commands
- ๐ Application URLs
- ๐ก API Endpoints
- ๐๏ธ PostgreSQL & pgAdmin
- โก Redis Caching
- ๐ Nginx Reverse Proxy
- ๐พ Docker Volumes
- ๐ Docker Networks
- โค๏ธ Health Checks
- ๐ Security Considerations
- ๐งช Testing
- ๐ Troubleshooting
- ๐ฅ Project Demonstration
- ๐ What I Learned
- ๐ฎ Future Improvements
- ๐ง Key Takeaways
- ๐จโ๐ป Author
- โญ Final Note
This project demonstrates how a backend application can be split into multiple services and run together using Docker Compose.
The application contains:
- FastAPI โ REST API and core application logic
- PostgreSQL โ Persistent relational database
- Redis โ In-memory caching layer
- Nginx โ Reverse proxy and public application entry point
- pgAdmin โ Web-based PostgreSQL administration interface
The project was built as a hands-on learning exercise to understand:
- Docker containerization
- Multi-container application architecture
- Service-to-service communication
- Docker networking & isolation
- Persistent volume storage
- Environment-based configuration
- Caching strategies & invalidation
- Reverse proxy configurations
- Container health checks
The architecture is intentionally production-inspired, while remaining lightweight enough to spin up locally with a single command.
The primary goals of this project are:
- Containerize a FastAPI application using a production-ready Dockerfile
- Run multiple independent services in sync using Docker Compose
- Connect FastAPI with PostgreSQL using an asynchronous driver/ORM
- Implement Redis caching for read-heavy operations
- Configure Nginx as a reverse proxy entry point
- Attach Docker volumes for persistent database storage
- Manage secrets and parameters using environment files
- Isolate traffic across dedicated frontend and backend Docker networks
- Implement container health checks to ensure proper boot sequencing
- Master container-to-container communication using internal DNS
- โ FastAPI REST API: Asynchronous, high-performance web API
- โ PostgreSQL Database: Robust relational data storage
- โ SQLModel Integration: Unified ORM and Pydantic validation
- โ Psycopg Driver: Modern Python driver for PostgreSQL
- โ Redis Caching: Fast key-value retrieval for repeated queries
- โ Cache Invalidation: Automatic Redis cache purges upon user mutations
- โ Nginx Reverse Proxy: Traffic routing, header management, and isolation
- โ pgAdmin Web UI: Visual database dashboard for monitoring data
- โ Production-Style Dockerfile: Multi-stage build support
- โ Docker Compose Orchestration: Declarative multi-service setup
- โ Persistent Volumes: Data survives container lifecycles
- โ Network Segregation: Distinct frontend and backend bridge networks
- โ Health Checks: Container startup dependencies tied to service readiness
- โ
Interactive Docs: Out-of-the-box Swagger UI via
/docs
The application follows a decoupled multi-tier architecture:
โโโโโโโโโโโโโโโโโโโ
โ Browser โ
โโโโโโโโโโฌโโโโโโโโโ
โ
localhost:8080
โ
โผ
โโโโโโโโโโโโโโโโโโโ
โ Nginx โ
โ Reverse Proxy โ
โโโโโโโโโโฌโโโโโโโโโ
โ
frontend network
โ
โผ
โโโโโโโโโโโโโโโโโโโ
โ FastAPI โ
โ Backend โ
โโโโโโโโโฌโโฌโโโโโโโโ
โ โ
backend โ โ network
โโโโโโโโโโโโโโ โโโโโโโโโโโโโโ
โผ โผ
โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
โ PostgreSQL โ โ Redis โ
โ Database โ โ Cache โ
โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
โฒ
โ
โโโโโโโโดโโโโโโโ
โ pgAdmin โ
โ DB Manager โ
โโโโโโโโโโโโโโโ
| Component | Why It Exists |
|---|---|
| FastAPI | Provides the core backend REST API and business logic |
| PostgreSQL | Stores relational application data persistently |
| Redis | Provides ultra-fast temporary/cache storage |
| Nginx | Acts as a reverse proxy, security barrier, and entry point |
| pgAdmin | Provides a browser-based management interface for PostgreSQL |
| Docker | Packages applications and dependencies into reproducible containers |
| Docker Compose | Declares, connects, and orchestrates all containers together |
Client
โ
โผ
Nginx :8080
โ
โผ
FastAPI :8000
โ
โผ
Check Redis
โ
โโโโโโโโโ Cache Hit โโโโโโโโโบ Return Cached Users
โ
โโโโโโโโโ Cache Miss
โ
โผ
PostgreSQL
โ
โผ
Store Result in Redis
โ
โผ
Return Users
Why cache? Avoids querying PostgreSQL every time repeated, non-mutated data is requested, dramatically reducing database load.
Client
โ
โผ
Nginx
โ
โผ
FastAPI
โ
โผ
PostgreSQL (Write record)
โ
โผ
Invalidate Redis "users" Cache
โ
โผ
Return Created User
Why invalidate? Ensures clients never receive stale user lists on subsequent reads after an insert.
| Service | Purpose | Container Port | Host Port | Network |
|---|---|---|---|---|
| fastapi | REST API Backend | 8000 |
8000 (optional) |
frontend, backend |
| postgres | Database | 5432 |
Not exposed | backend |
| redis | In-Memory Cache | 6379 |
Not exposed | backend |
| nginx | Reverse Proxy | 80 |
8080 |
frontend |
| pgadmin | Database UI | 80 |
5050 |
backend |
Host ports and container ports do not need to match:
ports:
- "8080:80"Host Machine (localhost:8080)
โ
โผ
Nginx Container (port 80)
| Technology | Purpose |
|---|---|
| Python | Core programming language |
| FastAPI | High-performance backend web framework |
| SQLModel | ORM layer combining SQLAlchemy and Pydantic |
| Psycopg | Modern Python driver for PostgreSQL |
| PostgreSQL | Primary relational database |
| Redis | In-memory key-value cache |
| Nginx | Reverse proxy and HTTP gateway |
| pgAdmin | PostgreSQL management GUI |
| Docker | Containerization platform |
| Docker Compose | Multi-container coordination |
.
โโโ main.py # FastAPI application and API endpoints
โโโ database.py # Database connection, SQLModel engine, session dependencies
โโโ models.py # SQLModel data models & schemas
โโโ requirements.txt # Python application dependencies
โ
โโโ Dockerfile # FastAPI container image definition
โโโ docker-compose.yml # Multi-service composition configuration
โโโ nginx.conf # Reverse proxy configuration
โ
โโโ .env # Local secrets and config (do NOT commit)
โโโ .env.example # Template for environment variables
โโโ .gitignore # Git untracked file rules
โโโ .dockerignore # Docker context exclusions
โ
โโโ README.md # Comprehensive project documentation
Ensure you have the following installed locally:
- Docker Desktop (Docker Engine โฅ 24.x, Docker Compose v2)
- Git
Verify installations:
docker --version
docker compose versiongit clone <YOUR_GITHUB_REPOSITORY_URL>
cd <PROJECT_FOLDER>Copy the sample environment file:
cp .env.example .envUpdate credentials inside .env:
POSTGRES_USER=postgres
POSTGRES_PASSWORD=your_password
POSTGRES_DB=myapp
PGADMIN_DEFAULT_EMAIL[email protected]
PGADMIN_DEFAULT_PASSWORD=your_passwordRun all services using Docker Compose:
docker compose up --build -ddocker compose ps
docker compose logs -f| Variable | Description |
|---|---|
POSTGRES_USER |
PostgreSQL superuser username |
POSTGRES_PASSWORD |
PostgreSQL superuser password |
POSTGRES_DB |
Application database name |
PGADMIN_DEFAULT_EMAIL |
Web login username/email for pgAdmin |
PGADMIN_DEFAULT_PASSWORD |
Web login password for pgAdmin |
Inside Docker Compose networks, service names act as internal hostnames:
- Database URL:
postgresql+psycopg://postgres:your_password@postgres:5432/myapp - Redis URL:
redis://redis:6379
| Action | Command |
|---|---|
| Start in background | docker compose up -d |
| Rebuild images & start | docker compose up --build -d |
| List container statuses | docker compose ps |
| Follow all logs | docker compose logs -f |
| Follow service logs | `docker compose logs -f <fastapi |
| Stop containers | docker compose stop |
| Tear down containers | docker compose down |
| Tear down & delete volumes | docker compose down -v |
| Service | URL | Notes |
|---|---|---|
| Nginx โ FastAPI | http://localhost:8080 | Public Entry Point |
| FastAPI Direct | http://localhost:8000 | Internal/Host debugging |
| Swagger UI Docs | http://localhost:8080/docs | Interactive API exploration |
| Health Check | http://localhost:8080/health | Uptime check |
| Redis Cache Test | http://localhost:8080/redis_test | Redis sanity endpoint |
| pgAdmin Web UI | http://localhost:5050 | Database administration |
- Endpoint:
GET /health - Response:
{ "status": "healthy" }
- Endpoint:
POST /users - Payload:
{ "name": "Vijay", "email": "[email protected]" } - Action: Inserts user into PostgreSQL and clears the cached user list from Redis.
- Endpoint:
GET /users - Action: Fetches from Redis cache if available; queries PostgreSQL and populates cache on miss.
- Endpoint:
GET /redis_test - Response:
{ "redis_msg": "hello from redis" }
FastAPI connects to PostgreSQL across the internal Docker bridge network using postgres:5432.
- Open http://localhost:5050.
- Authenticate with
PGADMIN_DEFAULT_EMAILandPGADMIN_DEFAULT_PASSWORD. - Add a new server connection using:
- Host name/address:
postgres - Port:
5432 - Maintenance database:
myapp - Username:
postgres - Password:
<POSTGRES_PASSWORD>
- Host name/address:
Redis acts as a low-latency read-through cache layer:
GET /users
โ
โผ
Check Redis
โ
โโโโโโดโโโโโ
โ โ
Hit Miss
โ โ
โผ โผ
Return PostgreSQL
Data โ
โผ
Store in Redis
โ
โผ
Return Data
Cache invalidation occurs immediately when data changes:
POST /users โโโบ PostgreSQL โโโบ Delete "users" Redis Key
Nginx acts as the single public gateway on port 8080:
Browser โโโบ Nginx (:80) โโโบ FastAPI (:8000)
Configuration snippet from nginx.conf:
events {}
http {
server {
listen 80;
location / {
proxy_pass http://fastapi:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}PostgreSQL uses a named volume to ensure persistence across container removals:
volumes:
postgres_data:Mounted to:
/var/lib/postgresql/data
Key Takeaway: Stopping or recreating a container does not delete stored records. Data persists safely inside
postgres_datauntil wiped withdocker compose down -v.
Network isolation ensures backend services are never directly reachable from the host machine or public internet.
frontend
โ
Nginx
โ
FastAPI
โ
backend
โโโโโโโโผโโโโโโโ
โ โ โ
PostgreSQL Redis pgAdmin
- Frontend Network: Accessible by Nginx and FastAPI.
- Backend Network: Accessible by FastAPI, PostgreSQL, Redis, and pgAdmin.
Container startup does not guarantee application readiness. Health checks ensure services only accept connections when fully booted:
- PostgreSQL: Checked via
pg_isready - Redis: Checked via
redis-cli ping - FastAPI: Checked via
GET /health
In docker-compose.yml:
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthyThis project focuses on architectural fundamentals. Good practices implemented:
- Credentials managed via
.envand excluded from source control via.gitignore .envexcluded from image layers via.dockerignore- Database and Cache ports (
5432,6379) are unexposed to the host - All internal communication happens within isolated private bridge networks
- SSL/TLS Termination via Let's Encrypt / Certbot on Nginx
- HashiCorp Vault or AWS Secrets Manager for credential storage
- Rate limiting and WAF rules via Nginx
- JWT-based authentication & role-based authorization
- Automated database backup cron jobs to external storage (e.g., AWS S3)
The system was verified across key integration layers:
- FastAPI: Verified endpoints (
/health,/users,/docs) via Swagger and curl - Redis: Confirmed cache hits and invalidation logic with
/redis_testand/users - PostgreSQL: Validated persistent data storage after container restarts
- Nginx: Confirmed reverse proxy forwarding to upstream FastAPI containers
- Docker: Monitored real-time system logs using
docker compose logs
โ PostgreSQL password authentication failed
PostgreSQL sets credentials on initial database creation. If .env is updated after the volume has already been initialized, the password won't change automatically.
Development Fix:
docker compose down -v
docker compose up --build -d
โ ๏ธ Note: This deletes the database volume and all existing data.
โ FastAPI cannot connect to PostgreSQL
Do not use localhost:5432 from inside a container. Use the Docker service name:
DATABASE_URL = "postgresql+psycopg://postgres:password@postgres:5432/myapp"โ FastAPI cannot connect to Redis
Use the Redis container service name:
REDIS_URL = "redis://redis:6379"โ Nginx cannot reach FastAPI
Verify that both services share the frontend network and that proxy_pass points to http://fastapi:8000;.
Check logs:
docker compose logs nginx
docker compose logs fastapiโ Host port 8080 already in use
Change the host mapping in docker-compose.yml:
ports:
- "8081:80"โ Stale or inconsistent Redis cache data
Restart the Redis container to flush memory:
docker compose restart redisKey concepts verified during live testing:
- Docker Images & Multi-Stage Builds: Creating lean application runtime layers.
- Container Lifecycle: Running multi-service topologies in detached mode.
- Volume Persistence: Proving data survival across container destructions.
- Service Discovery: Validating name resolution via Docker internal DNS.
- End-to-End Traffic Routing: Tracing requests from client browser through Nginx to FastAPI, PostgreSQL, and Redis.
- Docker Fundamentals: Images vs. containers, bind mounts vs. named volumes, bridge networks.
- Service Orchestration: Coordinating dependencies and health status with Docker Compose.
- Backend Architecture: Structuring production-ready APIs with FastAPI, SQLModel, and Psycopg.
- Caching Mechanics: Handling read-through patterns and mitigating cache staleness.
- Network Boundaries: Segmenting public-facing proxies from private backend databases.
- Automated database migrations using Alembic
- Redis key expiration policies (TTL)
- Unit & integration test suites run via Pytest in CI/CD
- Centralized metric monitoring using Prometheus & Grafana
- Cloud deployment via Kubernetes & Terraform
- SSL/TLS automation using Let's Encrypt
Application โโโบ FastAPI โโโฌโโโบ PostgreSQL
โโโโบ Redis
โฒ
โ
Nginx
โฒ
โ
Client
Understanding how individual services communicate, isolate, and persist data is far more valuable than simply knowing how to run docker compose up. Fundamentals compound.
Vijay Rangvani
IT Engineer | Backend & Cloud Explorer
- Focus Areas: Python, FastAPI, Docker, Kubernetes, Cloud Architecture
"Small steps look ordinary today, but they build the foundations for tomorrow."