Finance-Accounts/ar-aging-app/docker-compose.prod.yml

93 lines
3.1 KiB
YAML

# Production stack: [your reverse proxy] -> web (nginx: SPA + /api proxy) -> backend + mysql.
#
# cp .env.example .env.production # fill the PRODUCTION section first
# docker compose --env-file .env.production -f docker-compose.prod.yml up -d --build
#
# --env-file is REQUIRED: the ${AR_DOMAIN} / ${MYSQL_ROOT_PASSWORD} references below are
# resolved from it (env_file: alone only feeds the containers, not this YAML).
#
# SHARED SERVER (default): the app's only host port is 127.0.0.1:81 (the web UI). Point
# the server's reverse proxy for ar.utopiabrands.com at http://127.0.0.1:81 with
# client_max_body_size 2g; proxy_read_timeout 600s; proxy_request_buffering off;
# All other ports (backend 8000, mysql 3306) are container-internal and can never
# conflict with other apps on the box.
#
# DEDICATED SERVER: nothing else on 80/443? Start the bundled auto-HTTPS front instead:
# docker compose --env-file .env.production -f docker-compose.prod.yml --profile caddy up -d --build
#
# Sized for one 8 GB server (300-500 MB Excel parsing needs the RAM). Backend runs ONE
# worker by design — jobs and their progress live in-process. See deploy/DEPLOY.md.
services:
mysql:
image: mysql:8.4
restart: unless-stopped
env_file: .env.production # uses MYSQL_PASSWORD / MYSQL_DATABASE / MYSQL_USER
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?set MYSQL_ROOT_PASSWORD in .env.production}
command:
- --innodb-buffer-pool-size=1G
- --max-allowed-packet=256M
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-p${MYSQL_ROOT_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 12
# Not exposed to the host network — only the backend reaches it.
backend:
build: ./backend
restart: unless-stopped
env_file: .env.production
environment:
AR_DATA_DIR: /data
MYSQL_HOST: mysql
AR_DB_BACKEND: mysql
volumes:
- ar_data:/data
depends_on:
mysql:
condition: service_healthy
healthcheck:
test: ["CMD", "python", "-c",
"import urllib.request;urllib.request.urlopen('http://localhost:8000/api/health', timeout=5)"]
interval: 30s
timeout: 10s
retries: 3
web:
build:
context: ./frontend
target: prod
restart: unless-stopped
depends_on:
- backend
ports:
# Loopback-only: reachable by the server's own reverse proxy, never the internet.
# Host port 81 avoids clashing with anything else on a shared box.
- "127.0.0.1:81:80"
# OPTIONAL auto-HTTPS front for a DEDICATED server (--profile caddy). Not started by
# default: on a shared box another proxy usually owns 80/443 already.
caddy:
image: caddy:2-alpine
restart: unless-stopped
profiles: ["caddy"]
ports:
- "80:80"
- "443:443"
environment:
AR_DOMAIN: ${AR_DOMAIN:?set AR_DOMAIN in .env.production}
command: caddy reverse-proxy --from "https://${AR_DOMAIN}" --to web:80
volumes:
- caddy_data:/data
- caddy_config:/config
volumes:
mysql_data:
ar_data:
caddy_data:
caddy_config: