This lab demonstrates building and running a simple FastAPI service with PostgreSQL using Docker and Docker Compose.
The project shows:
- A minimal API containerized with a slim, non-root Dockerfile
- A two-service stack (API + Postgres) connected via a private network
- Data persistence with a named volume
- Structured logging and a
/health
endpoint - Horizontal scaling of the API service
- Reflection on GitHub Copilot usage
docker-basics-lab/ │── app.py │── requirements.txt │── Dockerfile │── docker-compose.yml │── .dockerignore │── README.md
- Docker Engine: 28.4.0
- Docker Compose: v2.39.2
- Python: 3.11-slim (base image)
- FastAPI: 0.115.0
- Uvicorn: 0.30.6
- Postgres: 16
docker compose up -d --build
2. Check running containers
docker compose ps
Expected:
NAME STATUS PORTS
docker-basics-lab-api-1 Up 0.0.0.0:8000->8000/tcp
docker-basics-lab-db-1 Up 5432/tcp
3. Call health endpoint
curl http://localhost:8000/health
Expected:
{"status":"ok"}
4. View logs
docker compose logs -f api
Expected log line:
{"event":"health","ok":true}
💾 Persistence Demo
Create table & insert row:
docker exec -it $(docker ps -qf "name=_db_") \
psql -U app -d appdb -c "CREATE TABLE notes(id SERIAL PRIMARY KEY, msg TEXT);"
docker exec -it $(docker ps -qf "name=_db_") \
psql -U app -d appdb -c "INSERT INTO notes(msg) VALUES ('hello volume');"
Restart DB:
docker compose restart db
Query data:
docker exec -it $(docker ps -qf "name=_db_") \
psql -U app -d appdb -c "SELECT * FROM notes;"
✅ Expected: Row with "hello volume" remains after restart.
This confirms the named volume is persisting data.
📈 Scaling Demo
Scale API to 3 replicas:
docker compose up -d --scale api=3
docker compose ps
Expected: 3 containers of API running, all connected to the same DB.