Currently once deploy <image> --host <host> fails with ErrHostnameInUse if the hostname is already in use. This makes the CLI unsuitable for automated workflows (CI/CD pipelines, deployment scripts) where the same command is expected to be re-runnable safely.
The TUI already has the right behaviour: when you change settings for a running app, it performs a zero-downtime replacement — start the new container, wait for kamal-proxy to health-check it and cut over traffic, then remove the old container. The CLI should do the same.
Proposed behaviour
- If the hostname is not in use: deploy as today (no change).
- If the hostname is already in use by an app with the same image and settings: do nothing and exit successfully (idempotent).
- If the hostname is already in use but the image or settings differ: perform a zero-downtime replacement using the same
deployWithVolume → removeContainersExcept flow the TUI uses.
Why this matters
- Running
once deploy from a CI pipeline on every push is the natural usage pattern, but today it requires a manual once remove first.
- The zero-downtime logic already exists in
Application.Deploy() — the CLI just needs to look up the existing app by hostname instead of failing when it finds one.
- Idempotency also removes the footgun where a failed deploy leaves an app registered under a hostname and blocks all future deploys until manually cleaned up.
Suggested implementation sketch
In deploy.go, instead of calling ns.HostInUse(host) and returning an error, look up the existing app by host. If found, reuse its Application object (preserving the app name and volume) and call app.Deploy() with the updated settings — exactly what settings.go:handleFormSubmit does.
Currently
once deploy <image> --host <host>fails withErrHostnameInUseif the hostname is already in use. This makes the CLI unsuitable for automated workflows (CI/CD pipelines, deployment scripts) where the same command is expected to be re-runnable safely.The TUI already has the right behaviour: when you change settings for a running app, it performs a zero-downtime replacement — start the new container, wait for kamal-proxy to health-check it and cut over traffic, then remove the old container. The CLI should do the same.
Proposed behaviour
deployWithVolume→removeContainersExceptflow the TUI uses.Why this matters
once deployfrom a CI pipeline on every push is the natural usage pattern, but today it requires a manualonce removefirst.Application.Deploy()— the CLI just needs to look up the existing app by hostname instead of failing when it finds one.Suggested implementation sketch
In
deploy.go, instead of callingns.HostInUse(host)and returning an error, look up the existing app by host. If found, reuse itsApplicationobject (preserving the app name and volume) and callapp.Deploy()with the updated settings — exactly whatsettings.go:handleFormSubmitdoes.