Backlog: Multi-port Traefik routing per app #149

Open
opened 2026-04-23 17:50:37 +02:00 by claude · 0 comments
Owner

Context

Today an app can only expose one external port through Traefik. TraefikLabelBuilder.build(...) emits a single loadbalancer.server.port label from config.appPort() and a single router rule (path-prefix or subdomain). The containerConfig.exposedPorts field (UI: Resources → Exposed Ports) was only writing Docker's Config.ExposedPorts metadata via withExposedPorts(...) — it never produced a host binding or a Traefik route. The field is cosmetic and has been removed from the UI for now (see follow-up commit).

Use case

Apps that need to expose two or more HTTP surfaces externally with different routing (e.g. a REST API on 9090 and a management/admin interface on 9091). Current workarounds:

  1. Register the same JAR as a second app with its own slug and appPort — independent Traefik routes, but 2× resource cost for the same JVM.
  2. Consolidate at the app layer — one port, separate path prefixes inside the app.
  3. Keep the second port internal — already works; other containers in cameleer-traefik / cameleer-env-{tenantId}-{envSlug} can reach any listening port via Docker DNS, no config needed.

Proposal

Extend the container config to support N externally-routed ports:

  • New field containerConfig.additionalPorts: List<{ port: int, pathSuffix?: string, subdomainSuffix?: string }> (alongside the existing appPort which stays the primary).
  • TraefikLabelBuilder emits one extra traefik.http.services.{svc}-{suffix}.loadbalancer.server.port + traefik.http.routers.{svc}-{suffix}.rule per entry.
    • Path mode: suffix appends to the existing prefix, e.g. /{envSlug}/{appSlug}-admin/.
    • Subdomain mode: distinct subdomain, e.g. {appSlug}-admin-{envSlug}.{domain}.
  • ConfigMerger / ResolvedContainerConfig / ContainerRequest carry the new list.
  • UI Resources tab: new "Additional Routes" section (replaces the removed Exposed Ports UI).
  • Decide: does each additional route share sslOffloading / stripPathPrefix with the primary, or do they get their own toggles? (Default: share — simpler.)

Scope

  • Schema: JSONB-only, no SQL migration (containerConfig is already free-form).
  • Breaking change: no — additional-ports is purely additive.
  • The retired exposedPorts JSONB values stay on disk; ConfigMerger can ignore them or a one-liner can drop them on next write.

Out of scope

  • Host port publishing (-p / withPortBindings) — not needed; all ingress flows through Traefik.
  • Non-HTTP protocols (TCP/UDP routers) — separate ticket if ever needed.

Acceptance

  • User can add a second route in the UI; after redeploy, both routes return 200 from curl.
  • Existing single-port apps continue to deploy unchanged.
  • TraefikLabelBuilder unit tests cover multi-port label generation in both path and subdomain modes.
## Context Today an app can only expose **one** external port through Traefik. `TraefikLabelBuilder.build(...)` emits a single `loadbalancer.server.port` label from `config.appPort()` and a single router rule (path-prefix or subdomain). The `containerConfig.exposedPorts` field (UI: Resources → Exposed Ports) was only writing Docker's `Config.ExposedPorts` metadata via `withExposedPorts(...)` — it never produced a host binding or a Traefik route. The field is cosmetic and has been removed from the UI for now (see follow-up commit). ## Use case Apps that need to expose two or more HTTP surfaces externally with different routing (e.g. a REST API on 9090 and a management/admin interface on 9091). Current workarounds: 1. Register the same JAR as a second app with its own slug and `appPort` — independent Traefik routes, but 2× resource cost for the same JVM. 2. Consolidate at the app layer — one port, separate path prefixes inside the app. 3. Keep the second port internal — already works; other containers in `cameleer-traefik` / `cameleer-env-{tenantId}-{envSlug}` can reach any listening port via Docker DNS, no config needed. ## Proposal Extend the container config to support N externally-routed ports: - New field `containerConfig.additionalPorts: List<{ port: int, pathSuffix?: string, subdomainSuffix?: string }>` (alongside the existing `appPort` which stays the primary). - `TraefikLabelBuilder` emits one extra `traefik.http.services.{svc}-{suffix}.loadbalancer.server.port` + `traefik.http.routers.{svc}-{suffix}.rule` per entry. - Path mode: suffix appends to the existing prefix, e.g. `/{envSlug}/{appSlug}-admin/`. - Subdomain mode: distinct subdomain, e.g. `{appSlug}-admin-{envSlug}.{domain}`. - `ConfigMerger` / `ResolvedContainerConfig` / `ContainerRequest` carry the new list. - UI Resources tab: new "Additional Routes" section (replaces the removed Exposed Ports UI). - Decide: does each additional route share `sslOffloading` / `stripPathPrefix` with the primary, or do they get their own toggles? (Default: share — simpler.) ## Scope - Schema: JSONB-only, no SQL migration (containerConfig is already free-form). - Breaking change: no — additional-ports is purely additive. - The retired `exposedPorts` JSONB values stay on disk; `ConfigMerger` can ignore them or a one-liner can drop them on next write. ## Out of scope - Host port publishing (`-p` / `withPortBindings`) — not needed; all ingress flows through Traefik. - Non-HTTP protocols (TCP/UDP routers) — separate ticket if ever needed. ## Acceptance - User can add a second route in the UI; after redeploy, both routes return 200 from `curl`. - Existing single-port apps continue to deploy unchanged. - `TraefikLabelBuilder` unit tests cover multi-port label generation in both path and subdomain modes.
claude added the featureui labels 2026-04-23 17:50:37 +02:00
Sign in to join this conversation.