# shopdb-flask PULL stack for dev workstations (stable / edge channels). # # WHY this exists: pilots behind an inspecting proxy cannot reliably BUILD the # image from source (npm's many small fetches get reset mid-install). Instead # CI (.github/workflows/publish-images.yml) builds the image on clean runners # and publishes it to GHCR; this file just PULLS and RUNS it. One resumable # image pull survives the proxy where hundreds of npm requests do not. # # Nothing is built here (no `build:`), mirroring docker-compose.airgap.yml: # - api/migrate use `image:` (pulled from GHCR), never `build: .`. # - NO ./plugins bind mount; the image carries every plugin baked in. # - a one-shot `migrate` runs db upgrade + plugin upgrade-all + seed before # api starts, so `up -d` alone brings up a working site. # # Driven by scripts/dev-bootstrap.ps1, which sets SHOPDB_IMAGE (the channel # image ref), SHOPDB_ENV_LABEL, API_PORT and MYSQL_PORT per channel and picks a # compose project name (-p shopdb-stable / -p shopdb-edge) so the two channels # run side by side without colliding. x-app-env: &app-env FLASK_APP: wsgi.py 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} # Empty on stable; 'DEV' on edge, which lights the sidebar badge. SHOPDB_ENV_LABEL: ${SHOPDB_ENV_LABEL:-} services: db: image: mysql:8.4 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 ports: - "127.0.0.1:${MYSQL_PORT:-3306}:3306" healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"] interval: 10s timeout: 5s retries: 5 # One-shot schema + seed. Runs to completion and exits; api waits for it. # Every step is idempotent, so it is safe to run on every `up`. migrate: image: ${SHOPDB_IMAGE:?SHOPDB_IMAGE must be set to the channel image ref} pull_policy: always restart: "no" depends_on: db: condition: service_healthy environment: <<: *app-env command: - sh - -c - > flask db upgrade && flask plugin upgrade-all && flask seed permissions && flask seed settings && flask seed reference-data volumes: - instance_data:/app/instance api: image: ${SHOPDB_IMAGE:?SHOPDB_IMAGE must be set to the channel image ref} pull_policy: always restart: unless-stopped depends_on: db: condition: service_healthy migrate: condition: service_completed_successfully environment: <<: *app-env ports: # Loopback only, same as the DB below. This stack generates its own # dev secrets and may hold a real imported dataset; it has no business # listening on every interface of a laptop on a shop-floor network. - "127.0.0.1:${API_PORT:-5001}:5001" volumes: - instance_data:/app/instance volumes: db_data: instance_data: