Merge remote-tracking branch 'origin/wilm' into wj
This commit is contained in:
12
Dockerfile
12
Dockerfile
@@ -17,6 +17,13 @@
|
|||||||
# docker build -t shopdb-flask .
|
# docker build -t shopdb-flask .
|
||||||
# Run (with .env):
|
# Run (with .env):
|
||||||
# docker run --env-file .env -p 5001:5001 shopdb-flask
|
# 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 ----
|
# ---- Stage 1: build the Vue frontend ----
|
||||||
FROM node:20-slim AS frontendbuild
|
FROM node:20-slim AS frontendbuild
|
||||||
@@ -142,7 +149,10 @@ USER shopdb
|
|||||||
|
|
||||||
EXPOSE 5001
|
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"]
|
ENTRYPOINT ["/app/scripts/docker-entrypoint.sh"]
|
||||||
|
|
||||||
# Probe the public health route, not the login endpoint. Probing POST
|
# Probe the public health route, not the login endpoint. Probing POST
|
||||||
|
|||||||
@@ -46,12 +46,67 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
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:
|
api:
|
||||||
build: .
|
build: .
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
migrate:
|
||||||
|
condition: service_completed_successfully
|
||||||
environment:
|
environment:
|
||||||
FLASK_ENV: production
|
FLASK_ENV: production
|
||||||
DATABASE_URL: mysql+pymysql://shopdb:${MYSQL_PASSWORD}@db:3306/shopdb_flask?charset=utf8mb4
|
DATABASE_URL: mysql+pymysql://shopdb:${MYSQL_PASSWORD}@db:3306/shopdb_flask?charset=utf8mb4
|
||||||
|
|||||||
@@ -1,12 +1,25 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Docker container entrypoint for ShopDB Flask.
|
# 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
|
set -e
|
||||||
|
|
||||||
echo "==> ShopDB Flask Container Starting"
|
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
|
if [ -n "$DATABASE_URL" ]; then
|
||||||
echo "==> Waiting for database..."
|
echo "==> Waiting for database..."
|
||||||
for i in {1..30}; do
|
for i in {1..30}; do
|
||||||
@@ -19,23 +32,5 @@ if [ -n "$DATABASE_URL" ]; then
|
|||||||
done
|
done
|
||||||
fi
|
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"
|
echo "==> Starting application"
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|||||||
Reference in New Issue
Block a user