You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Stage 3: rename server artifacts with a rolling-upgrade bridge
Epic. Follow-up to #115 (final stage), depends on stage 2 (#117, landed in a2273854).
Split into three executable sub-stages after a full inventory of the artifact surface.
Problem / Goal
Everything an operator or a server can see still says kamal: the project's .kamal/
directory, the KAMAL_* env vars handed to hooks and injected into app containers, the .kamal/ run directory on every host, the kamal-proxy container, the kamal docker
network, the kamal-proxy-config volume, and the org.* labels the prune filters key on.
Stage 3 renames all of it to dash.
Done looks like: a fresh dash init produces .dash/, a deployed host has .dash/ and a dash-proxy container on a dash network, hooks and app containers see DASH_*, and an
operator upgrading from 3.2.0 gets there without a manual server intervention.
in-image binary kamal-proxy, user + /home/kamal-proxy/.config/kamal-proxy, /home/kamal-proxy/.apps-config
dash-proxy image
none
medium
3c
11
KAMAL_* emission removed for good
everywhere
high
breaking
3d (5.0)
Not in scope as a rename: KAMAL_REGISTRY_PASSWORD and friends in lib/dash/cli/templates/
and lib/dash/configuration/docs/*.yml are operator-chosen secret names, not identifiers
the gem enforces. They get renamed in the sample text only (3a), never read by name in code. KAMAL_PROXY_DOMAINS_TOKEN / KAMAL_PROXY_REFRESH_TOKEN and the /.kamal-proxy/redirects/refresh
HTTP route are owned by the proxy, not the gem — track them in zoolutions/dash-proxy.
Sub-stages
Stage
Scope
Repos
Target
3a
Local project files + DASH_* env dual-emit + dash migrate
gem
3.3.0
3b
Server .kamal/ → .dash/, config-digest label, local registry container
Drop KAMAL_* emission and the .kamal/ local fallback
gem
5.0.0
3b and 3c both force one proxy container replacement. If they land close together they may be
released as one gem version; the sub-stage split is about reviewable PRs, not mandatory releases.
Settled in interview (do not re-litigate)
Split into 3a/3b/3c, each independently shippable. KAMAL_* removal happens last (3d), not
bundled into any of them.
Rename the docker network AND the volumes (inventory 7 and 8) rather than freezing them.
Rationale from the user: no major site is live on dash yet, load balancing and ACME have
effectively one user, so a clean break now beats carrying legacy identifiers forever. The
migration must still be written defensively — see 3c.
KAMAL_* env vars: emit both.DASH_* and KAMAL_* are written side by side from 3a.
Reads (ENV["KAMAL_LOCK"], ENV["KAMAL_SERVER_LOCK"]) accept either, preferring DASH_*.
Old names are removed only at 5.0 (3d).
Server .kamal/ → .dash/: one guarded mv on boot. Not a dual-read fallback, not a
fresh start. [ -d .kamal ] && [ ! -d .dash ] && mv .kamal .dash, idempotent.
Proxy image: rename + MINIMUM_VERSION floor. No symlink compatibility layer, no runtime
probing. dash-proxy releases the renamed image first; the gem then bumps MINIMUM_VERSION to
that tag and switches every path in one commit. The existing Dash::Utils.older_version? gate
in lib/dash/cli/proxy.rb:35 already refuses older images.
Local .kamal/secrets and .kamal/hooks: fall back, with an explicit opt-in move. Defaults
become .dash/*; if .dash/ is absent and .kamal/ is present, use .kamal/ and warn. A new dash migrate command performs the local move when the operator asks for it — the gem never
rewrites the operator's working tree on its own.
Hard constraints carried into every sub-stage
Proxy image before gem, always. MINIMUM_VERSION must name a published, public ghcr.io/zoolutions/dash-proxy tag before the gem releases (rake release gates on it).
Never a -suffix version tag: Gem::Version reads - as prerelease and sorts it older,
which hard-fails dash proxy boot's minimum-version check.
Test assertions interpolate Dash::Configuration::Proxy::Run::MINIMUM_VERSION, never a literal.
No direct pushes to main; no manual lib/dash/version.rb bumps (only rake release writes it).
docs/ is a dash-deployed app with its own .kamal/ — it is a real consumer of 3a and should
be migrated in the same PR as a dogfooding check.
3d — file at 5.0 planning time. Removal list: KAMAL_* env emission (Dash::Tags#env, Dash::Configuration, Dash::Commands::Accessory), Dash::ProjectDirectory::LEGACY, Configuration::LEGACY_RUN_DIRECTORY, the org.kamal.proxy-config-digest read fallback,
the dual prune filters, the legacy-container replacement path, and the kamal network.
Each child issue is self-contained — hand one to a fresh implementation session on the sonnet
tier without needing this epic for context.
Execution order
3a → 3b → 3c, each a separate release. 3b and 3c may be collapsed into one gem version if they
land close together; the split exists to keep PRs reviewable, not because the releases must be
distinct. 3c is the only stage that requires a zoolutions/dash-proxy release first, and the only
one that causes a proxy outage.
Stage 3: rename server artifacts with a rolling-upgrade bridge
Epic. Follow-up to #115 (final stage), depends on stage 2 (#117, landed in
a2273854).Split into three executable sub-stages after a full inventory of the artifact surface.
Problem / Goal
Everything an operator or a server can see still says
kamal: the project's.kamal/directory, the
KAMAL_*env vars handed to hooks and injected into app containers, the.kamal/run directory on every host, thekamal-proxycontainer, thekamaldockernetwork, the
kamal-proxy-configvolume, and theorg.*labels the prune filters key on.Stage 3 renames all of it to
dash.Done looks like: a fresh
dash initproduces.dash/, a deployed host has.dash/and adash-proxycontainer on adashnetwork, hooks and app containers seeDASH_*, and anoperator upgrading from 3.2.0 gets there without a manual server intervention.
Full artifact inventory
.kamal/secrets,.kamal/hooks/KAMAL_*hook env +Dash::Tags#env+KAMAL_HOSTaccessory env.kamal/run directory on the hostorg.kamal.proxy-config-digestlabelkamal-docker-registrycontainerkamal-proxy/kamal-proxy-netcontainer nameskamaldocker networkkamal-proxy-config,kamal-loadbalancer-configvolumes (routing table + ACME cache)org.opencontainers.image.title=kamal-proxylabel (prune filter)kamal-proxy, user +/home/kamal-proxy/.config/kamal-proxy,/home/kamal-proxy/.apps-configKAMAL_*emission removed for goodNot in scope as a rename:
KAMAL_REGISTRY_PASSWORDand friends inlib/dash/cli/templates/and
lib/dash/configuration/docs/*.ymlare operator-chosen secret names, not identifiersthe gem enforces. They get renamed in the sample text only (3a), never read by name in code.
KAMAL_PROXY_DOMAINS_TOKEN/KAMAL_PROXY_REFRESH_TOKENand the/.kamal-proxy/redirects/refreshHTTP route are owned by the proxy, not the gem — track them in
zoolutions/dash-proxy.Sub-stages
DASH_*env dual-emit +dash migrate.kamal/→.dash/, config-digest label, local registry containerv1.1.0.0+ gem 4.0.0KAMAL_*emission and the.kamal/local fallback3b and 3c both force one proxy container replacement. If they land close together they may be
released as one gem version; the sub-stage split is about reviewable PRs, not mandatory releases.
Settled in interview (do not re-litigate)
KAMAL_*removal happens last (3d), notbundled into any of them.
Rationale from the user: no major site is live on dash yet, load balancing and ACME have
effectively one user, so a clean break now beats carrying legacy identifiers forever. The
migration must still be written defensively — see 3c.
KAMAL_*env vars: emit both.DASH_*andKAMAL_*are written side by side from 3a.Reads (
ENV["KAMAL_LOCK"],ENV["KAMAL_SERVER_LOCK"]) accept either, preferringDASH_*.Old names are removed only at 5.0 (3d).
.kamal/→.dash/: one guardedmvon boot. Not a dual-read fallback, not afresh start.
[ -d .kamal ] && [ ! -d .dash ] && mv .kamal .dash, idempotent.MINIMUM_VERSIONfloor. No symlink compatibility layer, no runtimeprobing. dash-proxy releases the renamed image first; the gem then bumps
MINIMUM_VERSIONtothat tag and switches every path in one commit. The existing
Dash::Utils.older_version?gatein
lib/dash/cli/proxy.rb:35already refuses older images..kamal/secretsand.kamal/hooks: fall back, with an explicit opt-in move. Defaultsbecome
.dash/*; if.dash/is absent and.kamal/is present, use.kamal/and warn. A newdash migratecommand performs the local move when the operator asks for it — the gem neverrewrites the operator's working tree on its own.
Hard constraints carried into every sub-stage
MINIMUM_VERSIONmust name a published, publicghcr.io/zoolutions/dash-proxytag before the gem releases (rake releasegates on it).-suffixversion tag:Gem::Versionreads-as prerelease and sorts it older,which hard-fails
dash proxy boot's minimum-version check.Dash::Configuration::Proxy::Run::MINIMUM_VERSION, never a literal.main; no manuallib/dash/version.rbbumps (onlyrake releasewrites it).docs/is a dash-deployed app with its own.kamal/— it is a real consumer of 3a and shouldbe migrated in the same PR as a dogfooding check.
Child issues
.kamal/→.dash/andDASH_*env vars (gem 3.3.0).kamal/→.dash/, config-digest label, local registry container (gem 3.4.0)v1.1.0.0+ gem 4.0.0)KAMAL_*env emission (Dash::Tags#env,Dash::Configuration,Dash::Commands::Accessory),Dash::ProjectDirectory::LEGACY,Configuration::LEGACY_RUN_DIRECTORY, theorg.kamal.proxy-config-digestread fallback,the dual prune filters, the legacy-container replacement path, and the
kamalnetwork.Each child issue is self-contained — hand one to a fresh implementation session on the
sonnettier without needing this epic for context.
Execution order
3a → 3b → 3c, each a separate release. 3b and 3c may be collapsed into one gem version if they
land close together; the split exists to keep PRs reviewable, not because the releases must be
distinct. 3c is the only stage that requires a
zoolutions/dash-proxyrelease first, and the onlyone that causes a proxy outage.