Deploy Python Microservices with Docker Compose
Step-by-step guide to deploying Python microservices using Docker Compose. Covers Dockerfile setup, environment configuration, deployment commands, and real-world gotchas to avoid common pitfalls.
From Laptop to Live: How to Deploy Python Microservices with Docker Compose
You’ve built a shiny new Python microservice. It works perfectly on your machine. Then you try to deploy it, and suddenly everything breaks. Dependencies conflict, the database connection fails, and you end up debugging for three hours. I’ve been there, and it’s painful. That’s exactly why Docker Compose exists. Let me show you how we do this at PythonSkillset.
The Problem with "It Works on My Machine"
The real pain point for most Python developers is environment inconsistency. Your laptop has Python 3.11, but the production server runs 3.9. Your colleague uses Windows, you use macOS, and the deployment target is Linux. Microservices make this worse because you now have multiple services, each with its own dependencies, databases, and configuration.
Docker Compose is the solution. It lets you define all your services, networks, and volumes in one YAML file. One command spins everything up. No more "but it worked on my machine" conversations.
Our Setup: A Simple Microservice Example
Let’s say you have two services: a Flask API that handles user data and a Redis instance for caching. Here’s what a typical docker-compose.yml looks like:
version: '3.8'
services:
api:
build: ./api
ports:
- "5000:5000"
depends_on:
- redis
environment:
- REDIS_HOST=redis
- REDIS_PORT=6379
networks:
- app-network
redis:
image: redis:7-alpine
ports:
- "6379:6379"
networks:
- app-network
networks:
app-network:
driver: bridge
Notice how api service uses build: ./api. That tells Docker to look in the api folder for a Dockerfile.
The Dockerfile That Made This Work
You need a solid Dockerfile for your Python microservice. Here’s the one I use at PythonSkillset:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]
Keep it slim. The slim version of Python saves you hundreds of megabytes. And always use --no-cache-dir to avoid unnecessary cache bloat.
Making Configuration Dynamic
Hardcoding environment variables in your Python code is a recipe for disaster. Instead, use environment variables that Docker Compose injects:
import os
from flask import Flask
import redis
app = Flask(__name__)
REDIS_HOST = os.getenv('REDIS_HOST', 'localhost')
REDIS_PORT = int(os.getenv('REDIS_PORT', 6379))
cache = redis.Redis(host=REDIS_HOST, port=REDIS_PORT)
This way, your code works locally (with defaults) and in production with Docker Compose feeding the real values.
The Deployment Command You’ll Use Every Time
Once your docker-compose.yml and Dockerfiles are ready, deployment is just one line:
docker-compose up -d
The -d flag runs everything in detached mode. To see logs:
docker-compose logs -f
To rebuild after code changes:
docker-compose up -d --build
Real World Gotchas I’ve Learned the Hard Way
-
Port conflicts: If something else is using port 5000 on your host, change the left side of
"5000:5000"to"5001:5000". -
Volume persistence: If you don’t add volumes for databases, you lose data when containers restart. Redis doesn’t need it by default, but PostgreSQL does.
-
Dependency ordering:
depends_ononly waits for the container to start, not for the service inside it to be ready. For critical services, add health checks.
Scaling Without Losing Your Mind
The beauty of microservices with Docker Compose is scaling. Want three instances of your API? Just do:
docker-compose up -d --scale api=3
But remember: if you scale, you can’t use port mapping like "5000:5000" because multiple containers can’t share the same host port. Instead, use a reverse proxy like Nginx in your Compose file to distribute traffic.
What This Looks Like in Production
At PythonSkillset, we use a similar pattern. Our production setup adds environment variables through a .env file:
REDIS_PASSWORD=mysecretpass
API_KEY=abc123
Then reference them in docker-compose.yml:
environment:
- REDIS_PASSWORD=${REDIS_PASSWORD}
And we rotate secrets using Docker secrets or a vault service, never committing them to the repo.
Final Thoughts
Docker Compose gave me back my sanity. Instead of debugging cross-platform dependency hell, I now spend my time actually writing Python code that matters. It’s not a magic bullet—you still need to handle database migrations, logging, and monitoring—but it removes the biggest headache: environment inconsistency.
Start small. Define two services. Add a database. Then expand. Before you know it, you’ll have a full microservices stack running with a single command, and you’ll never look back.
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.