Repository navigation
fix(traefik): renew certificates over 443 only and never publish port 80 - #53
Merged
Merged
Conversation
buildComponentUpdateSteps returns the pre-download gate and the steps of the selected components. wrapComponentUpdateSteps adds the shell header and the done marker. buildComponentUpdateScript composes the two with the same output, so the SSH update path is unchanged, and the panel host update can run the Traefik steps as they are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
buildHostStackRows turns the versions read on the panel host into one row per component: panel, docker, traefik, postgres and redis. Each row reports its installed and target version, whether it is outdated, and how it can be updated: from the Web Server Update dialog (panel), from the panel itself (traefik, redis), or only in the maintenance window (docker, postgres). Postgres is compared by major version, and a major change is reported as needing a migration. Values that cannot be read are unknown, never outdated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
updateHostStackComponents runs one bash script on the panel host. It replaces Traefik with the steps the SSH update path already uses, and updates the Redis service to the pinned image with stop-first and rollback. Redis is pulled before anything changes. The Redis update waits until the swarm reports the update as completed and redis-cli answers PONG, within a timeout; a rollback, a pause, a refused update or a timeout fails the run. A second update is refused while one is running. readHostStackRows reads the installed versions with read-only docker commands and the panel's own update check, which has a timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s page settings.hostStack returns the rows of the panel host components. It is limited to local host access (owner or admin) and returns nothing on cloud. settings.updateHostStack is a subscription that runs the Traefik and Redis updates on the panel host and streams their logs. It refuses docker, postgres and panel with the next step, refuses while a panel update or another host update is running, and writes an audit entry. Both procedures are classified in the procedure access map; the update is dangerous, like the other host settings changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A "Panel host (this server)" card sits above the server cards. Its Components button opens the panel host rows with the installed and target versions, the status and the action for each component. Traefik and Redis can be updated from the dialog after a confirmation, with a warning that Traefik is recreated and the admin site is unreachable for a few seconds. Docker and Postgres show the maintenance-window command, and the panel row opens the Web Server Update dialog. The card is hidden for users who cannot manage the panel host. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ostgres Docker and Postgres are always updated in the maintenance window, so their rows keep the reason and the exact installer command even when they are current. The card can then show the command at any time. Traefik and Redis rows carry neither, since the panel runs them. A Postgres row whose version cannot be read gets no command, because the image it would keep is unknown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rows the panel can update always show a checkbox. It is enabled only when the component is outdated, so an up-to-date component cannot be selected by accident. Manual rows show their reason and command, and unknown rows show the reason they could not be read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The host components update replaces Traefik and restarts Redis from a script run by this panel. A panel update restarts the panel container, which would stop that script in the middle of the change. The update mutation now refuses while the host components update runs, the same way the host update refuses while a panel update runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ainer The installer mounts the socket read-only, but the generated container for remote servers and the panel host dropped the :ro flag. Keep the same mount in both places, so an update does not change how Traefik sees the daemon. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The panel host's Traefik publishes 443 only, but the generated container and the installer always published 80 as well. A replacement would have opened port 80 without anyone asking for it. Read the current publication before the old container stops and reuse it for the new one and for the restore; a fresh install still publishes 80. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The settings router reads the host stack constant and update guard, so the module mocks in the settings boundary tests must provide them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ontainer did not have it A replacement container is created with the default bridge network. The live Traefik had no bridge attachment, so the update added one it did not have before. Detach bridge after the new container runs, but only when the container being replaced was not on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An empty array expanded under set -u fails on bash 3.2, which the macOS CI runner uses. Use the guarded form, so port 80 is passed only when it is published. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d of refusing The operator update used to stop before any change when the installed Postgres major version differed from the pinned one. It now prints "Postgres <N> stays. A major upgrade is a separate migration; this update does not change it." and continues: - no Postgres backup, no Postgres pre-pull and no Postgres swap in that run - Redis, Traefik, the panel and the Docker Engine step run as before - when the majors match, the backup and the swap behave as before postgres_ready now checks the image the service actually runs. Checking the pinned image would never find a kept older Postgres, so the Docker Engine health check would roll the upgrade back. Tests: the refusal test now expects the skip line, no backup, no swap and the other steps in order. A new Docker Engine upgrade test covers a kept Postgres. The fake docker ps honors the ancestor filter for dokploy-postgres, so the readiness check against the pinned image fails that test. Docs: docs/server-maintenance.md is a new operator guide. The install guide describes the new Postgres behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The operator guide now says only port 443 (TCP and UDP) serves domain traffic, port 80 is never opened, and certificates renew only over 443 through the TLS challenge. The UFW note no longer lists port 80. The note about the panel's own port 3000 is unchanged. The installer code is not changed here; that change is on a separate branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Traefik no longer publishes host port 80 from the panel or the installer. The installer no longer opens it in UFW or the WSL firewall rule and no longer requires it to be free. The ACME resolver uses tlsChallenge (TLS-ALPN-01 on port 443) instead of httpChallenge, so certificate renewal needs only port 443. - traefik-setup: remove the port 80 publication from the run, create, standalone and swarm builders. Stored port lists that still contain host port 80 are filtered before the container is built, so a later recreate cannot restore it. The web entrypoint stays for plain-HTTP routers. - server-components and server-setup: remove the traefik_http_publish branch, the UFW rule for port 80 and the port 80 warning. - settings router: updateTraefikPorts rejects host port 80. - install-agenthits.sh: remove TRAEFIK_HTTP_PUBLISH, the -p 80:80/tcp flag, the UFW rule and the port 80 preflight. The static config uses tlsChallenge. install-agenthits.ps1 drops port 80 from the WSL firewall rule and the busy-port check. - Existing traefik.yml files are not rewritten. The operator guide covers migrating a server. - Docs and CLAUDE.md state the rule and explain renewal errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Renewal used the HTTP-01 challenge on port 80. Servers installed before the port 80 change may still have an httpChallenge block in traefik.yml. Removing host port 80 without converting that block would stop renewals, so every path that creates or replaces the Traefik container converts it to tlsChallenge first: - fresh installer install, and the installer Traefik update - the Traefik component update in the panel - saving Traefik ports in the panel - server setup when the Traefik container is missing Only the httpChallenge block is rewritten; other settings stay as they are. A dated copy of the config is kept next to it, and the result is verified before it is written. The awk exit status is checked, and a failed write is restored from the copy. A config path that is a directory, cannot be read, or cannot be checked through sudo stops the step without any change. The installer keeps its config path in TRAEFIK_CONFIG_FILE, so the file it converts is the file the container mounts. Tests cover the installer function and call order under bash, the panel step under bash and /bin/sh with temp files, the failing awk, sudo and test cases, and the port-save and server-setup paths. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A comment line inside the httpChallenge block no longer ends the removal, and the output is rejected if a child line is left indented under tlsChallenge. - A failed update keeps the converted config instead of restoring the old httpChallenge one: the old container works with it because 443 is published, and restoring httpChallenge would leave renewal needing a closed port 80. - The fresh-install path restores the original config when the write fails. - rm -rf on the config path is limited to a directory named traefik.yml. - Saving Traefik ports no longer fails for a user without sudo when the config is readable and has no httpChallenge; otherwise it still stops without changes. - The error from a failed conversion names the config and its backup. Tests cover each case, including a comment inside the block and a failing awk. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- The Traefik step does nothing when the running container already uses the target image, so a repeated run (for example after the panel reconnects) causes no second outage. - The update lock is taken synchronously before the audit call and released on every exit path, and a panel update cannot start while a host update runs. - The Postgres row compares the full tag, so 18 against 18.6 shows as needing an update. The manual command is shown only for a different major. - The dialog looks for the done and failed markers in the accumulated log, so a marker split across two chunks is not missed. Tests cover the repeated run, the lock race and release, the row comparison, and the split marker. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…/traefik-port-80-policy # Conflicts: # packages/server/src/setup/server-components.ts
Checks run only for PRs whose base is AgentHits-Dev, so a PR stacked on another branch is never checked. The rule keeps every PR on that base. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…-port-80-policy # Conflicts: # apps/dokploy/__test__/server/server-components.test.ts # install-agenthits.sh # packages/server/src/setup/server-components.ts # packages/server/src/setup/traefik-setup.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Certificates now renew over port 443 only (TLS-ALPN-01). The installer and the panel never publish host port 80, and the static Traefik config uses
tlsChallenge.Servers installed before this change may still have an
httpChallengeblock in their Traefik config. Removing port 80 without converting that block would stop renewals, so every path that creates or replaces the Traefik container converts it first:How the conversion works
httpChallengeblock is rewritten, totlsChallenge: {}. Other settings stay as they are.traefik.yml.bak-<UTC time>.Stacking
This PR builds on the panel host stack branch, because it changes code that branch introduces. Before that PR is merged, change this PR's base to the main branch. Otherwise GitHub may close this PR when the old branch is removed.
Operator guide
docs/certificates-and-ports.mdexplains the rule, what to do when a renewal error appears, and how to check the published ports.Test plan
bun run test __test__/server): 233 passed, 19 files.bash -nandshellcheck -S warningpass for the installer.biome checkandtsc --noEmitpass in both packages.Not verified
🤖 Generated with Claude Code