Run the database version the rest of the product already recommends
Some checks failed
CI / backend (push) Failing after 7m18s
CI / naming (push) Failing after 7m14s
CI / frontend (push) Failing after 7m13s
CI / migrations-mysql (push) Failing after 7m10s

INSTALL-WINDOWS-IIS.md has said MySQL 8.4 LTS is standard for new installs since
8.0 reached end of life in April 2026, while both compose files and the offline
bundler still pinned 8.0. A site reading the Windows runbook and a site reading
the Docker one were being told to run different servers, and the migration page
written this week sent people onto the dead one.

Verified against a real server rather than by editing a tag: 8.4.11, core chain
plus five plugin chains applied clean, 66 tables at a single utf8mb4_unicode_ci
collation, six alembic version tables. The image's PyMySQL authenticates against
8.4's caching_sha2_password, which is what requirements.in already pins
cryptography for.

Existing servers need one thing done FIRST: 8.4 removes mysql_native_password,
so an account created on 5.6 or 5.7 must be moved to caching_sha2_password
before the upgrade or it cannot authenticate afterwards. In-place also has no
downgrade path, and 5.7 cannot reach 8.4 in one hop. For databases this size a
dump into a fresh 8.4 server is the better trade: same outage, and the old
server stays as the rollback.

Air-gapped sites need a fresh offline bundle, because the tarball carries the
MySQL image alongside the app image.

Also here, found by having it bite during that verification: the db healthcheck
pinged over the unix socket, and the entrypoint's init pass answers on the
socket while running the server with --skip-networking. The probe therefore
reported healthy DURING init, which is what `depends_on: service_healthy` gates
api and migrate on. A ping passed at 8 seconds and the next query failed because
the server was mid-restart. Probing 127.0.0.1 keeps it red until the real server
is listening.
This commit is contained in:
cproudlock
2026-08-19 19:57:40 -04:00
parent a7f5d2d0bf
commit c6c806667e
7 changed files with 41 additions and 9 deletions

View File

@@ -39,6 +39,28 @@ ADR-007 and ADR-002.
its own hostname, the gate runs BEFORE the manifest is read (the manifest is its own hostname, the gate runs BEFORE the manifest is read (the manifest is
on that share too), and it fails closed on an unparseable date. on that share too), and it fails closed on an unparseable date.
### Changed
- **MySQL 8.4 LTS** in both compose files, and as the default in
`build-offline-bundle.ps1`. 8.0 reached end of life in April 2026, which
`INSTALL-WINDOWS-IIS.md` already said while the stack still pinned it; the two
halves of the product disagreed about which server a site should run.
Verified against a real 8.4.11 server rather than by changing a tag: the core
chain and five plugin chains applied clean, 66 tables at a single
`utf8mb4_unicode_ci` collation, and the image's PyMySQL authenticates against
8.4's `caching_sha2_password` (which is why `cryptography` is pinned).
Sites upgrading an EXISTING server should note that 8.4 removes
`mysql_native_password`: an account created on 5.6 or 5.7 must be moved to
`caching_sha2_password` before the upgrade or it cannot log in afterwards.
Air-gapped sites need a fresh offline bundle, since the tarball carries the
MySQL image.
- **The database healthcheck probes over TCP** rather than the unix socket. The
entrypoint's init pass answers on the socket while running the server with
`--skip-networking`, so a socket ping reported healthy DURING init and
`depends_on: service_healthy` released `api` and `migrate` against a server
that was about to restart. Found by having it happen: a probe passed at 8s and
the next query failed because the server was mid-restart.
### Fixed ### Fixed
- **The image did not build.** `npm run build` fires a `prebuild` hook that runs - **The image did not build.** `npm run build` fires a `prebuild` hook that runs

View File

