Docker Deployment¶
Imagine packing for a trip. Instead of hoping your destination has everything you need, you pack your own suitcase with clothes, toiletries, and essentials. Docker works the same way. It packages your application with everything it needs to run, ensuring it works identically everywhere.
No more "it works on my machine" problems!
What You'll Learn¶
- What Docker is and why use it
- Creating a Dockerfile for Ravyn
- Docker Compose for multi-container setups
- Production-ready Docker configuration
- Nginx + Supervisor setup
- Testing and deploying your container
Quick Start¶
Simple Dockerfile¶
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Build and Run¶
# Build the image
docker build -t myapp .
# Run the container
docker run -d -p 8000:8000 myapp
# Visit http://localhost:8000
What is Docker?¶
Docker is a platform that uses OS-level virtualization to deliver software in packages called containers.
In simple terms: Docker creates isolated environments (containers) that include your code and all its dependencies, ensuring your app runs the same way everywhere.
Why Docker?¶
Traditional Deployment Problems: - "Works on my machine" syndrome - Different environments have different dependencies - Manual setup on each server - Difficult to reproduce issues
Docker Solutions: - Consistent environment everywhere - All dependencies packaged together - Easy to scale and replicate - Simplified deployment process
Basic Dockerfile¶
Minimal Setup¶
# Start from official Python image
FROM python:3.11-slim
# Set working directory
WORKDIR /app
# Copy and install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Expose port
EXPOSE 8000
# Run the application
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
With Multiple Workers¶
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
# Run with 4 workers for better performance
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
Production-Ready Setup¶
For production, use Nginx as a reverse proxy and Supervisor to manage processes.
Project Structure¶
.
├── app/
│ ├── __init__.py
│ └── main.py
├── deployment/
│ ├── nginx.conf
│ └── supervisor.conf
├── Dockerfile
└── requirements.txt
Requirements¶
ravyn
uvicorn
nginx
supervisor
Application¶
# app/main.py
from typing import Union
from ravyn import Ravyn, Gateway, get
@get("/")
def home():
return {"Hello": "World"}
@get("/users/{user_id}")
def read_user(user_id: int, q: Union[str, None] = None):
return {"item_id": user_id, "q": q}
app = Ravyn(
routes=[
Gateway(handler=home),
Gateway(handler=read_user),
]
)
Nginx Configuration¶
Nginx acts as a reverse proxy, handling SSL, static files, and load balancing:
events {
worker_connections 1024;
}
http {
server {
listen 80;
client_max_body_size 4G;
server_name example.com;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-CSS-Protection "1; mode=block";
proxy_set_header X-Content-Type-Options "nosniff";
proxy_set_header Cache-Control "public,max-age=120,must-revalidate,s-maxage=120";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self';" always;
add_header X-Frame-Options "SAMEORIGIN";
proxy_redirect off;
proxy_buffering off;
proxy_pass http://uvicorn;
}
location /static {
# path for static files
root /path/to/app/static;
}
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream uvicorn {
server unix:/tmp/uvicorn.sock;
}
}
What this does: - Listens on port 80 - Proxies requests to Uvicorn (port 8000) - Adds security headers - Handles timeouts
Supervisor Configuration¶
Supervisor manages and monitors your processes:
[unix_http_server]
file = /run/supervisor.sock
chown = root:root
chmod = 0700
username = username
password = passwd
[supervisord]
nodaemon = true
nocleanup = true
logfile =/var/log/supervisord.log
loglevel = warn
childlogdir =/var/log
user = root
[supervisord]
nodaemon = true
nocleanup = true
logfile =/var/log/supervisord.log
loglevel = warn
childlogdir =/var/log
user = root
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl = unix:///run/supervisor.sock
username = username
password = passwd
[program:nginx]
command = nginx -g "daemon off;"
autostart = true
autorestart = true
priority = 200
stopwaitsecs = 60
stdout_logfile = /dev/stdout
stdout_logfile_maxbytes = 0
stderr_logfile = /dev/stderr
stderr_logfile_maxbytes = 0
[fcgi-program:uvicorn]
socket = tcp://localhost:8000
command = uvicorn --fd 0 app.main:app
numprocs = 4
priority = 14
startsecs = 10
autostart = true
autorestart = true
process_name = uvicorn-%(process_num)d
stdout_logfile = /dev/stdout
stdout_logfile_maxbytes = 0
redirect_stderr = true
What this does: 1. Configures Supervisor daemon 2. Manages Nginx process 3. Manages Uvicorn process 4. Auto-restarts on failure
Production Dockerfile¶
# Start from Python base image
FROM python:3.11-slim
# Install system dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \
nginx supervisor nginx-extras && \
rm -rf /var/lib/apt/lists/*
# Set working directory
WORKDIR /app
# Copy and install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY ./app /app/app
# Copy configuration files
COPY deployment/nginx.conf /etc/nginx/
COPY deployment/nginx.conf /etc/nginx/sites-enabled/default
COPY deployment/supervisor.conf /etc/supervisor/conf.d/
# Expose port
EXPOSE 80
# Start Supervisor (which starts Nginx and Uvicorn)
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
Dockerfile explained:
- Base image - Start with Python 3.11
- System packages - Install Nginx and Supervisor
- Working directory - Set to
/app - Dependencies - Install Python packages
- Application - Copy your code
- Configuration - Copy Nginx and Supervisor configs
- Start - Run Supervisor (manages everything)
Docker Compose¶
For multi-container setups (app + database):
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/myapp
- SECRET_KEY=${SECRET_KEY}
depends_on:
- db
db:
image: postgres:15
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=myapp
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
# Start all services
docker-compose up -d
# View logs
docker-compose logs -f
# Stop all services
docker-compose down
Building and Testing¶
Build the Image¶
docker build -t myapp:latest .
Test Locally¶
# Run the container
docker run -d --name myapp-container -p 80:80 myapp:latest
# Check if it's running
docker ps
# View logs
docker logs myapp-container
# Stop the container
docker stop myapp-container
# Remove the container
docker rm myapp-container
Verify Endpoints¶
Test your application:
OpenAPI Documentation¶
Access API docs (if enabled):
Disabling OpenAPI in Production¶
By default, OpenAPI docs are enabled. Disable them in production:
Via Code¶
from typing import Union
from ravyn import Ravyn, Gateway, get
@get("/")
def home():
return {"Hello": "World"}
@get("/users/{user_id}")
def read_user(user_id: int, q: Union[str, None] = None):
return {"item_id": user_id, "q": q}
app = Ravyn(
routes=[
Gateway(handler=home),
Gateway(handler=read_user),
],
enable_openapi=False,
)
Via Settings¶
from ravyn import RavynSettings
class AppSettings(RavynSettings):
enable_openapi: bool = False
Common Pitfalls & Fixes¶
Pitfall 1: Large Image Size¶
Problem: Docker image is too large.
Solution: Use multi-stage builds:
# Build stage
FROM python:3.11 as builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# Runtime stage
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]
Pitfall 2: Not Using .dockerignore¶
Problem: Copying unnecessary files.
Solution: Create .dockerignore:
__pycache__
*.pyc
*.pyo
*.pyd
.Python
env/
venv/
.git
.gitignore
.env
*.log
Pitfall 3: Running as Root¶
Problem: Security risk.
Solution: Create a non-root user:
FROM python:3.11-slim
# Create non-root user
RUN useradd -m -u 1000 appuser
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Switch to non-root user
USER appuser
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]
Best Practices¶
1. Use Specific Image Tags¶
# Good - specific version
FROM python:3.11.5-slim
# Avoid - can change unexpectedly
FROM python:latest
2. Leverage Build Cache¶
# Copy requirements first (changes less often)
COPY requirements.txt .
RUN pip install -r requirements.txt
# Copy code last (changes more often)
COPY . .
3. Use Health Checks¶
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:8000/health || exit 1
4. Set Resource Limits¶
docker run -d \
--memory="512m" \
--cpus="1.0" \
-p 8000:8000 \
myapp
Deployment to Cloud¶
Docker Hub¶
# Tag your image
docker tag myapp:latest username/myapp:latest
# Push to Docker Hub
docker push username/myapp:latest
AWS ECR¶
# Authenticate
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 123456789.dkr.ecr.us-east-1.amazonaws.com
# Tag and push
docker tag myapp:latest 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:latest
docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:latest
Learn More¶
- Docker Documentation - Official Docker docs
- Docker Compose - Multi-container apps
- Uvicorn - ASGI server
- Nginx - Web server
Next Steps¶
- Deployment Introduction - Deployment concepts
- Settings - Configure your app
- Security - Secure your deployment