Document the Traefik label loss and certificate refresh

This commit is contained in:
2026-10-25 23:41:30 +08:00
parent 437a2e447e
commit f4a63c884f
2 changed files with 21 additions and 2 deletions
+13 -2
View File
@@ -96,7 +96,12 @@ what we take vs. skip). Never vendor it into the build.
*not* merge its generated Traefik labels for API-created apps: the full
label set must live in `custom_labels` (mirror a working app's generated
format — http/https routers with `certresolver=letsencrypt` — plus the TCP
passthrough lines). Deployment JSON from the API can break `jq` (invalid
passthrough lines). **On 2026-10-25 the TCP lines were found missing after a
deploy** (custom_labels held only HTTP/Caddy labels), so Traefik terminated
tunnel SNI with its default certificate. Restored with
`PATCH /api/v1/applications/{uuid}` (`custom_labels` only, current HTTP
labels preserved) and verified they survive a redeploy; check them after any
app update. Deployment JSON from the API can break `jq` (invalid
escapes in the logs field) — parse with python instead.
- **Deployment**: the multi-stage `Dockerfile` builds a static non-root
alpine image; verify `CGO_ENABLED=0 go build -trimpath -ldflags="-s -w"
@@ -123,7 +128,13 @@ what we take vs. skip). Never vendor it into the build.
`ACME_DIRECTORY_URL=https://acme-staging-v02.api.letsencrypt.org/directory`
for tests; staging certs are untrusted. Local ACME E2E is Pebble +
pebble-challtestsrv with `DNS_PROVIDER=challtestsrv` and
`ACME_CA_BUNDLE=.../test/certs/pebble.minica.pem`.
`ACME_CA_BUNDLE=.../test/certs/pebble.minica.pem`. The official CLI caches
its certificate and polls `GET /api/tunnel/{id}/certificate` every 12 hours
(every minute within 30 days of expiry), applying a renewed certificate to
new connections without disconnecting; after switching the issuer, restart
the service (`opentunnel down` / `up`) to fetch it immediately. An issuance
killed mid-flight can leave `_acme-challenge` TXT records behind; delete them
with `dns/deleteByNameType` (production and test tunnels share the zone).
- **macOS system curl (LibreSSL) does not send SNI for `*.localhost` names**,
so `curl https://api.<id>.localhost:8443/` gets closed (the relay requires
SNI). Test with `openssl s_client -servername ...` or Homebrew curl.
+8
View File
@@ -107,6 +107,11 @@ For testing against Let's Encrypt staging (untrusted certificates, separate
rate limits), set
`ACME_DIRECTORY_URL=https://acme-staging-v02.api.letsencrypt.org/directory`.
The official CLI checks the certificate every 12 hours (every minute within
30 days of expiry) and applies a renewed certificate to new connections; after
switching the server's issuer, restart the CLI service (`opentunnel down` /
`opentunnel up`) to fetch the new certificate immediately.
## Admin dashboard
With `ADMIN_TOKEN` set, `https://<domain>/` (and `/admin`) serves the admin
@@ -148,6 +153,9 @@ untouched by SNI, because visitor TLS is terminated by the clients:
`...rule=HostSNIRegexp(`^.+\.tunnel\.example\.com$`)`,
`...tls.passthrough=true`,
`traefik.tcp.services.tunnel-data.loadbalancer.server.port=8443`.
After any app update or recreation, confirm the TCP labels are still in
`custom_labels`: Coolify has dropped them when regenerating labels, leaving
Traefik to terminate tunnel SNI with its default certificate.
- Persistent volume on `/app/data`; healthcheck on `/health`.
**B. Plain Docker host.** tunneld owns the public ports itself: map host