linguaielts-api / docker-compose.yml
AnhviNguyen
update cookie
02a9d1a
Raw
History Blame Contribute Delete
3.65 kB
# Stack gọn cho demo / Fly.io (<50 users): Postgres + API + Vue + Nginx
# ML (speaking/shadowing) chạy inline trong API — không cần Redis, MinIO, Celery, PgBouncer.
services:
db:
image: postgres:17-alpine
environment:
POSTGRES_USER: linguaielts
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: linguaielts
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U linguaielts"]
interval: 10s
timeout: 5s
retries: 5
api:
build: ./backend
user: "0"
command: >
sh -c "mkdir -p /app/uploads/avatars && chown -R appuser:appuser /app/uploads &&
for i in 1 2 3 4 5 6 7 8 9 10; do
su appuser -s /bin/sh -c 'alembic upgrade head' && break;
echo \"Waiting for database ($$i/10)…\";
sleep 4;
done &&
exec su appuser -s /bin/sh -c 'exec gunicorn app.main:app -w 2 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 --timeout 300 --access-logfile -'"
environment:
DATABASE_URL: postgresql+asyncpg://linguaielts:${DB_PASSWORD}@db:5432/linguaielts
PGBOUNCER_ENABLED: "false"
REDIS_URL: ""
REDIS_REQUIRED: "false"
CELERY_ENABLED: "false"
SECRET_KEY: ${SECRET_KEY}
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}
FRONTEND_ORIGIN: ${FRONTEND_ORIGIN}
SENTRY_DSN: ${SENTRY_DSN:-}
ENVIRONMENT: production
AUTO_CREATE_TABLES: "false"
STORAGE_BACKEND: ${STORAGE_BACKEND:-local}
CLOUDINARY_CLOUD_NAME: ${CLOUDINARY_CLOUD_NAME:-}
CLOUDINARY_API_KEY: ${CLOUDINARY_API_KEY:-}
CLOUDINARY_API_SECRET: ${CLOUDINARY_API_SECRET:-}
AVATAR_UPLOAD_DIR: uploads/avatars
METRICS_ENABLED: "true"
METRICS_TOKEN: ${METRICS_TOKEN}
ML_PRELOAD_ON_STARTUP: "true"
AUTH_HTTPONLY_REFRESH: "true"
AUTH_COOKIE_SECURE: "false"
REQUIRE_EMAIL_VERIFICATION: "false"
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USER: ${SMTP_USER:-}
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
SMTP_FROM: ${SMTP_FROM:-}
SMTP_USE_TLS: ${SMTP_USE_TLS:-true}
PASSWORD_RESET_EXPIRE_HOURS: ${PASSWORD_RESET_EXPIRE_HOURS:-24}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
GOOGLE_REDIRECT_URI: ${GOOGLE_REDIRECT_URI:-http://localhost/auth/google/callback}
depends_on:
db:
condition: service_healthy
volumes:
- ./backend/data:/app/data
- uploads:/app/uploads
expose:
- "8000"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 120s
restart: unless-stopped
frontend:
build:
context: ./fronted
args:
VITE_GOOGLE_CLIENT_ID: ${VITE_GOOGLE_CLIENT_ID:-}
VITE_GOOGLE_REDIRECT_URI: ${GOOGLE_REDIRECT_URI:-http://localhost/auth/google/callback}
VITE_AUDIO_CDN_BASE: /api/audio
VITE_AUDIO_CDN_EXT: .mp3
VITE_CLOUDINARY_CLOUD_NAME: ${CLOUDINARY_CLOUD_NAME:-dq955rfxi}
environment:
BACKEND_INTERNAL_URL: http://api:8000
expose:
- "80"
restart: unless-stopped
gateway:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.docker.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
api:
condition: service_healthy
frontend:
condition: service_started
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1/"]
interval: 30s
timeout: 5s
retries: 3
restart: unless-stopped
volumes:
pgdata:
uploads: