# ============================================================================ # 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