Deploying Python Apps with Docker Compose: A No-Fluff Guide
Learn to deploy Python apps with Docker Compose using a simple YAML file. This guide walks you through setting up a Flask app with PostgreSQL, multiple environments, and common pitfalls.
You've built a Python app that works perfectly on your machine. Now it needs to run on a server. Maybe you need a database alongside it, or a queue system. Docker Compose turns what could be a chaotic deployment into something you can reproduce reliably.
I've been working with Python deployments for a while now, and Docker Compose is one of those tools that makes you wonder how you managed without it. Let me walk you through a practical setup.
Why Docker Compose matters for Python apps
Here's a scenario PythonSkillset readers will recognize: You're building a Flask or Django app that needs PostgreSQL. Without Docker Compose, you'd install PostgreSQL manually, configure networking, set up environment variables, and hope nothing breaks when someone else tries to run it.
Docker Compose lets you define your entire stack in a single YAML file. One command, and everything spins up together. It's not just convenient — it's consistent across environments.
Your first Compose file
Let's start with a Python application structure:
myapp/
├── app.py
├── requirements.txt
├── Dockerfile
└── docker-compose.yml
Here's a Dockerfile for a basic Flask app:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]
And the docker-compose.yml that brings it to life:
version: '3.8'
services:
web:
build: .
ports:
- "5000:5000"
environment:
- DATABASE_URL=postgresql://user:password@db:5432/myapp
depends_on:
- db
db:
image: postgres:15
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=myapp
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Notice how the web service references "db" as the hostname. Docker Compose creates an internal network where services can talk to each other using service names. No more hardcoded IP addresses.
Running your stack
Once you've got your files ready, deployment is straightforward:
docker-compose up -d
The -d flag runs everything in detached mode. To see what's happening:
docker-compose logs -f web
Want to rebuild after code changes? Just run:
docker-compose up -d --build
Environment-specific configurations
Here's where things get interesting for real-world use. You'll likely have different settings for development and production. Docker Compose supports this through multiple files.
Create docker-compose.override.yml for local development extras:
version: '3.8'
services:
web:
environment:
- FLASK_ENV=development
- DEBUG=True
volumes:
- .:/app # Hot-reloading during development
For production, use docker-compose.prod.yml:
version: '3.8'
services:
web:
restart: always
environment:
- FLASK_ENV=production
- GUNICORN_WORKERS=4
Run production like this:
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Common pitfalls and fixes
Port conflicts: If port 5000 is already in use, change the external port: "5001:5000".
Database connection refused: Your app might start before PostgreSQL is ready. Add a simple retry loop in your Python code, or use the healthcheck option in Compose.
Persistent data loss: Always use named volumes for databases. Anonymous volumes get cleaned up more easily.
Security mistakes: Never hardcode passwords in your Compose file. Use environment variables or Docker secrets for production.
Scaling beyond simple deployments
Once you're comfortable with the basics, Docker Compose handles more advanced scenarios. You can add Redis for caching, NGINX for reverse proxying, or Celery workers for background tasks. Each service gets its own section in the Compose file, and they all communicate through the same internal network.
For monitoring, add a service like Prometheus or Grafana. For logging, forward everything to an ELK stack. The beauty of Compose is that adding these becomes just a few more lines of YAML.
Final thoughts
Docker Compose won't solve every deployment challenge, but it handles the most common ones elegantly. Your Python app becomes truly portable — anyone with Docker installed can run your entire stack with one command. That's the kind of reliability that makes deployments boring, and boring deployments are exactly what you want.
Start with a simple setup, then expand as you need. Your future self will thank you when you're deploying updates without breaking a sweat.
Comments
Questions, corrections, and tips stay visible for everyone reading this page.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.