# shopdb-flask single-site docker-compose template. # # Per ADR-004, each adopting facility runs its own stack. This template # brings up MySQL + the API container and exposes the API on port 5001. # The Vue frontend is served separately by the API in production builds # (see register_frontend_routes in shopdb/__init__.py); for dev, run # `npm run dev` in frontend/ on a separate port. # # Usage: # cp .env.example .env # # edit .env with site-specific secrets and origins # docker compose up -d # # Refresh after pulling new code: # docker compose build api # docker compose up -d api services: db: image: mysql:8.4 # utf8mb4 server-wide so the auto-created MYSQL_DATABASE is utf8mb4, not the # image default. Keeps every site's schema on the same charset/collation. command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD must be set} MYSQL_DATABASE: shopdb_flask MYSQL_USER: shopdb MYSQL_PASSWORD: ${MYSQL_PASSWORD:?MYSQL_PASSWORD must be set} volumes: - db_data:/var/lib/mysql # Bind MySQL to loopback only. The api container reaches db over the # compose network regardless of this mapping; the published port is just # for local admin tools (mysqldump, a client on the host). Exposing 3306 # on all interfaces would put the database on the facility network. ports: - "127.0.0.1:${MYSQL_PORT:-3306}:3306" healthcheck: # 127.0.0.1, not localhost: localhost means the unix socket, and the # entrypoint's init pass answers on the socket while running the server # with --skip-networking. A socket ping therefore reports healthy DURING # init, and the api/migrate services start against a server that is about # to restart. Over TCP the probe stays red until the real server listens. test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"] interval: 10s timeout: 5s retries: 5 api: build: . restart: unless-stopped depends_on: db: condition: service_healthy environment: FLASK_ENV: production DATABASE_URL: mysql+pymysql://shopdb:${MYSQL_PASSWORD}@db:3306/shopdb_flask?charset=utf8mb4 SECRET_KEY: ${SECRET_KEY:?SECRET_KEY must be set} JWT_SECRET_KEY: ${JWT_SECRET_KEY:?JWT_SECRET_KEY must be set} CORS_ORIGINS: ${CORS_ORIGINS:?CORS_ORIGINS must be set} LOG_LEVEL: ${LOG_LEVEL:-INFO} ZABBIX_URL: ${ZABBIX_URL:-} ZABBIX_TOKEN: ${ZABBIX_TOKEN:-} ports: - "${API_PORT:-5001}:5001" volumes: - ./plugins:/app/plugins:ro # /app/instance is WRITTEN state, not code: plugins.json (which plugins # this site has enabled), uploaded floor plans, branding, model and # application images, employee photos, warranty proofs, slides, # printed-part files, and the Dell OAuth token. Without this volume a # `docker compose build api && up -d api` recreates the container and # takes all of it with it, so the site comes back with its plugins # disabled and MySQL rows pointing at files that no longer exist. - instance_data:/app/instance volumes: db_data: instance_data: