Merge remote-tracking branch 'origin/wilm' into wj

This commit is contained in:
Proudlock
2026-09-11 12:30:51 -04:00
3 changed files with 81 additions and 21 deletions

View File

@@ -17,6 +17,13 @@
# docker build -t shopdb-flask .
# Run (with .env):
# docker run --env-file .env -p 5001:5001 shopdb-flask
#
# Note: this image does not migrate its own database on start. A plain
# `docker run` expects the schema to already be current; run the same sequence
# the compose `migrate` service does first:
# flask db upgrade && ./scripts/bootstrap-plugins.sh && flask plugin upgrade-all
# && flask seed permissions && flask seed settings && flask seed reference-data
# The supported path is `docker compose up`, which runs that for you.
# ---- Stage 1: build the Vue frontend ----
FROM node:20-slim AS frontendbuild
@@ -142,7 +149,10 @@ USER shopdb
EXPOSE 5001
# Entrypoint runs migrations and bootstraps plugins on first deployment
# Entrypoint waits for the database, then execs the CMD. Migrations, plugin
# bootstrap and seeds are NOT run here - they belong to the `migrate` service
# in the compose files so they happen exactly once per deployment rather than
# on every container restart.
ENTRYPOINT ["/app/scripts/docker-entrypoint.sh"]
# Probe the public health route, not the login endpoint. Probing POST

View File

@@ -46,12 +46,67 @@ services:
timeout: 5s
retries: 5
# Schema, plugin bootstrap and seed data all happen here, not in the api
# container. One place, one ordering, and a failure stops the rollout instead
# of being retried forever: restart "no" means a bad migration exits once and
# leaves the error as the last thing in the log. The api service below will
# not start at all until this exits 0, so `up -d api` still migrates first -
# compose honours service_completed_successfully even when a single service
# is named on the command line.
migrate:
build: .
restart: "no"
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}
# Required even though this service never serves a request: create_app()
# runs config validation before any CLI command dispatches, and the
# production config refuses to build without an explicit CORS allowlist.
CORS_ORIGINS: ${CORS_ORIGINS:?CORS_ORIGINS must be set}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
# Chained with && so the first failure stops the rest. Ordering is
# load-bearing: plugin tables cannot be created before the plugins are
# installed, and seedscopes only links permissions that already exist as
# rows, so the seeds run last.
#
# The seeds are all create-if-missing (see shopdb/cli/__init__.py), so
# running them on every start is a no-op once the site is current. They run
# here rather than only on first deploy because a patch that adds a
# permission or a default setting has to reach an existing site on the
# upgrade it already performs - that is the whole point of this service.
# No entrypoint override: the image entrypoint now only waits for the
# database and execs, which is exactly the preamble this service wants.
command:
- sh
- -c
- >
flask db upgrade &&
/app/scripts/bootstrap-plugins.sh &&
flask plugin upgrade-all &&
flask seed permissions &&
flask seed settings &&
flask seed reference-data
volumes:
- ./plugins:/app/plugins:ro
- ./scripts:/app/scripts:ro
# plugins.json is written here by bootstrap-plugins.sh, so this service
# needs the same instance volume the api uses. They never run at the same
# time, so the shared mount is not contended.
- instance_data:/app/instance
api:
build: .
restart: unless-stopped
depends_on:
db:
condition: service_healthy
migrate:
condition: service_completed_successfully
environment:
FLASK_ENV: production
DATABASE_URL: mysql+pymysql://shopdb:${MYSQL_PASSWORD}@db:3306/shopdb_flask?charset=utf8mb4

View File

@@ -1,12 +1,25 @@
#!/bin/bash
# Docker container entrypoint for ShopDB Flask.
# Runs migrations, bootstraps plugins on first deployment, then starts the app.
# Waits for the database to answer, then starts whatever it was given.
#
# This deliberately does NOT run migrations, plugin bootstrap or seeds. That
# sequence lives in the `migrate` service in docker-compose.yml (and its
# equivalents in the pull/airgap compose files) so there is exactly one place
# it happens. Running it here as well would mean every api restart re-attempted
# schema work, and a failing migration under `restart: unless-stopped` would
# crash-loop and bury the original error under retry noise.
#
# `up -d api` is still safe on its own: compose honours the
# service_completed_successfully dependency even when a single service is
# named, so the migrate service runs to completion first.
set -e
echo "==> ShopDB Flask Container Starting"
# Wait for database to be ready (optional, useful with docker-compose)
# Wait for database to be ready. Kept here because it is about this container's
# own readiness to serve, not about schema state. `flask db current` is a cheap
# round-trip that proves the connection works.
if [ -n "$DATABASE_URL" ]; then
echo "==> Waiting for database..."
for i in {1..30}; do
@@ -19,23 +32,5 @@ if [ -n "$DATABASE_URL" ]; then
done
fi
# Run migrations
echo "==> Running database migrations"
flask db upgrade || {
echo "ERROR: Database migrations failed"
exit 1
}
# Bootstrap plugins on first deployment
echo "==> Checking plugin installation"
/app/scripts/bootstrap-plugins.sh
# Run plugin migrations after bootstrap (creates tables for enabled plugins)
# Safe to run even if no plugins are enabled - it will just do nothing
echo "==> Running plugin migrations"
flask plugin upgrade-all || {
echo "WARNING: Plugin migrations failed - some features may not work"
}
echo "==> Starting application"
exec "$@"