services: db: image: postgres:16 container_name: shuna-db restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} TZ: ${TZ} volumes: - shuna-pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 5s timeout: 5s retries: 10 # Exposed on a non-default host port for debugging; remove to keep db internal. ports: - "5433:5432" # Fence Shuna off the shared prod core (the host may run other services). Caps # keep a Shuna spike from starving those. Honored by `docker compose up` (Compose v2). deploy: resources: limits: memory: 1g cpus: "1.0" app: build: . container_name: shuna-app restart: unless-stopped depends_on: db: condition: service_healthy deploy: resources: limits: memory: 2g cpus: "2.0" environment: DATABASE_URL: ${DATABASE_URL} TZ: ${TZ} env_file: - .env volumes: # Persist user-uploaded recipe photos on the host (under /home -> backrest offsite; # ops/backup-db.sh tars it). Matches config.uploads_dir default (/app/uploads). - ./uploads:/app/uploads ports: - "8770:8000" healthcheck: # /health is public (no auth); python is in the image (no curl). test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8000/health', timeout=4).status==200 else 1)"] interval: 30s timeout: 6s retries: 3 start_period: 60s # migrations + seed run on boot frontend: build: ./frontend container_name: shuna-frontend restart: unless-stopped depends_on: - app ports: - "8771:80" deploy: resources: limits: memory: 256m cpus: "0.5" volumes: shuna-pgdata: