This approach is cleaner, isolated, and scales much better than managing multiple Gunicorn + Nginx systemd services manually.
π§© Overview
Weβll use:
- Docker: runs each Flask app in its own container.
- Gunicorn: runs Flask inside each container.
- Nginx: reverse proxy that routes to each app container.
- Docker Compose: orchestrates everything.
Your setup will look like this:
ββββββββββββββββ
app1.example.com ββββΆβ Flask App 1 β
ββββββββββββββββ
β
βΌ
ββββββββββ
β Nginx βββ app2.example.com ββΆ Flask App 2
ββββββββββ
βοΈ Step 1: Directory Structure
Letβs assume youβll host two Flask apps: app1 and app2.
Create this structure:
multi-flask/
βββ nginx/
β βββ nginx.conf
βββ app1/
β βββ app.py
β βββ requirements.txt
β βββ Dockerfile
βββ app2/
β βββ app.py
β βββ requirements.txt
β βββ Dockerfile
βββ docker-compose.yml
π§± Step 2: Each Flask App Setup
app1/app.py
from flask import Flask
app = Flask(**name**)
@app.route('/')
def home():
return "Hello from App 1!"
if __name__ == '__main__':
app.run(host='0.0.0.0')
app2/app.py
from flask import Flask
app = Flask(**name**)
@app.route('/')
def home():
return "Hello from App 2!"
if __name__ == '__main__':
app.run(host='0.0.0.0')
app1/requirements.txt and app2/requirements.txt
flask
gunicorn
π§° Step 3: Dockerfile for Each App
app1/Dockerfile
FROM python:3.10-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
Same for app2/Dockerfile.
π Step 4: Nginx Configuration
Create nginx/nginx.conf:
events {}
http {
server {
listen 80;
server_name app1.example.com;
location / {
proxy_pass http://app1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
server {
listen 80;
server_name app2.example.com;
location / {
proxy_pass http://app2:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
π§© Step 5: docker-compose.yml
Create docker-compose.yml in the root (multi-flask/):
version: "3.9"
services:
app1:
build: ./app1
container_name: app1
restart: always
expose:
- "5000"
app2:
build: ./app2
container_name: app2
restart: always
expose:
- "5000"
nginx:
image: nginx:latest
container_name: nginx
restart: always
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- app1
- app2
π Step 6: Run Everything
In the root directory:
docker-compose up -d --build
Check running containers:
docker ps
Visit:
http://app1.example.com β App 1
http://app2.example.com β App 2
π Step 7: Add HTTPS (Optional)
You can use Caddy or Traefik instead of Nginx for automatic HTTPS.
For example, using Caddy is as simple as:
caddy:
image: caddy
restart: always
ports: - "80:80" - "443:443"
volumes: - ./Caddyfile:/etc/caddy/Caddyfile
depends_on: - app1 - app2
And your Caddyfile:
app1.example.com {
reverse_proxy app1:5000
}
app2.example.com {
reverse_proxy app2:5000
}
Caddy will automatically fetch and renew SSL certificates. π₯
π§ Summary
| Component | Role |
|---|---|
| Flask | The app itself |
| Gunicorn | Runs Flask app efficiently |
| Nginx / Caddy | Reverse proxy (and SSL if desired) |
| Docker | Isolates each app |
| Docker Compose | Orchestrates all services |
