Files
orion/docs/deployment/docker.md
Samir Boulahtit e9253fbd84 refactor: rename Wizamart to Orion across entire codebase
Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart
with Orion/orion/ORION across 184 files. This includes database
identifiers, email addresses, domain references, R2 bucket names,
DNS prefixes, encryption salt, Celery app name, config defaults,
Docker configs, CI configs, documentation, seed data, and templates.

Renames homepage-wizamart.html template to homepage-orion.html.
Fixes duplicate file_pattern key in api.yaml architecture rule.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 16:46:56 +01:00

10 KiB

Docker Deployment

This guide covers deploying Orion using Docker and Docker Compose.

Best for: Teams who want consistent environments and easy rollbacks.


Development vs Production

Aspect Development Production
Compose file docker-compose.yml docker-compose.prod.yml
App server Hot reload enabled Multiple workers
Database Local volume Persistent volume with backups
SSL Not needed Required (via Nginx)
Logging Console File + centralized

Development Setup

# Start all services
make docker-up

# Or manually
docker compose up -d

# View logs
docker compose logs -f

# Stop services
make docker-down

Current Services

Service Port Purpose
db 5432 PostgreSQL database
redis 6379 Cache and queue broker
api 8000 FastAPI application

Production Deployment

1. Create Production Compose File

# docker-compose.prod.yml
services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
    restart: always
    ports:
      - "127.0.0.1:8000:8000"
    environment:
      DATABASE_URL: postgresql://orion_user:${DB_PASSWORD}@db:5432/orion_db
      REDIS_URL: redis://redis:6379/0
      CELERY_BROKER_URL: redis://redis:6379/1
    env_file:
      - .env
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    volumes:
      - uploads:/app/uploads
      - logs:/app/logs
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 1G

  celery:
    build: .
    restart: always
    command: celery -A app.celery worker --loglevel=info --concurrency=4
    environment:
      DATABASE_URL: postgresql://orion_user:${DB_PASSWORD}@db:5432/orion_db
      REDIS_URL: redis://redis:6379/0
      CELERY_BROKER_URL: redis://redis:6379/1
    env_file:
      - .env
    depends_on:
      - db
      - redis
    volumes:
      - logs:/app/logs
    deploy:
      resources:
        limits:
          memory: 512M

  celery-beat:
    build: .
    restart: always
    command: celery -A app.celery beat --loglevel=info
    environment:
      CELERY_BROKER_URL: redis://redis:6379/1
    env_file:
      - .env
    depends_on:
      - redis
    deploy:
      resources:
        limits:
          memory: 256M

  db:
    image: postgres:15-alpine
    restart: always
    environment:
      POSTGRES_DB: orion_db
      POSTGRES_USER: orion_user
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U orion_user -d orion_db"]
      interval: 10s
      timeout: 5s
      retries: 5
    deploy:
      resources:
        limits:
          memory: 512M

  redis:
    image: redis:7-alpine
    restart: always
    command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    deploy:
      resources:
        limits:
          memory: 300M

  nginx:
    image: nginx:alpine
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./static:/app/static:ro
      - uploads:/app/uploads:ro
      - /etc/letsencrypt:/etc/letsencrypt:ro
    depends_on:
      - api
    deploy:
      resources:
        limits:
          memory: 128M

volumes:
  postgres_data:
  redis_data:
  uploads:
  logs:

2. Create Dockerfile

# Dockerfile
FROM python:3.11-slim

# Install system dependencies
RUN apt-get update && apt-get install -y \
    curl \
    && rm -rf /var/lib/apt/lists/*

# Install Tailwind CLI
RUN curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 \
    && chmod +x tailwindcss-linux-x64 \
    && mv tailwindcss-linux-x64 /usr/local/bin/tailwindcss

WORKDIR /app

# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application
COPY . .

# Build Tailwind CSS
RUN tailwindcss -i ./static/admin/css/tailwind.css -o ./static/admin/css/tailwind.output.css --minify \
    && tailwindcss -i ./static/store/css/tailwind.css -o ./static/store/css/tailwind.output.css --minify \
    && tailwindcss -i ./static/shop/css/tailwind.css -o ./static/shop/css/tailwind.output.css --minify \
    && tailwindcss -i ./static/public/css/tailwind.css -o ./static/public/css/tailwind.output.css --minify

# Create non-root user
RUN useradd -m -u 1000 orion && chown -R orion:orion /app
USER orion

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

3. Nginx Configuration

mkdir -p nginx/conf.d
# nginx/conf.d/orion.conf
upstream api {
    server api:8000;
}

server {
    listen 80;
    server_name yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;

    # Static files
    location /static {
        alias /app/static;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    location /uploads {
        alias /app/uploads;
        expires 7d;
    }

    location / {
        proxy_pass http://api;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

4. Deploy

# Create .env file with production values
cp .env.example .env
nano .env

# Set database password
export DB_PASSWORD=$(openssl rand -hex 16)
echo "DB_PASSWORD=$DB_PASSWORD" >> .env

# Build and start
docker compose -f docker-compose.prod.yml build
docker compose -f docker-compose.prod.yml up -d

# Run migrations
docker compose -f docker-compose.prod.yml exec api alembic upgrade head

# Initialize data
docker compose -f docker-compose.prod.yml exec api python scripts/seed/init_production.py

Daily Operations

View Logs

# All services
docker compose -f docker-compose.prod.yml logs -f

# Specific service
docker compose -f docker-compose.prod.yml logs -f api
docker compose -f docker-compose.prod.yml logs -f celery

# Last 100 lines
docker compose -f docker-compose.prod.yml logs --tail 100 api

Access Container Shell

# API container
docker compose -f docker-compose.prod.yml exec api bash

# Database
docker compose -f docker-compose.prod.yml exec db psql -U orion_user -d orion_db

# Redis
docker compose -f docker-compose.prod.yml exec redis redis-cli

Restart Services

# Single service
docker compose -f docker-compose.prod.yml restart api

# All services
docker compose -f docker-compose.prod.yml restart

Deploy Updates

# Pull latest code
git pull origin main

# Rebuild and restart
docker compose -f docker-compose.prod.yml build api celery
docker compose -f docker-compose.prod.yml up -d api celery

# Run migrations if needed
docker compose -f docker-compose.prod.yml exec api alembic upgrade head

Rollback

# View image history
docker images orion-api

# Tag current as backup
docker tag orion-api:latest orion-api:backup

# Rollback to previous
docker compose -f docker-compose.prod.yml down api
docker tag orion-api:previous orion-api:latest
docker compose -f docker-compose.prod.yml up -d api

Backups

Database Backup

# Create backup
docker compose -f docker-compose.prod.yml exec db pg_dump -U orion_user orion_db | gzip > backup_$(date +%Y%m%d).sql.gz

# Restore backup
gunzip -c backup_20240115.sql.gz | docker compose -f docker-compose.prod.yml exec -T db psql -U orion_user -d orion_db

Volume Backup

# Backup all volumes
docker run --rm \
  -v orion_postgres_data:/data \
  -v $(pwd)/backups:/backup \
  alpine tar czf /backup/postgres_$(date +%Y%m%d).tar.gz /data

Monitoring

Resource Usage

docker stats

Health Checks

# Check service health
docker compose -f docker-compose.prod.yml ps

# Test API health
curl -s http://localhost:8000/health | jq

Troubleshooting

Container Won't Start

# Check logs
docker compose -f docker-compose.prod.yml logs api

# Check container status
docker compose -f docker-compose.prod.yml ps -a

# Inspect container
docker inspect <container_id>

Database Connection Issues

# Test from API container
docker compose -f docker-compose.prod.yml exec api python -c "
from app.core.database import engine
with engine.connect() as conn:
    print('Connected!')
"

Out of Disk Space

# Check disk usage
docker system df

# Clean up
docker system prune -a --volumes

Memory Issues

# Check memory usage
docker stats --no-stream

# Increase limits in docker-compose.prod.yml
deploy:
  resources:
    limits:
      memory: 2G

Security

Non-Root User

All containers run as non-root users. The Dockerfile creates a orion user.

Secret Management

# Use Docker secrets (Swarm mode)
echo "your-password" | docker secret create db_password -

# Or use environment files
# Never commit .env to git

Network Isolation

# Add to docker-compose.prod.yml
networks:
  frontend:
  backend:

services:
  nginx:
    networks:
      - frontend
  api:
    networks:
      - frontend
      - backend
  db:
    networks:
      - backend
  redis:
    networks:
      - backend

Scaling

Horizontal Scaling

# Scale API containers
docker compose -f docker-compose.prod.yml up -d --scale api=3

# Update nginx upstream
upstream api {
    server api_1:8000;
    server api_2:8000;
    server api_3:8000;
}

Moving to Kubernetes

When you outgrow Docker Compose, see our Kubernetes migration guide (coming soon).