Docker AI API Deployment: Multi-Stage Builds, GPU Compose, and Nginx for Production 2026
TL;DR — Docker AI API deployment: multi-stage, GPU compose, Nginx. Markaicode: "Deploy FastAPI with docker compose, gunicorn uvicorn nginx. Multi-stage build, .dockerignore, production guide." AgileSoftLabs: "10K req/min on t3.medium. Multi-stage <150MB vs 1GB+ naive. .dockerignore for models. Gunicorn Uvicorn workers." Markaicode Ollama Docker: "Application container talks to Ollama. Docker Compose orchestrates. Host-mounted volume persists models. Port 11434." Markaicode Ollama: "Persistent volumes avoid re-downloads. GPU VRAM ceiling is first bottleneck." ZestMinds: "Stable deployment combines ASGI server, reverse proxy, container orchestration." Learn more with FastAPI AI tutorial, Ollama Docker, async backend, and job queue.
Markaicode introduces the approach: "Deploy FastAPI with docker compose, gunicorn uvicorn nginx. Multi-stage build, .dockerignore, production deployment guide for Python developers."
AgileSoftLabs adds the production benchmark: "Handles 10K req/min on t3.medium ECS tasks with Gunicorn Uvicorn workers. Multi-stage: FROM python:3.11-slim, install deps, COPY app, EXPOSE 8000, CMD uvicorn. Final image <150MB vs 1GB+ naive builds."
Docker AI API Architecture
Stage 1: Builder
install build deps
compile requirements
Stage 2: Runtime
python:3.12-slim
copy installed packages
final image <150MB"] Dockerignore[".dockerignore
exclude __pycache__
exclude .git
exclude models
exclude venv
exclude tests"] LayerCache["Layer Caching
COPY requirements.txt first
pip install --no-cache-dir
COPY app code last
cache deps across builds"] end subgraph Compose["Docker Compose Services"] API["FastAPI Container
build from Dockerfile
port 8000
uvicorn --workers 4
depends_on Ollama
OLLAMA_URL=http://ollama:11434"] Ollama["Ollama Container
image ollama/ollama
port 11434
GPU device reservation
volume ollama_data
persistent models"] Nginx["Nginx Container
image nginx
port 80, 443
proxy_pass to api:8000
SSL termination
rate limiting
SSE proxy_buffering off"] Redis["Redis Container
image redis
port 6379
volume redis_data
job queue + cache"] end subgraph Config["Production Configuration"] Workers["uvicorn Workers
--workers 4
2-4 per core
each own event loop
each own connection pool"] Health["Health Checks
FastAPI: GET /health
Ollama: GET /api/tags
Redis: redis-cli ping
interval 30s
retries 5"] Volume["Volume Persistence
ollama_data: models
redis_data: queue state
named volumes
survive container restarts"] Security["Security
non-root user
EXPOSE only needed ports
read-only filesystem
resource limits
secrets via env"] end subgraph Network["Docker Network"] Bridge["Bridge Network
container isolation
internal communication
api ↔ ollama ↔ redis
nginx → api external"] end MultiStage --> API Dockerignore --> MultiStage LayerCache --> MultiStage API --> Ollama Nginx --> API API --> Redis Workers --> API Health --> API Health --> Ollama Health --> Redis Volume --> Ollama Volume --> Redis Security --> API Bridge --> API Bridge --> Ollama Bridge --> Redis Bridge --> Nginx style Build fill:#4169E1,color:#fff style Compose fill:#39FF14,color:#000 style Config fill:#2D1B69,color:#fff
Service Configuration
| Service | Image | Port | Volume | Purpose |
|---|---|---|---|---|
| FastAPI | Custom build | 8000 | — | AI API endpoints |
| Ollama | ollama/ollama | 11434 | ollama_data | LLM inference |
| Nginx | nginx | 80, 443 | — | Reverse proxy, SSL |
| Redis | redis | 6379 | redis_data | Job queue, cache |
Implementation
from dataclasses import dataclass
from typing import Optional
from enum import Enum
class DeployComponent(Enum):
DOCKERFILE = "dockerfile"
COMPOSE = "compose"
NGINX = "nginx"
OPTIMIZATION = "optimization"
@dataclass
class DockerAIApiDeployGuide:
"""Docker AI API deployment implementation guide."""
def get_dockerfile(self) -> str:
"""Multi-stage Dockerfile for FastAPI AI API."""
return (
"# === MULTI-STAGE DOCKERFILE ===\n"
"\n"
"# Stage 1: Builder\n"
"FROM python:3.12-slim AS builder\n"
"WORKDIR /build\n"
"\n"
"# Install build dependencies\n"
"RUN apt-get update && \\\n"
" apt-get install -y --no-install-\n"
" recommends gcc && \\\n"
" rm -rf /var/lib/apt/lists/*\n"
"\n"
"# Copy requirements and install\n"
"COPY requirements.txt .\n"
"RUN pip install --no-cache-dir\n"
" --prefix=/install\n"
" -r requirements.txt\n"
"\n"
"# Stage 2: Runtime\n"
"FROM python:3.12-slim AS runtime\n"
"WORKDIR /app\n"
"\n"
"# Copy installed packages\n"
"COPY --from=builder\n"
" /install /usr/local\n"
"\n"
"# Copy application code\n"
"COPY . .\n"
"\n"
"# Non-root user for security\n"
"RUN useradd -m appuser && \\\n"
" chown -R appuser:appuser /app\n"
"USER appuser\n"
"\n"
"# Expose port\n"
"EXPOSE 8000\n"
"\n"
"# Health check\n"
"HEALTHCHECK --interval=30s\n"
" --timeout=10s --retries=5\n"
" CMD curl -f http://localhost:8000\n"
" /health || exit 1\n"
"\n"
"# Run with uvicorn\n"
"CMD ['uvicorn', 'main:app',\n"
" '--host', '0.0.0.0',\n"
" '--port', '8000',\n"
" '--workers', '4']\n"
"\n"
"# Final image: <150MB\n"
"# (vs 1GB+ naive builds)"
)
def get_dockerignore(self) -> str:
""".dockerignore file."""
return (
"# === .dockerignore ===\n"
"\n"
"# Python\n"
"__pycache__/\n"
"*.pyc\n"
"*.pyo\n"
".pytest_cache/\n"
".mypy_cache/\n"
".ruff_cache/\n"
"\n"
"# Virtual environments\n"
"venv/\n"
".venv/\n"
"env/\n"
"\n"
"# Git\n"
".git/\n"
".gitignore\n"
"\n"
"# Models (large files)\n"
"models/\n"
"*.gguf\n"
"*.bin\n"
"\n"
"# Tests\n"
"tests/\n"
"test_*.py\n"
"\n"
"# Docs\n"
"docs/\n"
"*.md\n"
"\n"
"# IDE\n"
".vscode/\n"
".idea/\n"
"\n"
# Docker\n"
"Dockerfile\n"
"docker-compose*.yml\n"
".dockerignore\n"
"\n"
"# Environment\n"
".env\n"
".env.*"
)
def get_docker_compose(self) -> str:
"""Docker Compose for FastAPI + Ollama + Nginx."""
return (
"# === DOCKER COMPOSE ===\n"
"# docker-compose.yml\n"
"\n"
"version: '3.8'\n"
"\n"
"services:\n"
" api:\n"
" build: .\n"
" container_name: ai-api\n"
" ports:\n"
" - '8000:8000'\n"
" environment:\n"
" - OLLAMA_URL=http://ollama:11434\n"
" - REDIS_URL=redis://redis:6379\n"
" depends_on:\n"
" ollama:\n"
" condition: service_healthy\n"
" redis:\n"
" condition: service_healthy\n"
" restart: unless-stopped\n"
" healthcheck:\n"
" test: ['CMD', 'curl', '-f',\n"
" 'http://localhost:8000/health']\n"
" interval: 30s\n"
" timeout: 10s\n"
" retries: 5\n"
" start_period: 30s\n"
" networks:\n"
" - ai-network\n"
" deploy:\n"
" resources:\n"
" limits:\n"
" memory: 1G\n"
"\n"
" ollama:\n"
" image: ollama/ollama:latest\n"
" container_name: ollama\n"
" ports:\n"
" - '11434:11434'\n"
" volumes:\n"
" - ollama_data:/root/.ollama\n"
" restart: unless-stopped\n"
" healthcheck:\n"
" test: ['CMD', 'curl', '-f',\n"
" 'http://localhost:11434/api/tags']\n"
" interval: 30s\n"
" timeout: 10s\n"
" retries: 5\n"
" start_period: 60s\n"
" networks:\n"
" - ai-network\n"
" deploy:\n"
" resources:\n"
" reservations:\n"
" devices:\n"
" - capabilities: [gpu]\n"
"\n"
" nginx:\n"
" image: nginx:alpine\n"
" container_name: nginx\n"
" ports:\n"
" - '80:80'\n"
" - '443:443'\n"
" volumes:\n"
" - ./nginx.conf:/etc/nginx/\n"
" conf.d/default.conf\n"
" - ./certs:/etc/nginx/certs\n"
" depends_on:\n"
" - api\n"
" restart: unless-stopped\n"
" networks:\n"
" - ai-network\n"
"\n"
" redis:\n"
" image: redis:7-alpine\n"
" container_name: redis\n"
" ports:\n"
" - '6379:6379'\n"
" volumes:\n"
" - redis_data:/data\n"
" restart: unless-stopped\n"
" healthcheck:\n"
" test: ['CMD',\n"
" 'redis-cli', 'ping']\n"
" interval: 30s\n"
" timeout: 10s\n"
" retries: 5\n"
" networks:\n"
" - ai-network\n"
"\n"
"volumes:\n"
" ollama_data:\n"
" redis_data:\n"
"\n"
"networks:\n"
" ai-network:\n"
" driver: bridge"
)
def get_nginx_config(self) -> str:
"""Nginx reverse proxy configuration."""
return (
"# === NGINX CONFIG ===\n"
"# nginx.conf\n"
"\n"
"upstream fastapi_backend {\n"
" server api:8000;\n"
" # Load balancing:\n"
" # server api2:8000;\n"
" # server api3:8000;\n"
"}\n"
"\n"
"# Rate limiting\n"
"limit_req_zone $binary_remote_addr\n"
" zone=api:10m rate=10r/s;\n"
"\n"
"server {\n"
" listen 80;\n"
" server_name _;\n"
" return 301 https://$host\n"
" $request_uri;\n"
"}\n"
"\n"
"server {\n"
" listen 443 ssl;\n"
" server_name api.example.com;\n"
" \n"
" ssl_certificate\n"
" /etc/nginx/certs/cert.pem;\n"
" ssl_certificate_key\n"
" /etc/nginx/certs/key.pem;\n"
" \n"
" # Client body size for\n"
" # large prompts\n"
" client_max_body_size 10M;\n"
" \n"
" # Gzip compression\n"
" gzip on;\n"
" gzip_types\n"
" application/json;\n"
" \n"
" # API endpoints\n"
" location / {\n"
" limit_req zone=api burst=20\n"
" nodelay;\n"
" \n"
" proxy_pass\n"
" http://fastapi_backend;\n"
" proxy_set_header Host $host;\n"
" proxy_set_header X-Real-IP\n"
" $remote_addr;\n"
" proxy_set_header X-Forwarded-For\n"
" $proxy_add_x_forwarded_for;\n"
" proxy_set_header\n"
" X-Forwarded-Proto $scheme;\n"
" }\n"
" \n"
" # SSE streaming endpoint\n"
" location /v1/chat/stream {\n"
" proxy_pass\n"
" http://fastapi_backend;\n"
" \n"
" # 6 SSE directives\n"
" proxy_buffering off;\n"
" proxy_cache off;\n"
" proxy_read_timeout 300s;\n"
" proxy_set_header\n"
" Connection '';\n"
" proxy_http_version 1.1;\n"
" chunked_transfer_encoding off;\n"
" }\n"
" \n"
" # Health check\n"
" location /health {\n"
" proxy_pass\n"
" http://fastapi_backend;\n"
" access_log off;\n"
" }\n"
"}"
)
def get_requirements(self) -> str:
"""requirements.txt for production."""
return (
"# === requirements.txt ===\n"
"fastapi>=0.115.0\n"
"uvicorn[standard]>=0.34.0\n"
"httpx>=0.28.0\n"
"pydantic>=2.0\n"
"redis>=5.0\n"
"arq>=0.26.0\n"
"sse-starlette>=2.0\n"
"\n"
"# Optional for production\n"
"# gunicorn>=23.0\n"
"# (for gunicorn + uvicorn\n"
"# workers setup)"
)
def get_production_tips(self) -> dict:
"""Production deployment tips."""
return {
"multi_stage": "Multi-stage Dockerfile: builder for deps, slim runtime. Final image <150MB vs 1GB+ naive.",
"dockerignore": ".dockerignore: exclude __pycache__, .git, models, venv, tests. Reduces build context.",
"layer_cache": "COPY requirements.txt before app code. Cache deps across builds. pip install --no-cache-dir.",
"workers": "uvicorn --workers 4 (2-4 per core). Each worker own event loop and connection pool.",
"gpu": "GPU device reservation in docker-compose deploy.resources. Ollama with GPU support.",
"volumes": "Persistent volumes for models (ollama_data) and queue (redis_data). Avoid re-downloads.",
"health": "Health checks for all services: FastAPI /health, Ollama /api/tags, Redis ping. interval 30s, retries 5.",
"nginx": "Nginx reverse proxy: SSL, rate limiting, SSE proxy_buffering off, load balancing.",
"security": "Non-root user in Dockerfile. EXPOSE only needed ports. Resource limits. Secrets via env.",
"scaling": "10K req/min on t3.medium with Gunicorn Uvicorn workers. Scale horizontally with multiple API containers.",
}
Docker AI API Deployment Checklist
- [ ] Multi-stage Dockerfile: builder stage for dependencies, slim runtime stage for smaller image
- [ ] Multi-stage: final image <150MB vs 1GB+ naive builds
- [ ] Base image: python:3.12-slim (not full python image)
- [ ] .dockerignore: exclude pycache, *.pyc, .git, models, venv, tests, docs, IDE files
- [ ] .dockerignore: exclude Dockerfile, docker-compose*.yml, .env files
- [ ] Layer caching: COPY requirements.txt before app code — cache deps across builds
- [ ] pip install --no-cache-dir to reduce image size
- [ ] Non-root user for security: useradd appuser, USER appuser
- [ ] EXPOSE only needed ports (8000 for FastAPI)
- [ ] HEALTHCHECK in Dockerfile: curl -f http://localhost:8000/health
- [ ] CMD: uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
- [ ] Docker Compose: FastAPI + Ollama + Nginx + Redis services
- [ ] FastAPI service: build from Dockerfile, port 8000, depends_on Ollama and Redis
- [ ] FastAPI: OLLAMA_URL=http://ollama:11434, REDIS_URL=redis://redis:6379 environment variables
- [ ] FastAPI: health check, restart unless-stopped, resource limits (memory 1G)
- [ ] Ollama service: image ollama/ollama, port 11434, GPU device reservation
- [ ] Ollama: volume ollama_data:/root/.ollama for persistent models — avoid re-downloads
- [ ] Ollama: health check GET /api/tags, start_period 60s (model loading time)
- [ ] Ollama: GPU VRAM ceiling is first bottleneck for latency-sensitive workloads
- [ ] Nginx service: image nginx:alpine, ports 80 and 443, depends_on api
- [ ] Nginx: SSL termination with certbot certificates
- [ ] Nginx: rate limiting with limit_req_zone (10r/s, burst 20)
- [ ] Nginx: SSE streaming — proxy_buffering off, proxy_read_timeout 300s, proxy_http_version 1.1
- [ ] Nginx: client_max_body_size 10M for large prompts
- [ ] Nginx: gzip compression for JSON responses
- [ ] Nginx: load balancing with upstream multiple FastAPI containers
- [ ] Redis service: image redis:7-alpine, port 6379, volume redis_data
- [ ] Redis: health check redis-cli ping, restart unless-stopped
- [ ] Docker network: bridge network for container isolation and internal communication
- [ ] Volume persistence: ollama_data for models, redis_data for queue state — survive restarts
- [ ] Persistent volumes avoid re-downloads and improve startup time
- [ ] Ephemeral containers guarantee clean state but re-download models on every run
- [ ] depends_on with condition: service_healthy for startup ordering
- [ ] restart: unless-stopped for all services
- [ ] Health checks: FastAPI GET /health, Ollama GET /api/tags, Redis redis-cli ping
- [ ] Health check: interval 30s, timeout 10s, retries 5, start_period 30-60s
- [ ] uvicorn workers: 2-4 per core, each worker own event loop and connection pool
- [ ] 10K req/min on t3.medium ECS tasks with Gunicorn Uvicorn workers
- [ ] Resource limits: memory limits per container to prevent OOM
- [ ] Security: non-root user, EXPOSE only needed ports, read-only filesystem where possible
- [ ] Security: secrets via environment variables, not hardcoded in image
- [ ] requirements.txt: fastapi, uvicorn[standard], httpx, pydantic, redis, arq, sse-starlette
- [ ] Optional: gunicorn for gunicorn + uvicorn workers setup
- [ ] Read FastAPI AI tutorial for API setup
- [ ] Read Ollama Docker for Ollama deployment
- [ ] Read async backend for async patterns
- [ ] Read job queue for background tasks
- [ ] Test: docker-compose up builds and starts all services
- [ ] Test: health checks pass for all services
- [ ] Test: FastAPI can reach Ollama at http://ollama:11434
- [ ] Test: Nginx proxies requests to FastAPI correctly
- [ ] Test: SSE streaming works through Nginx (not buffered)
- [ ] Test: volumes persist data across container restarts
- [ ] Test: image size is <150MB with multi-stage build
- [ ] Test: non-root user runs the application
- [ ] Document Dockerfile stages, compose services, Nginx config, volume strategy, health checks
FAQ
How do you deploy a FastAPI AI API with Docker in production?
Use multi-stage Dockerfile, docker-compose with Ollama GPU, Nginx reverse proxy, and uvicorn workers. Markaicode: "Deploy FastAPI with docker compose, gunicorn uvicorn nginx. Multi-stage build, .dockerignore, production deployment guide." AgileSoftLabs: "Handles 10K req/min on t3.medium ECS tasks with Gunicorn Uvicorn workers. Multi-stage: FROM python:3.11-slim, install deps, COPY app, EXPOSE 8000, CMD uvicorn. Final image <150MB vs 1GB+ naive builds. Includes .dockerignore for models." ZestMinds: "FastAPI deployment requires more than dev server. Stable deployment combines right Python runtime, ASGI server, reverse proxy, container orchestration." Deploy: (1) Multi-stage Dockerfile: python:3.12-slim, <150MB. (2) docker-compose: FastAPI + Ollama + Nginx. (3) uvicorn --workers 4 --host 0.0.0.0. (4) Nginx: SSL, load balancing, rate limiting. (5) GPU: device reservation in compose. (6) Health checks. (7) Volume persistence.
How do you create a multi-stage Dockerfile for AI APIs?
Use builder stage for dependencies and slim runtime stage for smaller image. AgileSoftLabs: "Multi-stage: FROM python:3.11-slim, install deps, COPY app, EXPOSE 8000, CMD uvicorn. Final image <150MB vs 1GB+ naive builds. Includes .dockerignore for models." Markaicode: "Multi-stage build, .dockerignore, gunicorn uvicorn nginx production deployment." Multi-stage: (1) Builder stage: install build deps, compile requirements. (2) Runtime stage: python:3.12-slim, copy installed packages. (3) .dockerignore: exclude pycache, .git, models, venv. (4) Non-root user for security. (5) EXPOSE 8000. (6) CMD uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4. (7) Final image <150MB. (8) Layer caching: COPY requirements.txt before app code.
How do you configure docker-compose for FastAPI and Ollama with GPU?
Use docker-compose with FastAPI, Ollama GPU, Nginx, Redis, and persistent volumes. Markaicode Ollama Docker: "For production you rarely run just Ollama — application container (FastAPI, Node.js) talks to it. Docker Compose keeps everything orchestrated. Host-mounted volume persists models. REST API exposed on port 11434." Markaicode Ollama Integration: "Persistent volumes avoid re-downloads and improve startup time. Ephemeral containers guarantee clean state but re-download models. For latency-sensitive workloads, GPU VRAM ceiling — not CPU or RAM — is almost always the first bottleneck." Compose: (1) FastAPI: build from Dockerfile, port 8000, depends_on Ollama. (2) Ollama: image ollama/ollama, port 11434, GPU device reservation, volume for models. (3) Nginx: image nginx, port 80/443, proxy to FastAPI. (4) Redis: image redis, port 6379, volume for persistence. (5) Networks: bridge for isolation. (6) Volumes: ollama_data, redis_data. (7) Restart: unless-stopped. (8) Health checks for all services.
How do you configure Nginx as a reverse proxy for FastAPI AI APIs?
Use Nginx to proxy to FastAPI, handle SSL, rate limiting, and SSE streaming. Markaicode: "gunicorn uvicorn nginx production deployment. Nginx reverse proxy for SSL and load balancing." ZestMinds: "Stable deployment combines ASGI server, reverse proxy, container orchestration." Nginx: (1) proxy_pass http://api:8000. (2) SSL termination with certbot. (3) Rate limiting: limit_req_zone. (4) SSE: proxy_buffering off, proxy_read_timeout 300s, proxy_http_version 1.1. (5) Load balancing: upstream with multiple FastAPI workers. (6) Static files: serve directly. (7) Gzip compression. (8) Client max body size for large prompts. (9) Health check endpoint passthrough.
How do you optimize Docker images for AI API deployment?
Use multi-stage builds, slim base images, .dockerignore, and layer caching. AgileSoftLabs: "Final image <150MB vs 1GB+ naive builds. Includes .dockerignore for models. Multi-stage FROM python:3.11-slim." Markaicode: "Persistent volumes avoid re-downloads and improve startup time." Optimization: (1) Multi-stage: builder for deps, slim runtime. (2) python:3.12-slim base (not full). (3) .dockerignore: pycache, .git, models, venv, tests. (4) Layer caching: COPY requirements.txt before app code. (5) pip install --no-cache-dir. (6) Non-root user. (7) Final image <150MB. (8) Persistent volumes for models — avoid re-download. (9) EXPOSE only needed ports. (10) HEALTHCHECK for orchestration.
Want a self-hosted AI company brain that does all of this out of the box?
Book a demo →