Skip to content

feat: rename the binary, user, data directory and image label to dash-proxy - #122

Merged
mhenrixon merged 1 commit into
mainfrom
issue-124-container-identity
Aug 26, 2026
Merged

feat: rename the binary, user, data directory and image label to dash-proxy#122
mhenrixon merged 1 commit into
mainfrom
issue-124-container-identity

Conversation

@mhenrixon

Copy link
Copy Markdown
Collaborator

Summary

Part 1 of stage 3c, the server-artifact rename (zoolutions/dash#124). This must be released before the gem side merges, and the gem then pins MINIMUM_VERSION to the resulting tag — so this is effectively one-way once that happens.

Moved From To
cmd package (and so make output) cmd/kamal-proxy cmd/dash-proxy
container user + home kamal-proxy dash-proxy
data directory $HOME/.config/kamal-proxy $HOME/.config/dash-proxy
unix socket kamal-proxy.sock dash-proxy.sock
image label org.opencontainers.image.title=kamal-proxy …=dash-proxy

Deliberately not moved, per the issue: the Go module path, the RPC service name, the Prometheus namespace, the /.kamal-proxy/ HTTP routes, and the KAMAL_PROXY_* token env names. Each has its own follow-up.

The routing table is the risky part

The gem migrates the old volume by copying it verbatim, so a renamed proxy finds a data directory holding kamal-proxy.state and no dash-proxy.state. Left alone it would boot with an empty routing table and every service would need re-registering by a deploy — an outage, not a migration.

Config.AdoptLegacyState() runs once at boot, before the router is constructed, and seeds the current file from the legacy one. Two properties matter:

  • It copies rather than renames, so an operator who rolls back to a pre-rename image still finds the table where that image looks for it.
  • StatePath is unconditional, so an upgraded host converges onto the new name. My first attempt had StatePath return whichever file existed; a container test showed that makes the proxy read and write the legacy name forever, which would leave stage 3d unable to retire the fallback.

Everything else in the data directory (certs/, acme.state, dynamic-domains.state, dynamic-redirects.state) already has a name that doesn't mention the proxy, so the copy carries it untouched. That narrows the compatibility surface to one file.

Test plan

published v1.0.0.7 : uid=1001(kamal-proxy) gid=1001(kamal-proxy)
this branch        : uid=1001(dash-proxy)  gid=1001(dash-proxy)

Same numeric uid; only the name moves. So the gem's cp -a needs no chown.

  • Adoption verified end to end in a container, not just in unit tests. Given a data directory holding only kamal-proxy.state:
INFO Adopted the pre-rename routing table from=/data/kamal-proxy.state to=/data/dash-proxy.state
INFO Restored saved state path=/data/dash-proxy.state
INFO Server started http=80 https=443
--- files ---
dash-proxy.state  dash-proxy.state.bak  kamal-proxy.state

It converges onto the new file, keeps the legacy one for rollback, and a second boot logs no adoption — idempotent.

  • Image smoke test: binary at /usr/local/bin/dash-proxy, /home/dash-proxy/.config/dash-proxy owned by the runtime user, dash-proxy --help works.

Deviations & judgment calls

Deviations

  • The state fallback became an explicit one-time adoption rather than a read-fallback in StatePath, for the convergence reason above. The issue's wording ("reading the legacy name as a fallback ... and always writing the new name") describes the intent; a read-fallback alone cannot deliver the second half of it.
  • Adoption copies rather than renames, to keep rollback working.

Judgment calls

  • Updated CLAUDE.md's "Never Do" rules 1 and 4. They forbid renaming the binary, socket and image label — invariants this change deliberately overturns. Left as-is they would tell the next reader to revert this. Rewritten to keep what's still true (module path and RPC name stay) and name the stage that moved the rest.
  • Renamed the binary references throughout README.md (86 occurrences). It is full of kamal-proxy deploy … invocations naming a binary this change removes. Done with a lookbehind-guarded substitution that skips /.kamal-proxy/, KAMAL_PROXY_* and basecamp/kamal-proxy; the count of those preserved patterns is identical before and after.
  • Fixed all three --data-dir help strings, not just the one the issue cites — changing the default made it wrong in run.go and export.go too.

Discovery

  • The Makefile needed no change to emit bin/dash-proxy: go build -o bin/ ./cmd/... names the output after the package directory, so the git mv did it. Only the docker target's tag needed editing.

Release and sequencing

After merge: script/release-dash v1.1.0.0, then confirm the tag is pullable, multi-arch and public from a logged-out shell before any gem change merges.

Not done: the manual upgrade rehearsal from the issue — deploying a real host on the pre-rename gem, upgrading, and confirming the ACME certificate is not re-issued. It needs a scratch host and a real certificate. The container-level evidence above covers the state adoption and the uid, but not the full volume/network/container migration, which is gem-side anyway.

…-proxy

Stage 3c part 1 of the server-artifact rename (zoolutions/dash#124). The
gem follows in a separate change, behind a MINIMUM_VERSION floor, once
this is released — so this must ship first and cannot be reverted after
the gem moves.

Renamed: cmd/kamal-proxy -> cmd/dash-proxy (which is what makes `make`
emit bin/dash-proxy), the container user and its home, the data directory
$HOME/.config/dash-proxy, the unix socket, and the
org.opencontainers.image.title label the gem prunes by.

The routing table needs care, because the gem migrates the old volume by
copying it verbatim: a renamed proxy therefore finds a data directory
holding kamal-proxy.state and no dash-proxy.state. Config.AdoptLegacyState
seeds the current file from the legacy one at boot, before the router
reads it. Without that the proxy boots with an empty table and every
service has to be re-registered by a deploy — an outage, not a migration.

It copies rather than renames, so a rollback to a pre-rename image still
finds the table where it looks for it, and StatePath is unconditional so
an upgraded host converges onto the new name instead of writing the
legacy one forever. Every other file in the data directory (certs/,
acme.state, dynamic-domains.state) already has a name that does not
mention the proxy, so the copy carries them untouched.

DASH_PROXY_SOCKET is accepted alongside KAMAL_PROXY_SOCKET, which the gem
still sets on containers booted before the rename.

Verified against the published v1.0.0.7 image that the runtime uid is
unchanged at 1001 — only its name moves. That is what lets the gem's
volume copy use `cp -a` with no chown, and it was an assumption worth
checking rather than trusting.

The Go module path, the RPC service name, the Prometheus namespace, the
/.kamal-proxy/ HTTP routes and the KAMAL_PROXY_* token env names are
deliberately unchanged; they have their own follow-up.

Refs zoolutions/dash#124
@mhenrixon mhenrixon self-assigned this Aug 26, 2026
@mhenrixon
mhenrixon merged commit 3ab1120 into main Aug 26, 2026
3 checks passed
@mhenrixon
mhenrixon deleted the issue-124-container-identity branch August 26, 2026 16:33
mhenrixon added a commit to zoolutions/dash that referenced this pull request Aug 26, 2026
…ss (#130)

* feat(proxy): rename container identity to dash and migrate hosts across

Stage 3c part 2 of the server-artifact rename (#124), gem side. The proxy
image moved first in zoolutions/dash-proxy#122, released as v1.1.0.0.

Renamed: the proxy container and its port holder, the docker network every
app, accessory and proxy joins, the config volumes, the image title the
prune filters key on, and the in-image paths the gem mounts and execs.

Three migrations run once per proxy host, before the normal boot, wired
through a new Dash::Cli::Proxy::LegacyRename:

  - The network is bridged rather than renamed, because docker cannot
    rename one. App containers would be replaced by the next deploy
    anyway; accessories are not, and without the bridge a renamed proxy
    cannot reach db or redis.

  - The config volume is copied before anything starts. It holds the
    routing table and the ACME account and certificate cache, so losing it
    means re-issuing every certificate and spending Let's Encrypt rate
    limits to get back to where we were.

  - The legacy container is stopped and removed so the renamed one can
    claim ports 80/443. No port-holder handoff spans two container names,
    so this stage costs a brief outage per host. Deliberate, and settled
    in the issue.

Each is idempotent and guarded on its destination not existing, so a
second deploy is a no-op. The volume copy's guard is negated and leads its
chain: shell && and || share precedence and associate left, so the obvious
`exists || legacy && create && copy` parses as `((exists || legacy) &&
create) && copy` and would re-copy the legacy volume over live state on
every deploy. Verified against a shell rather than reasoned about.

Prune filters now match the pre-rename image title as well as the current
one, or `dash proxy remove` would silently leave the old container and
image on a host that has not yet migrated. Docker ANDs multiple
`--filter label=` values, so that is two chained commands.

Nothing removes the legacy network or volume: an operator who wants them
gone removes them by hand, and stage 3d deletes this whole path.

MINIMUM_VERSION is NOT bumped here - that lands with the docs, once
v1.1.0.0 is pullable.

Refs #124

* feat(proxy): pin dash-proxy v1.1.0.0 and document the container rename

MINIMUM_VERSION moves to v1.1.0.0, the first image with the renamed
binary, user, data directory and image title. Verified before pinning:
the published tag carries /usr/local/bin/dash-proxy, runs as uid 1001,
is multi-arch, and its org.opencontainers.image.title is dash-proxy —
which the prune filters key on.

bin/sync-proxy-flags invoked `kamal-proxy` to read Cobra's help, so it
broke the moment the binary moved. Fixed and the manifest regenerated;
the flag surface is unchanged, this release being a rename.

The docs claimed three things this stage falsifies: that the container
keeps the kamal-proxy name, that dash manages the one kamal booted, and
that an upgrade leaves it untouched. Replaced with what actually happens,
including the part operators need to plan for — replacing the container
costs a short outage on that host, because the old one has to release
ports 80/443 before the new one can bind them.

Refs #124
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