Air-gapped sites cannot pip install / npm ci / docker pull, so a build-at-site compose (build: .) fails and reports 'service api is not running'. Add a build-once-ship-image path: - scripts/build-offline-bundle.ps1: on a connected box, build shopdb-flask + pull mysql:8.0, docker save both into one gzipped tarball with a sha256. - docker-compose.airgap.yml: runs pre-loaded images (image:, never build:), drops the ./plugins bind mount (which would mask the image's baked-in plugins with an empty host dir and load zero plugins at an image-only site), and adds a one-shot migrate service (db upgrade + plugin upgrade-all + seed) that api waits on via service_completed_successfully, so 'up -d' brings a working site. - docs/DEPLOY-AIRGAP.md: full runbook (build, transfer+verify, load+run, admin, verify, upgrade, troubleshooting), incl the Zscaler in-build cert caveat. - .env.example: IMAGE_TAG for the air-gap compose to pin the loaded image tag.
91 lines
3.2 KiB
YAML
91 lines
3.2 KiB
YAML
# shopdb-flask AIR-GAPPED single-site stack.
|
|
#
|
|
# For a site with NO internet. Nothing is built or pulled here: the images are
|
|
# built on a connected box (scripts/build-offline-bundle.ps1), shipped as a
|
|
# tarball, and `docker load`ed at the site. This file only RUNS pre-loaded
|
|
# images. See docs/DEPLOY-AIRGAP.md for the full runbook.
|
|
#
|
|
# Differences from docker-compose.yml (the connected/build template):
|
|
# - api uses `image:` (a loaded image), never `build: .` (build needs the net).
|
|
# - NO ./plugins bind mount. The image already carries every plugin baked in;
|
|
# binding a host ./plugins (which does not exist at an image-only site) would
|
|
# mask the baked plugins with an empty dir and load ZERO plugins.
|
|
# - a one-shot `migrate` service runs db upgrade + plugin upgrade-all + seed
|
|
# BEFORE api starts, so `up -d` alone brings up a working site (no manual
|
|
# `docker compose exec ... flask db upgrade` to forget).
|
|
#
|
|
# Usage at the site:
|
|
# docker load < shopdb-stack-<version>.tar.gz
|
|
# cp .env.example .env # then edit: secrets, CORS_ORIGINS, IMAGE_TAG
|
|
# docker compose -f docker-compose.airgap.yml up -d
|
|
# docker compose -f docker-compose.airgap.yml exec api flask seed admin <user> <email> <password>
|
|
|
|
# Shared application environment, reused by the migrate one-shot and the api
|
|
# service so the two never drift. A YAML anchor, not a container.
|
|
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}
|
|
ZABBIX_URL: ${ZABBIX_URL:-}
|
|
ZABBIX_TOKEN: ${ZABBIX_TOKEN:-}
|
|
|
|
services:
|
|
db:
|
|
image: mysql:8.0
|
|
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", "localhost", "-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-flask:${IMAGE_TAG:-0.7.0}
|
|
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
|
|
|
|
api:
|
|
image: shopdb-flask:${IMAGE_TAG:-0.7.0}
|
|
restart: unless-stopped
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
migrate:
|
|
condition: service_completed_successfully
|
|
environment:
|
|
<<: *app-env
|
|
ports:
|
|
- "${API_PORT:-5001}:5001"
|
|
|
|
volumes:
|
|
db_data:
|