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>
525 lines
10 KiB
Markdown
525 lines
10 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```yaml
|
|
# 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
|
|
# 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
|
|
|
|
```bash
|
|
mkdir -p nginx/conf.d
|
|
```
|
|
|
|
```nginx
|
|
# 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# Single service
|
|
docker compose -f docker-compose.prod.yml restart api
|
|
|
|
# All services
|
|
docker compose -f docker-compose.prod.yml restart
|
|
```
|
|
|
|
### Deploy Updates
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
docker stats
|
|
```
|
|
|
|
### Health Checks
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# Check disk usage
|
|
docker system df
|
|
|
|
# Clean up
|
|
docker system prune -a --volumes
|
|
```
|
|
|
|
### Memory Issues
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# Use Docker secrets (Swarm mode)
|
|
echo "your-password" | docker secret create db_password -
|
|
|
|
# Or use environment files
|
|
# Never commit .env to git
|
|
```
|
|
|
|
### Network Isolation
|
|
|
|
```yaml
|
|
# 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
|
|
|
|
```bash
|
|
# 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).
|