fashionistar-celery-queues / docker-compose.production.yml
FASHIONISTAR CI/CD
🔄 Celery Queues Deploy: 4aae6106b1530c3cc7d7d64f5e3c3e1d5015691d [GitHub Actions]
27c799c
Raw
History Blame
7.48 kB
# ============================================================================
# FASHIONISTAR — Production Docker Compose (Oracle Cloud Always Free VM)
# ============================================================================
# Deploys on: Oracle Cloud VM.Standard.A1.Flex (2 OCPUs / 12 GB RAM / ARM64)
#
# Services on Oracle (THIS FILE):
# nginx — Reverse proxy + SSL termination (Let's Encrypt)
# api — Django ASGI via Gunicorn + UvicornWorker (separate API logs)
# keepalive — Oracle idle-prevention pinger (every 4 minutes)
#
# ❌ NO postgres container — uses Neon PostgreSQL (DATABASE_URL in .env.production)
# ❌ NO redis container — uses Aiven Redis (REDIS_URL=rediss://... in .env.production)
#
# Services on Northflank (northflank.json):
# celery_worker — All background task queues (separate Celery logs)
# celery_beat — Celery Beat scheduler (cron job)
#
# Both API (Oracle) and Celery (Northflank) share the SAME Aiven Redis
# for broker/cache — no cross-cloud Redis setup needed!
#
# Usage:
# docker compose -f docker-compose.production.yml up -d
# docker compose -f docker-compose.production.yml pull && \
# docker compose -f docker-compose.production.yml up -d --no-deps api
# ============================================================================
version: '3.9'
services:
# ── Nginx: Reverse proxy + SSL + Static files ───────────────────────────────
nginx:
image: nginx:1.27-alpine
container_name: fashionistar_nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./deploy/nginx/conf.d:/etc/nginx/conf.d:ro
- /etc/letsencrypt:/etc/letsencrypt:ro
- static_files:/app/staticfiles:ro
- media_files:/app/mediafiles:ro
- nginx_logs:/var/log/nginx
depends_on:
api:
condition: service_healthy
restart: always
networks:
- fashionistar_net
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "5"
healthcheck:
test: ["CMD", "nginx", "-t"]
interval: 30s
timeout: 10s
retries: 3
# ── Django ASGI API Gateway ─────────────────────────────────────────────────
# PORT=10000 (matches fashionistar_backend_production.env)
# Uses Aiven Redis (TLS rediss://) from .env.production — NO local Redis needed
# Uses Neon PostgreSQL (DATABASE_URL) from .env.production — NO local DB needed
# Separate from Celery — has its own log volume (api_logs)
api:
image: ghcr.io/${GITHUB_REPOSITORY:-fashionistar-clothings/fashionistar_backend}/api:${IMAGE_TAG:-latest}
container_name: fashionistar_api
pull_policy: always
# Load ALL production secrets from .env.production (mirrors fashionistar_backend_production.env)
env_file:
- .env.production
environment:
# ── Core Settings ───────────────────────────────────────────────────────
- DJANGO_SETTINGS_MODULE=backend.config.production
# PORT must be 10000 to match your fashionistar_backend_production.env
- PORT=10000
# ── Oracle Cloud VM: 2 OCPUs → 4 Gunicorn workers (2×CPU, I/O-bound) ──
- GUNICORN_WORKERS=4
- GUNICORN_TIMEOUT=300
- GUNICORN_KEEPALIVE=75
- GUNICORN_LOG_LEVEL=info
# ── Updated Hosts — include Oracle domain ───────────────────────────────
- ALLOWED_HOSTS=api.fashionistar.net,fashionistar.net,fashionistar-frontend.vercel.app,localhost,127.0.0.1
- BACKEND_URL=https://api.fashionistar.net
- BACKEND_TUNNEL_URL=https://api.fashionistar.net
- SITE_URL=https://api.fashionistar.net
- HEALTH_URL_ENDPOINT=https://api.fashionistar.net/api/v1/health
# ── CORS / CSRF — include Oracle domain ────────────────────────────────
- CORS_ALLOWED_ORIGINS=https://fashionistar.net,https://fashionistar-frontend.vercel.app,https://api.fashionistar.net
- CSRF_TRUSTED_ORIGINS=https://fashionistar.net,https://fashionistar-frontend.vercel.app,https://api.fashionistar.net
# ── Log directory — separate from Celery ───────────────────────────────
- LOG_DIR=/app/logs/api
volumes:
- static_files:/app/staticfiles
- media_files:/app/mediafiles
# Dedicated API log volume — Celery logs are separate (on Northflank)
- api_logs:/app/logs/api
# No depends_on redis or postgres — both are external managed services
restart: always
deploy:
resources:
limits:
# Oracle 2 OCPU / 12 GB RAM — reserve headroom for OS + logging
cpus: '1.8'
memory: 9G
reservations:
cpus: '0.5'
memory: 2G
networks:
- fashionistar_net
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "10"
tag: "fashionistar-api"
healthcheck:
# PORT=10000 (matches production env)
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:10000/api/v1/health/"]
interval: 30s
timeout: 10s
start_period: 90s
retries: 5
# ── Oracle Keep-Alive (Idle Prevention) ─────────────────────────────────────
# Oracle reclaims Always Free instances if CPU < 10% for 7 consecutive days.
# This lightweight pinger on PORT=10000 prevents idle reclamation.
keepalive:
image: curlimages/curl:8-alpine
container_name: fashionistar_keepalive
command: >
sh -c "while true; do
curl -fsS http://api:10000/api/v1/health/ > /dev/null 2>&1 || true;
sleep 240;
done"
depends_on:
- api
restart: always
deploy:
resources:
limits:
cpus: '0.05'
memory: 32M
networks:
- fashionistar_net
logging:
driver: "json-file"
options:
max-size: "5m"
max-file: "2"
# ── Volumes ──────────────────────────────────────────────────────────────────
volumes:
static_files:
name: fashionistar_static_files
media_files:
name: fashionistar_media_files
# Dedicated API log volume (Django ASGI gateway logs only — Celery logs on Northflank)
api_logs:
name: fashionistar_api_logs
driver: local
driver_opts:
type: none
o: bind
device: /var/log/fashionistar/api
# Nginx access/error logs
nginx_logs:
name: fashionistar_nginx_logs
driver: local
driver_opts:
type: none
o: bind
device: /var/log/fashionistar/nginx
# ── Networks ─────────────────────────────────────────────────────────────────
networks:
fashionistar_net:
name: fashionistar_production_net
driver: bridge
ipam:
driver: default
config:
- subnet: 172.20.0.0/16