fix(deploy): push through a TLS-terminating proxy, not raw Gitea HTTP #8

Merged
BBergle merged 2 commits from fix/deploy-registry-tls into fix/deploy-unraid-template-port 2026-09-21 20:59:15 -04:00
Owner

Fixes the registry push failure from PR #6's first real run (#16): docker/login-action against 192.168.0.3:3000 hit server gave HTTP response to HTTPS client — Docker refuses any non-localhost registry over plain HTTP by default.

Stacked on #7 (same file, unraid-template.xml, different fields — rebased on it to avoid a conflict).

What was considered and rejected (full record in docs/DECISIONS.md D17)

  • insecure-registries in daemon.json — the obvious fix. Rejected after reading this Unraid host's actual /etc/rc.d/rc.docker: applying it needs a full dockerd restart, and with Live Restore disabled here, that stops all ~40 other containers on the box first (Plex, Home Assistant, Vaultwarden, everything) as part of the restart sequence.
  • A real Let's Encrypt cert on a public bbergle.com subdomain — this host's other subdomains are Cloudflare-proxied (confirmed via dig), which would terminate TLS at Cloudflare's edge and never reach anything we control.

What was built instead

  • A 10-year self-signed cert for registry.bbergle.com, no real domain dependency.
  • An NPMplus proxy host (registry.bbergle.com192.168.0.3:3000 over plain HTTP internally), on NPMplus's real HTTPS port (9537 on this host — found via docker port NPMplus, not assumed to be 443, which turned out to be a different nginx process on the box).
  • /etc/hosts on the Unraid host mapping registry.bbergle.com → itself — the only client that ever needs to resolve this is that host's own dockerd (DooD), so this sidesteps DNS/Cloudflare/router NAT entirely.
  • The cert's CA dropped into /etc/docker/certs.d/registry.bbergle.com:9537/ca.crt — confirmed via Docker's own docs this is read per-connection, no daemon restart.
  • docker/setup-buildx-action@v3 pinned to driver: docker instead of its default docker-container driver, which runs an isolated builder that doesn't see /etc/docker/certs.d and would have silently defeated all of the above (confirmed via a docker/buildx GitHub issue before relying on it).

How this was verified

