# Production stack. The images only copy pre-built output (`pnpm build`, committed), nothing is # installed or compiled on the server. Copy .env.example to .env, fill in the secrets, then: # docker compose up -d --build # docker compose exec api node dist/seed/seed.js # first run only: initial content + admin user # # gateway (nginx) ─┬─ site domain → web (Nuxt SSR) /api → api /media → minio # └─ admin domain → admin (static) /api → api /media → minio # fixed project name, so this stack never shares containers or volumes with docker-compose.dev.yml name: imen services: postgres: image: docker.arvancloud.ir/postgres:18-alpine restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: - pgdata:/var/lib/postgresql healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 10 minio: image: docker.arvancloud.ir/minio/minio:latest restart: unless-stopped command: server /data environment: MINIO_ROOT_USER: ${S3_ACCESS_KEY} MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY} volumes: - miniodata:/data # runs pending database migrations, then exits; api starts after it succeeds migrate: image: imen-migrate build: context: . dockerfile: docker/api.Dockerfile target: migrate restart: "no" depends_on: postgres: condition: service_healthy environment: DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB} api: image: imen-api build: context: . dockerfile: docker/api.Dockerfile target: runtime restart: unless-stopped depends_on: migrate: condition: service_completed_successfully minio: condition: service_started environment: NODE_ENV: production DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB} JWT_SECRET: ${JWT_SECRET} COOKIE_SECURE: ${COOKIE_SECURE:-true} TRUST_PROXY: ${TRUST_PROXY:-2} S3_ENDPOINT: http://minio:9000 S3_BUCKET: ${S3_BUCKET:-imen-media} S3_ACCESS_KEY: ${S3_ACCESS_KEY} S3_SECRET_KEY: ${S3_SECRET_KEY} S3_FORCE_PATH_STYLE: "true" S3_ENSURE_BUCKET: "true" MEDIA_PUBLIC_URL: /media WEB_REVALIDATE_URL: http://web:3000/_internal/revalidate REVALIDATE_SECRET: ${REVALIDATE_SECRET} ADMIN_PHONE: ${ADMIN_PHONE} ADMIN_PASSWORD: ${ADMIN_PASSWORD} ADMIN_NAME: ${ADMIN_NAME:-مدیر سایت} web: image: imen-web build: context: . dockerfile: docker/web.Dockerfile restart: unless-stopped depends_on: - api environment: NUXT_API_INTERNAL_URL: http://api:4000/api NUXT_REVALIDATE_SECRET: ${REVALIDATE_SECRET} NUXT_PUBLIC_SITE_URL: https://${SITE_DOMAIN} admin: image: imen-admin build: context: . dockerfile: docker/admin.Dockerfile restart: unless-stopped gateway: image: docker.arvancloud.ir/nginx:alpine restart: unless-stopped depends_on: - web - api - admin - minio environment: SITE_DOMAIN: ${SITE_DOMAIN} ADMIN_DOMAIN: ${ADMIN_DOMAIN} S3_BUCKET: ${S3_BUCKET:-imen-media} volumes: # rendered to /etc/nginx/conf.d/default.conf (replaces the stock server block) - ./docker/nginx/gateway.conf.template:/etc/nginx/templates/default.conf.template:ro ports: - "${HTTP_PORT:-8080}:80" # daily database dump into ./backups (kept 14 days) backup: image: docker.arvancloud.ir/postgres:18-alpine restart: unless-stopped depends_on: postgres: condition: service_healthy environment: PGHOST: postgres PGUSER: ${POSTGRES_USER} PGPASSWORD: ${POSTGRES_PASSWORD} PGDATABASE: ${POSTGRES_DB} volumes: - ./backups:/backups entrypoint: - /bin/sh - -c - | while true; do pg_dump -Fc -f /backups/imen-$$(date +%F).dump && find /backups -name '*.dump' -mtime +14 -delete sleep 86400 done volumes: pgdata: miniodata: