Skip to content

fix(adapters): detect untagged database images in backup autodetection - #413

Open
shuvamk wants to merge 1 commit into
oblien:mainfrom
shuvamk:fix/backup-producer-untagged-images
Open

fix(adapters): detect untagged database images in backup autodetection#413
shuvamk wants to merge 1 commit into
oblien:mainfrom
shuvamk:fix/backup-producer-untagged-images

Conversation

@shuvamk

@shuvamk shuvamk commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Backup auto-detection required a tag on the service image, so image: postgres took a crash-consistent volume tar instead of pg_dump. All four database producers now accept the untagged spelling.

Motivation

image: postgres with no tag is valid compose and is stored verbatim on the service row, but every producer gated on a colon after the image name. Measured through autoDetectProducer with the real registry:

service image main this branch
postgres:16 pg_dump pg_dump
postgres volume pg_dump

Same mechanism, same result for mysql, mariadb, mongo, redis, postgis/postgis and percona/percona-server(-mongodb): tagged detects, untagged falls through.

A tar of a live PGDATA is crash-consistent, not app-consistent — not the backup the policy asked for, and nothing reports the downgrade. The policy editor already renders "Detected PostgreSQL — Auto backs it up with pg_dump" for image: postgres while the run tars the volume, and it offers only auto / volume / custom_command, so there is no way to ask for the dump.

Related issue

None.

Changes

  • producers/{pg-dump,mysql-dump,mongo,redis}.ts — each image regex gains $ as an alternative terminator to :. Each Detection: header line is updated to the regex it describes.
  • producers/detect.test.ts — new.

/ is deliberately not added to the postgres, mysql and mongo patterns: that would admit the whole <db>/<repo> namespace, and mysql/mysql-router is a proxy with no data and no mysqldump binary whose documented compose env carries MYSQL_PASSWORD — it would satisfy the credential guard, fail the dump, and turn a green zero-artifact run red. Redis keeps the / it already had, which redis/redis-stack relies on.

So the image match widens by exactly the untagged spelling. The env guards are untouched (untagged postgres with no POSTGRES_DB/PGDATABASE still falls back to volume); a digest reference postgres@sha256:… still resolves to volume; and postgrest, postgresql, postgres-exporter, myapp/postgres, redis-sentinel, plus every <db>/<repo> form, resolve to volume before and after.

Verification

Five tests in detect.test.ts. The untagged case fails with only the source files reverted:

$ npx vitest run src/backup/producers/detect.test.ts   # fix reverted, tests kept
 ❯ src/backup/producers/detect.test.ts (5 tests | 1 failed)
     × routes an untagged database image to its dump producer
AssertionError: expected { postgres: 'volume', …(7) } to deeply equal { postgres: 'pg_dump', …(7) }
-   "postgres": "pg_dump",   +   "postgres": "volume",

$ npx vitest run src/backup/producers/detect.test.ts   # fix restored
      Tests  5 passed (5)

The <db>/<repo> test passes on main too, on purpose — it pins the boundary this change could have opened, and I checked it by inversion: widening the three terminators to [:/] makes that test, and only that test, fail.

Full suite green, tsc --noEmit clean in packages/adapters and apps/api.

Checklist

  • One change per PR — one bug, or one agreed feature, with nothing unrelated bundled in
  • The diff is scoped — no reformatting or lint fixes on lines I wasn't otherwise changing
  • A test fails without this change and passes with it (or I explained above why there isn't one)
  • bun run test and bun run --cwd packages/adapters lint pass locally. I did not run the repo-wide bun formatprettier --check already fails on three of these files on origin/main and I did not want to churn that drift; I checked the files individually instead, and every line this PR adds is prettier-clean.
  • I understand every line of this diff and can explain it in review

`image: postgres` with no tag is valid compose and is stored verbatim on the
service row, but all four database producers required a colon after the image
name, so `auto` fell through to the crash-consistent volume tar instead of
running a logical dump. Measured through `autoDetectProducer` with the real
registry loaded:

  postgres:16 -> pg_dump      postgres            -> volume
  mysql:8.0   -> mysql_dump   mysql, mariadb      -> volume
  mongo:7     -> mongo_dump   mongo               -> volume
  redis:7     -> redis_rdb    redis               -> volume
  postgis/postgis and percona/percona-server(-mongodb) untagged -> volume

The dashboard's policy editor renders "Detected PostgreSQL - Auto backs it up
with pg_dump" for `image: postgres` while the run tars a live PGDATA, and it
offers only auto / volume / custom_command, so there is no way to ask for the
dump.

Each regex gains `$` as an alternative terminator to `:` and nothing else. The
end anchor is the whole change: `/` is deliberately NOT added to the postgres,
mysql and mongo patterns, because that would pull in the entire `<db>/<repo>`
namespace - `mysql/mysql-router` is a proxy with no data and no `mysqldump`
binary, and its documented compose env carries MYSQL_PASSWORD, so it would
satisfy the credential guard, fail the dump, and turn a green zero-artifact run
red. Redis keeps the `/` it already had, which `redis/redis-stack` relies on.

The image match widens by exactly the untagged spelling; nothing else does. The
env guards are untouched, so an untagged `postgres` with no
`POSTGRES_DB`/`PGDATABASE` still falls back to volume. A digest reference
(`postgres@sha256:...`) resolves to volume as before, an image that merely
starts with a database name is unaffected (`postgrest`, `postgresql`,
`postgres-exporter`, `myapp/postgres`, `mysqld-exporter`, `mongo-express`,
`redis-sentinel`, `redisinsight`), and `<db>/<repo>` forms
(`mysql/mysql-server:8.0`, `mariadb/maxscale:23.08`, `postgres/whatever`) keep
resolving to volume - pinned by a test that fails if the terminator is widened
to `[:/]`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant