Skip to content

feat: add docker_auth top-level block for host registry login#1

Merged
guess merged 1 commit into
mainfrom
feat/docker-auth
Apr 28, 2026
Merged

feat: add docker_auth top-level block for host registry login#1
guess merged 1 commit into
mainfrom
feat/docker-auth

Conversation

@guess

@guess guess commented Apr 28, 2026

Copy link
Copy Markdown
Owner

Summary

Declares which container registries the host docker daemon should be authenticated to. On `up`, runs `dokku registry:login ` for each entry. Idempotent — docker overwrites the credential on subsequent runs.

```yaml
docker_auth:
ghcr.io:
username: "${GHCR_USERNAME}"
password: "${GHCR_PAT}"
```

Why

We're moving CI builds for some apps off the prod dokku host (heavy Vite + openapi-ts builds were saturating the box and causing Cloudflare Tunnel 502s during deploy). The new flow builds on GHA, pushes to private GHCR, and uses `dokku git:from-image` on the host to pull and swap.

For `git:from-image` to work, the host docker daemon needs to be authenticated to GHCR. Today that's a one-time `dokku registry:login` SSHed in manually. This brings it into the declarative config-application pipeline alongside `plugins:`, `networks:`, etc., so:

  • Rebuilding a host doesn't require reading the runbook.
  • Credential rotation is a 1Password update + `up`, not a manual login.
  • The credentials stay out of YAML via `${VAR}` interpolation + `op run`.

Naming

Top-level key is `docker_auth:` rather than `registry_logins:` to avoid confusion with the existing per-app `apps..registry:` block. That one maps to `dokku registry:set` (per-app push behavior) — a different surface.

Implementation notes

  • New `src/modules/docker-auth.ts` (matches the `plugins.ts` shape exactly: a Phase 1 `ensureFoo` function called from `runUp`).
  • Schema additions in `src/core/schema.ts` — `username` / `password` both `.min(1)` for validation symmetry with the postgres backup auth schema.
  • Always re-runs the login on `up` rather than diffing — `docker login` is fast and idempotent, and inspecting `~/.docker/config.json` for the dokku user is fragile (base64 creds, user-vs-daemon paths). Same cost-benefit as just re-running.
  • Extended `maskSensitiveArgs` to redact the positional password in `registry:login` commands in `--dry-run` output. Existing regex only catches `KEY=VALUE` patterns; `registry:login` uses positional args. Three new test cases.

Decisions called out

  • Removal semantics: dropping a server from `docker_auth:` does NOT auto-logout. Host credential state persists. Same posture as `plugins:` (uninstalls aren't automatic either). Documented in the reference doc.
  • `down`: untouched. Auth is host-level, not app-level state.
  • Argv vs stdin: passwords pass through dokku CLI as positional args. `docker login --password-stdin` exists but `dokku registry:login` doesn't expose it today (would be an upstream feature request). Argv is masked in our dry-run output, and the SSH transport is encrypted, so the practical exposure is the host's process listing during the brief `run` window.

Test coverage

```
212 pass / 0 fail (was 200 before — 12 new assertions across 3 new test files)
```

  • `src/modules/docker-auth.test.ts` — happy path, empty config no-op.
  • `src/core/schema.test.ts` — parse success, missing-password rejection, empty-password rejection.
  • `src/core/mask.test.ts` — positional password masking, short-password masking, doesn't accidentally mask `registry:set`.
  • `src/tests/fixtures/full.yml` — adds a `docker_auth` block so the existing `runUp` integration tests exercise it.

Diff size

12 files, +183 / -2.

Follow-up (downstream)

After release, the qlustr repo's `dokku-compose.{production,staging}.yml` will be updated to include a `docker_auth:` block with `op://` references for the GHCR PAT. The manual `dokku registry:login` step in the qultr CI rollout PR's pre-merge checklist becomes redundant once that lands.

Declares which container registries the host docker daemon should be
authenticated to. On `up`, runs `dokku registry:login <server> <user>
<password>` for each entry. Idempotent — docker overwrites the entry
on subsequent runs.

Use case: deploying via `dokku git:from-image` against a private
registry (e.g. ghcr.io). Without prior login the daemon can't pull,
so the deploy fails. Today operators must SSH in and run the login
manually; this brings it into the same idempotent config-application
pipeline as plugins, networks, etc.

Distinct from the per-app `apps.<app>.registry:` block, which sets
`registry:set` properties (push destinations, image-build behavior).
Naming chosen to make that distinction obvious.

Includes a small extension to `maskSensitiveArgs` to redact the
positional password in `registry:login` commands during `--dry-run`
output (the existing regex only matches KEY=VALUE pairs).

Removal: dropping a server from `docker_auth:` does NOT auto-logout —
host credential state persists. Same posture as `plugins:`. Documented.

Sized at ~80 LOC including tests and reference docs.
@guess
guess merged commit c8a28bd into main Apr 28, 2026
1 check passed
@guess
guess deleted the feat/docker-auth branch April 28, 2026 05:10
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