The pre-migrate backup ran INSIDE the API container, which made it depend on
that image's toolchain — and deadlocked: the running image shipped a MySQL
client that could not authenticate, so the backup failed, which blocked the
very deploy that would have replaced the broken image. A backup must not depend
on the thing being deployed.
The dump now runs in a throwaway container built from mysql:8.4 with the API's
backup volume mounted. The volume name is discovered from the API container's
mounts, so the file still lands where the Operaciones restore screen looks. As
a container rather than an exec, its logs can simply be read — no more failures
reported as a bare exit code. The image is pulled if the host lacks it, since a
scope:app deploy never touches the db stack.
Three further defects found while verifying, none of which would have surfaced
without dumping against the real database:
- The dump now runs as root. mysqldump --single-transaction issues FLUSH
TABLES, needing the global RELOAD privilege; the MySQL image grants the
application user only ALL ON `<db>`.*, and --skip-lock-tables does not avoid
it. Elevating the app's own runtime user would have been the worse trade.
- --set-gtid-purged=OFF. galactus is the replication SOURCE with GTID on, so a
default dump embeds SET @@GLOBAL.GTID_PURGED and is unrestorable onto the
server it came from. Verified: 0 GTID_PURGED lines in the output.
- Verification was too weak to be worth having. `test -s` plus `gzip -t` passes
on a 372-byte gzip containing no tables, which is exactly what a dump that
died on its first statement produces. It now asserts a CREATE TABLE count and
logs it. A failed attempt also deletes its own output, so a truncated file
never appears in the restore list.
Verified against live prod, both paths: success writes a 31-table dump the API
container can see; a wrong password fails with mysqldump's own error quoted and
leaves the volume empty.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>