Document the Traefik label loss and certificate refresh
This commit is contained in:
@@ -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
|
*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
|
label set must live in `custom_labels` (mirror a working app's generated
|
||||||
format — http/https routers with `certresolver=letsencrypt` — plus the TCP
|
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.
|
escapes in the logs field) — parse with python instead.
|
||||||
- **Deployment**: the multi-stage `Dockerfile` builds a static non-root
|
- **Deployment**: the multi-stage `Dockerfile` builds a static non-root
|
||||||
alpine image; verify `CGO_ENABLED=0 go build -trimpath -ldflags="-s -w"
|
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`
|
`ACME_DIRECTORY_URL=https://acme-staging-v02.api.letsencrypt.org/directory`
|
||||||
for tests; staging certs are untrusted. Local ACME E2E is Pebble +
|
for tests; staging certs are untrusted. Local ACME E2E is Pebble +
|
||||||
pebble-challtestsrv with `DNS_PROVIDER=challtestsrv` and
|
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**,
|
- **macOS system curl (LibreSSL) does not send SNI for `*.localhost` names**,
|
||||||
so `curl https://api.<id>.localhost:8443/` gets closed (the relay requires
|
so `curl https://api.<id>.localhost:8443/` gets closed (the relay requires
|
||||||
SNI). Test with `openssl s_client -servername ...` or Homebrew curl.
|
SNI). Test with `openssl s_client -servername ...` or Homebrew curl.
|
||||||
|
|||||||
@@ -107,6 +107,11 @@ For testing against Let's Encrypt staging (untrusted certificates, separate
|
|||||||
rate limits), set
|
rate limits), set
|
||||||
`ACME_DIRECTORY_URL=https://acme-staging-v02.api.letsencrypt.org/directory`.
|
`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
|
## Admin dashboard
|
||||||
|
|
||||||
With `ADMIN_TOKEN` set, `https://<domain>/` (and `/admin`) serves the admin
|
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$`)`,
|
`...rule=HostSNIRegexp(`^.+\.tunnel\.example\.com$`)`,
|
||||||
`...tls.passthrough=true`,
|
`...tls.passthrough=true`,
|
||||||
`traefik.tcp.services.tunnel-data.loadbalancer.server.port=8443`.
|
`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`.
|
- Persistent volume on `/app/data`; healthcheck on `/health`.
|
||||||
|
|
||||||
**B. Plain Docker host.** tunneld owns the public ports itself: map host
|
**B. Plain Docker host.** tunneld owns the public ports itself: map host
|
||||||
|
|||||||
Reference in New Issue
Block a user