Docker Compose Python: Multi-Service Applications Made Simple
Docker Compose is a tool that defines and runs multi-container applications using a single YAML file. Instead of running ten docker run commands to spin up your Python app, database, cache, and worker services, you write one docker-compose.yml file and run docker compose up. This eliminates coordination headaches, makes environments reproducible, and is the standard way teams develop locally in 2026.
I spent an afternoon onboarding a new developer on a project with a Flask app, PostgreSQL, Redis, Celery workers, and RabbitMQ. Without Compose, the setup was 10 manual steps and 20 minutes. With Compose, it was a single command and 30 seconds. Compose transforms onboarding from painful to frictionless.
What Is Docker Compose?
Docker Compose is a YAML-based orchestration tool for containers. It defines services (containers), networks, volumes, and environment variables in one file. When you run docker compose up, it starts all services, connects them to a shared network, and manages their lifecycle.
Key benefits:
- Single file definition: All services, networking, and volumes in docker-compose.yml.
- One-command startup:
docker compose upreplaces tendocker runcommands. - Service networking: Services auto-discover each other by name (app container resolves
postgres:5432as the database container). - Environment management: Environment variables, secrets, and config in one place.
- Reproducible development: New developers clone the repo and run
docker compose up. No manual setup.
A Real-World Docker Compose Example
Here's a docker-compose.yml for a FastAPI app with PostgreSQL and Redis:
version: '3.9'
services:
# Python FastAPI application
app:
build: .
container_name: fastapi-app
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://postgres:password@postgres:5432/myapp
- REDIS_URL=redis://redis:6379/0
- ENVIRONMENT=development
depends_on:
- postgres
- redis
volumes:
- .:/app
command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
# PostgreSQL database
postgres:
image: postgres:15-alpine
container_name: postgres-db
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
- POSTGRES_DB=myapp
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
# Redis cache
redis:
image: redis:7-alpine
container_name: redis-cache
ports:
- "6379:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
redis_data:
networks:
default:
name: myapp-network
Now your Dockerfile can be minimal:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]
And your FastAPI app can connect to services by name:
from fastapi import FastAPI
from sqlalchemy import create_engine
import os
DATABASE_URL = os.getenv("DATABASE_URL")
REDIS_URL = os.getenv("REDIS_URL")
engine = create_engine(DATABASE_URL)
app = FastAPI()
@app.get("/health")
def health_check():
return {"status": "ok", "db": DATABASE_URL, "cache": REDIS_URL}
Run the entire stack:
docker compose up
Output:
[+] Running 3/3
✔ postgres Running
✔ redis Running
✔ app Started
Your app is now running at http://localhost:8000 with a live PostgreSQL and Redis backend.
Key Compose File Sections
services: Each service is a container. Here, app, postgres, and redis are three separate containers that work together.
build: Instead of pulling an image, build from a Dockerfile in the current directory. If you wanted to pull postgres from Docker Hub instead, you'd use image: postgres:15-alpine.
environment: Pass environment variables to the container. Your Python code reads these with os.getenv().
depends_on: The app waits for postgres and redis to start (though "start" doesn't mean "ready"; use healthchecks for that).
volumes: Persist data. postgres_data:/var/lib/postgresql/data creates a named volume that survives container restarts. .:/app mounts your local code into the container, enabling live reload during development.
ports: Expose ports. 8000:8000 maps port 8000 inside the container to 8000 on your machine.
healthcheck: Docker periodically runs a command to verify the service is healthy. Postgres uses pg_isready; Redis uses redis-cli ping.
Common Compose Commands
# Start all services in the background
docker compose up -d
# View logs from all services
docker compose logs -f
# View logs from one service
docker compose logs -f app
# Stop all services
docker compose down
# Remove all services and volumes
docker compose down -v
# Execute a command in a running service
docker compose exec app python -m pytest
# Rebuild images
docker compose build
Overriding Compose Settings Locally
Create a docker-compose.override.yml file to override settings for local development without modifying the shared docker-compose.yml:
version: '3.9'
services:
app:
environment:
- DEBUG=true
- LOG_LEVEL=DEBUG
volumes:
- .:/app
Compose automatically merges override.yml with the main file. Perfect for debugging without changing shared files.
Running Multiple Environments
For production, create a separate file:
# Development
docker compose up
# Production (use docker-compose.prod.yml)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
The production file overrides development settings (removes hot-reload, uses external secrets, disables port exposure, etc.).
Key Takeaways
- Docker Compose defines multi-service setups in one YAML file.
- Services auto-discover each other by name (e.g.,
postgres:5432from the app service). depends_onensures startup order;healthcheckensures services are ready.- Use volumes for persistent data and live code reloading during development.
docker compose upreplaces manualdocker runcommands and is the team standard for local development.
Frequently Asked Questions
Can I use Docker Compose in production?
Docker Compose is designed for development and small deployments. For production, use Kubernetes or container orchestration platforms like AWS ECS, Google Cloud Run, or Docker Swarm. These handle scaling, networking, and failover better than Compose.
How do services communicate in Docker Compose?
By default, all services are on the same network and can resolve each other by service name. The postgres service is reachable from the app as postgres:5432 (no IP addresses needed). DNS resolution is automatic.
What is the difference between depends_on and healthcheck?
depends_on starts services in order but doesn't wait for them to be ready. A database container might start but not accept connections yet. healthcheck verifies readiness. Use both: depends_on for startup order, healthcheck for readiness.
Can I run shell commands in Compose?
Yes: docker compose exec app bash. This opens an interactive bash shell in the running app container, useful for debugging.
How do I pass secrets to Compose services?
Create a .env file (gitignored) and reference it in docker-compose.yml: env_file: .env. For production, use Docker secrets or environment variable injection from your deployment platform.