Not just built and hoped — actually exercised, and it caught a real bug:

  • First manual workflow_dispatch (run #18) failed with x509: certificate signed by unknown authority — the certs.d directory was missing the :9537 port suffix (created before we knew NPMplus's real port). Fixed, then confirmed with docker login directly on the host that the response changed from a TLS trust error to a proper unauthorized before re-running CI.
  • Second dispatch (run #19): succeeded. Confirmed via Gitea's packages API that the image actually landed, and separately pulled the manifest back through registry.bbergle.com:9537 from the Unraid host to confirm the full round trip, not just that CI reported green.

What's not done

The /etc/hosts entry and certs.d file are not persisted across a reboot — both live under /, which Unraid rebuilds from /boot on every boot, and wiring this into /boot/config/go was left as a deliberate follow-up for you to trigger when ready, given earlier caution about editing anything under /boot autonomously.

🤖 Generated with Claude Code

Fixes the registry push failure from PR #6's first real run (#16): `docker/login-action` against `192.168.0.3:3000` hit `server gave HTTP response to HTTPS client` — Docker refuses any non-localhost registry over plain HTTP by default. Stacked on #7 (same file, unraid-template.xml, different fields — rebased on it to avoid a conflict). ## What was considered and rejected (full record in docs/DECISIONS.md D17) - **`insecure-registries` in `daemon.json`** — the obvious fix. Rejected after reading this Unraid host's actual `/etc/rc.d/rc.docker`: applying it needs a full `dockerd` restart, and with `Live Restore` disabled here, that stops all ~40 other containers on the box first (Plex, Home Assistant, Vaultwarden, everything) as part of the restart sequence. - **A real Let's Encrypt cert on a public `bbergle.com` subdomain** — this host's other subdomains are Cloudflare-proxied (confirmed via `dig`), which would terminate TLS at Cloudflare's edge and never reach anything we control. ## What was built instead - A 10-year self-signed cert for `registry.bbergle.com`, no real domain dependency. - An NPMplus proxy host (`registry.bbergle.com` → `192.168.0.3:3000` over plain HTTP internally), on NPMplus's real HTTPS port (`9537` on this host — found via `docker port NPMplus`, not assumed to be 443, which turned out to be a *different* nginx process on the box). - `/etc/hosts` on the Unraid host mapping `registry.bbergle.com` → itself — the only client that ever needs to resolve this is that host's own `dockerd` (DooD), so this sidesteps DNS/Cloudflare/router NAT entirely. - The cert's CA dropped into `/etc/docker/certs.d/registry.bbergle.com:9537/ca.crt` — confirmed via Docker's own docs this is read per-connection, no daemon restart. - `docker/setup-buildx-action@v3` pinned to `driver: docker` instead of its default `docker-container` driver, which runs an isolated builder that doesn't see `/etc/docker/certs.d` and would have silently defeated all of the above (confirmed via a docker/buildx GitHub issue before relying on it). ## How this was verified Not just built and hoped — actually exercised, and it caught a real bug: - First manual `workflow_dispatch` (run #18) failed with `x509: certificate signed by unknown authority` — the `certs.d` directory was missing the `:9537` port suffix (created before we knew NPMplus's real port). Fixed, then confirmed with `docker login` directly on the host that the response changed from a TLS trust error to a proper `unauthorized` before re-running CI. - Second dispatch (run #19): **succeeded**. Confirmed via Gitea's packages API that the image actually landed, and separately pulled the manifest back through `registry.bbergle.com:9537` from the Unraid host to confirm the full round trip, not just that CI reported green. ## What's not done The `/etc/hosts` entry and `certs.d` file are **not** persisted across a reboot — both live under `/`, which Unraid rebuilds from `/boot` on every boot, and wiring this into `/boot/config/go` was left as a deliberate follow-up for you to trigger when ready, given earlier caution about editing anything under `/boot` autonomously. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
BBergle added 1 commit 2026-09-21 20:56:26 -04:00
fix(deploy): push through a TLS-terminating proxy, not raw Gitea HTTP
CI / Repo hygiene (pull_request) Successful in 2s
CI / Web (lint, typecheck, build) (pull_request) Successful in 16s
CI / Migrations reversible (pull_request) Successful in 6s
CI / API (lint, types, tests) (pull_request) Successful in 54s
a114a7d3d8
release.yml's first real run failed: docker/login-action against
192.168.0.3:3000 hit "server gave HTTP response to HTTPS client" — Docker
refuses any non-localhost registry over plain HTTP by default, so this was
never actually a workflow bug.

Rejected insecure-registries in daemon.json after reading this Unraid host's
own rc.docker script: applying it needs a full dockerd restart, and with
Live Restore disabled here, that stops every one of the ~40 other containers
on the box first. Also rejected a real Let's Encrypt cert on a public
bbergle.com subdomain — this host's other subdomains are Cloudflare-proxied,
which would terminate TLS at Cloudflare's edge and never reach our own cert
at all.

Chosen instead, scoped to touch nothing already working: a self-signed cert
for registry.bbergle.com behind a new NPMplus proxy host (found its real
HTTPS port, 9537, by reading `docker port NPMplus` rather than assuming 443,
which is a different nginx process on this box entirely); an /etc/hosts
entry on the Unraid host so only that host needs to resolve the name (no
DNS record, no router/NAT dependency); and its CA dropped into
/etc/docker/certs.d, which Docker's own docs confirm is read per-connection
with no daemon restart required. Also pins buildx to driver: docker instead
of setup-buildx-action's default docker-container driver, which runs an
isolated builder that doesn't see /etc/docker/certs.d and would have quietly
defeated all of the above.

Full record, including what was rejected and why, in docs/DECISIONS.md D17.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R2ZKeWkZV7ehf7fivrAkkG
BBergle added 1 commit 2026-09-21 20:57:54 -04:00
feat(deploy): build+push release image on every merge to main
CI / Repo hygiene (pull_request) Successful in 3s
CI / Web (lint, typecheck, build) (pull_request) Successful in 22s
CI / Migrations reversible (pull_request) Successful in 10s
CI / API (lint, types, tests) (pull_request) Successful in 1m4s
45719f284c
Was tag-push-or-manual-dispatch only. Adds a push:main trigger so main stays
continuously deployable without needing a version tag for every change.

Also fixes a real bug this surfaced while testing the D17 registry-TLS fix:
the old tag logic unconditionally retagged :latest on every run, including
manual test dispatches off a feature branch — one such dispatch, done while
verifying the previous commit, silently overwrote :latest with a
feature-branch build. Tag resolution now only moves :latest on an actual
main push or a version tag; a manual dispatch gets its own
manual-<timestamp>-<sha> tag and leaves :latest alone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R2ZKeWkZV7ehf7fivrAkkG
BBergle merged commit bd74c2c7c1 into fix/deploy-unraid-template-port 2026-09-21 20:59:15 -04:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: BBergle/bike-app#8