Summary
The installer accepts an APP_PORT and writes it to .env without checking whether that port is actually free on the host. Step 5 then runs:
docker compose ... up -d --remove-orphans --force-recreate --wait --wait-timeout 60
Because of --force-recreate, the existing Coolify container is torn down before the new one attempts to bind. If the bind fails, the installer exits with set -e and Coolify is left down — not rolled back, not restarted.
#63 fixed the value written to APP_PORT (bind address → plain port). This is the same failure mode surviving at the same step, from a different cause.
Actual failure
Fresh run of the current published script (identical to main) on a host where coolify-proxy already publishes 3000:3000:
2. Configuring Coolify dashboard port (APP_PORT)
- APP_PORT must be a plain port number (no bind address), e.g. 3000.
- Added APP_PORT (it was missing)
...
5. Restarting Coolify
Container coolify Recreated
Container coolify Starting
Error response from daemon: failed to set up container networking: driver failed
programming external connectivity on endpoint coolify: Bind for 0.0.0.0:3000 failed:
port is already allocated
Exit 1, Coolify down. Recovery required manually restoring .env and re-running docker compose up -d.
The validation added in #63 passes cleanly here — 3000 is a syntactically valid port. It just isn't available, which nothing checks.
Note the default makes this easy to hit: APP_PORT_DEFAULT=3000 is a very common port for something else to already hold, and pressing Enter accepts it.
Suggested fix
Validate availability in the prompt loop, next to the existing is_valid_port / reserved-8000 checks, so a bad choice is rejected before anything is written or recreated:
is_port_free() {
! ss -ltn "( sport = :$1 )" 2>/dev/null | grep -q LISTEN
}
with a caveat: the port currently used by Coolify itself must count as free, or re-running on an unchanged host would reject its own port. Comparing against the ports held by the coolify container specifically would handle that.
Belt-and-braces: drop --force-recreate in step 5, or capture the prior APP_PORT and restore .env + docker compose up -d on failure, so a failed run cannot leave the host down.
Related: #62, #63. Companion issues: #66 (dynamic config clobbering), #68 (additive Traefik merge).
Summary
The installer accepts an
APP_PORTand writes it to.envwithout checking whether that port is actually free on the host. Step 5 then runs:Because of
--force-recreate, the existing Coolify container is torn down before the new one attempts to bind. If the bind fails, the installer exits withset -eand Coolify is left down — not rolled back, not restarted.#63 fixed the value written to
APP_PORT(bind address → plain port). This is the same failure mode surviving at the same step, from a different cause.Actual failure
Fresh run of the current published script (identical to
main) on a host wherecoolify-proxyalready publishes3000:3000:Exit 1, Coolify down. Recovery required manually restoring
.envand re-runningdocker compose up -d.The validation added in #63 passes cleanly here —
3000is a syntactically valid port. It just isn't available, which nothing checks.Note the default makes this easy to hit:
APP_PORT_DEFAULT=3000is a very common port for something else to already hold, and pressing Enter accepts it.Suggested fix
Validate availability in the prompt loop, next to the existing
is_valid_port/ reserved-8000 checks, so a bad choice is rejected before anything is written or recreated:with a caveat: the port currently used by Coolify itself must count as free, or re-running on an unchanged host would reject its own port. Comparing against the ports held by the
coolifycontainer specifically would handle that.Belt-and-braces: drop
--force-recreatein step 5, or capture the priorAPP_PORTand restore.env+docker compose up -don failure, so a failed run cannot leave the host down.Related: #62, #63. Companion issues: #66 (dynamic config clobbering), #68 (additive Traefik merge).