@@ -35,7 +35,7 @@ x-app-env: &app-env
services: services:
db: db:
image: mysql:8.0 image: mysql:8.4
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
restart: unless-stopped restart: unless-stopped
environment: environment:
@@ -48,7 +48,12 @@ services:
ports: ports:
- "127.0.0.1:${MYSQL_PORT:-3306}:3306" - "127.0.0.1:${MYSQL_PORT:-3306}:3306"
healthcheck: healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"] # 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 interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5

View File

@@ -17,7 +17,7 @@
services: services:
db: db:
image: mysql:8.0 image: mysql:8.4
# utf8mb4 server-wide so the auto-created MYSQL_DATABASE is utf8mb4, not the # 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. # image default. Keeps every site's schema on the same charset/collation.
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
@@ -36,7 +36,12 @@ services:
ports: ports:
- "127.0.0.1:${MYSQL_PORT:-3306}:3306" - "127.0.0.1:${MYSQL_PORT:-3306}:3306"
healthcheck: healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"] # 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 interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5

View File

@@ -27,7 +27,7 @@ pwsh scripts/build-offline-bundle.ps1 -Version 0.7.0
``` ```
This builds `shopdb-flask:0.7.0` (frontend + all Python deps baked in), pulls This builds `shopdb-flask:0.7.0` (frontend + all Python deps baked in), pulls
`mysql:8.0`, and writes: `mysql:8.4`, and writes:
- `shopdb-stack-0.7.0.tar.gz` - both images in one archive - `shopdb-stack-0.7.0.tar.gz` - both images in one archive
- `shopdb-stack-0.7.0.tar.gz.sha256` - checksum to verify after transfer - `shopdb-stack-0.7.0.tar.gz.sha256` - checksum to verify after transfer

View File

@@ -25,7 +25,7 @@ sane, then use manual for day-to-day work.
| --- | --- | --- | | --- | --- | --- |
| Python | 3.14 (64-bit) - matches CI, the container image and the Windows installer wheelhouse | `python --version` | | Python | 3.14 (64-bit) - matches CI, the container image and the Windows installer wheelhouse | `python --version` |
| Node.js | 18+ | `node --version` | | Node.js | 18+ | `node --version` |
| MySQL | 8.0 (or Docker, below) | `mysql --version` | | MySQL | 8.4 LTS (or Docker, below) | `mysql --version` |
| Git | any recent | `git --version` | | Git | any recent | `git --version` |
On Windows, install all of them with winget (accept each license, then On Windows, install all of them with winget (accept each license, then

View File

@@ -57,7 +57,7 @@ image with the matching `VITE_BASE_PATH`.
### 3. MySQL version and character set ### 3. MySQL version and character set
The Windows runbook supports 5.6, 5.7 and 8.4. `docker-compose.yml` runs The Windows runbook supports 5.6, 5.7 and 8.4. `docker-compose.yml` runs
`mysql:8.0` and forces `utf8mb4` / `utf8mb4_unicode_ci` server-wide so every `mysql:8.4` and forces `utf8mb4` / `utf8mb4_unicode_ci` server-wide so every
site shares one collation. site shares one collation.
A dump from an older server can carry `latin1` or 3-byte `utf8` table A dump from an older server can carry `latin1` or 3-byte `utf8` table

View File

@@ -21,7 +21,7 @@
sets in its .env so docker-compose.airgap.yml runs the matching image. sets in its .env so docker-compose.airgap.yml runs the matching image.
.PARAMETER MysqlImage .PARAMETER MysqlImage
MySQL image the stack runs (default mysql:8.0). Must match db.image in MySQL image the stack runs (default mysql:8.4). Must match db.image in
docker-compose.airgap.yml. docker-compose.airgap.yml.
.PARAMETER OutDir .PARAMETER OutDir
@@ -36,7 +36,7 @@
[CmdletBinding()] [CmdletBinding()]
param( param(
[string]$Version = '0.7.0', [string]$Version = '0.7.0',
[string]$MysqlImage = 'mysql:8.0', [string]$MysqlImage = 'mysql:8.4',
[string]$OutDir = '.', [string]$OutDir = '.',
[switch]$SkipBuild [switch]$SkipBuild
) )