From 4087ce8f29eba0160034f798b150cb5c85d06fcd Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Thu, 9 Apr 2026 23:26:48 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20provisioned=20server=20containers=20?= =?UTF-8?q?=E2=80=94=20strip-prefix,=20Docker=20socket,=20env?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Traefik strip-prefix middleware so /t/{slug}/api -> /api on server - Add priority to routers (server API=10, UI=5) to prevent conflicts - Mount Docker socket + group_add in server containers for app deployment - Add JAR storage, Docker network, volume env vars for runtime orchestrator - Use HashMap for labels (>10 entries exceeds Map.of limit) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../provisioning/DockerTenantProvisioner.java | 63 ++++++++++++------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/src/main/java/net/siegeln/cameleer/saas/provisioning/DockerTenantProvisioner.java b/src/main/java/net/siegeln/cameleer/saas/provisioning/DockerTenantProvisioner.java index 8d5d894..35711c6 100644 --- a/src/main/java/net/siegeln/cameleer/saas/provisioning/DockerTenantProvisioner.java +++ b/src/main/java/net/siegeln/cameleer/saas/provisioning/DockerTenantProvisioner.java @@ -114,14 +114,22 @@ public class DockerTenantProvisioner implements TenantProvisioner { private void createServerContainer(TenantProvisionRequest req, String name) { String slug = req.slug(); - Map labels = Map.of( - "traefik.enable", "true", - "traefik.http.routers.server-" + slug + ".rule", "PathPrefix(`/t/" + slug + "/api`) || PathPrefix(`/t/" + slug + "/actuator`)", - "traefik.http.routers.server-" + slug + ".tls", "true", - "traefik.http.services.server-" + slug + ".loadbalancer.server.port", "8081", - "cameleer.tenant", slug, - "cameleer.role", "server" - ); + String prefix = "/t/" + slug; + + // Traefik labels — need >10 entries, use HashMap + var labels = new java.util.HashMap(); + labels.put("traefik.enable", "true"); + // Router: match /t/{slug}/api/* and /t/{slug}/actuator/* + labels.put("traefik.http.routers.server-" + slug + ".rule", + "PathPrefix(`" + prefix + "/api`) || PathPrefix(`" + prefix + "/actuator`)"); + labels.put("traefik.http.routers.server-" + slug + ".tls", "true"); + labels.put("traefik.http.routers.server-" + slug + ".priority", "10"); + // Strip the /t/{slug} prefix so server sees /api/... and /actuator/... + labels.put("traefik.http.middlewares.server-strip-" + slug + ".stripprefix.prefixes", prefix); + labels.put("traefik.http.routers.server-" + slug + ".middlewares", "server-strip-" + slug); + labels.put("traefik.http.services.server-" + slug + ".loadbalancer.server.port", "8081"); + labels.put("cameleer.tenant", slug); + labels.put("cameleer.role", "server"); List env = List.of( "SPRING_DATASOURCE_URL=" + props.datasourceUrl(), @@ -140,12 +148,18 @@ public class DockerTenantProvisioner implements TenantProvisioner { "CAMELEER_RUNTIME_ENABLED=true", "CAMELEER_SERVER_URL=http://" + name + ":8081", "CAMELEER_ROUTING_DOMAIN=" + props.publicHost(), - "CAMELEER_ROUTING_MODE=path" + "CAMELEER_ROUTING_MODE=path", + "CAMELEER_JAR_STORAGE_PATH=/data/jars", + "CAMELEER_DOCKER_NETWORK=" + props.networkName(), + "CAMELEER_JAR_DOCKER_VOLUME=cameleer-jars-" + slug ); + // Docker socket mount for app deployment within tenant server HostConfig hostConfig = HostConfig.newHostConfig() .withRestartPolicy(RestartPolicy.unlessStoppedRestart()) - .withNetworkMode(props.networkName()); + .withNetworkMode(props.networkName()) + .withBinds(new Bind("/var/run/docker.sock", new Volume("/var/run/docker.sock"))) + .withGroupAdd(List.of("0")); CreateContainerResponse resp = docker.createContainerCmd(props.serverImage()) .withName(name) @@ -159,7 +173,7 @@ public class DockerTenantProvisioner implements TenantProvisioner { .withRetries(12)) .exec(); - // Connect to traefik network + // Connect to traefik network with DNS alias docker.connectToNetworkCmd() .withNetworkId(props.traefikNetwork()) .withContainerId(resp.getId()) @@ -168,20 +182,23 @@ public class DockerTenantProvisioner implements TenantProvisioner { } private void createUiContainer(String slug, String uiName, String serverName) { - Map labels = Map.of( - "traefik.enable", "true", - "traefik.http.routers.ui-" + slug + ".rule", "PathPrefix(`/t/" + slug + "`)", - "traefik.http.routers.ui-" + slug + ".tls", "true", - "traefik.http.routers.ui-" + slug + ".priority", "1", - "traefik.http.services.ui-" + slug + ".loadbalancer.server.port", "80", - "traefik.http.middlewares.ui-strip-" + slug + ".stripprefix.prefixes", "/t/" + slug, - "traefik.http.routers.ui-" + slug + ".middlewares", "ui-strip-" + slug, - "cameleer.tenant", slug, - "cameleer.role", "server-ui" - ); + String prefix = "/t/" + slug; + + var labels = new java.util.HashMap(); + labels.put("traefik.enable", "true"); + // Router: catch-all for /t/{slug}/* (lower priority than server API) + labels.put("traefik.http.routers.ui-" + slug + ".rule", "PathPrefix(`" + prefix + "`)"); + labels.put("traefik.http.routers.ui-" + slug + ".tls", "true"); + labels.put("traefik.http.routers.ui-" + slug + ".priority", "5"); + // Strip /t/{slug} prefix so UI sees / + labels.put("traefik.http.middlewares.ui-strip-" + slug + ".stripprefix.prefixes", prefix); + labels.put("traefik.http.routers.ui-" + slug + ".middlewares", "ui-strip-" + slug); + labels.put("traefik.http.services.ui-" + slug + ".loadbalancer.server.port", "80"); + labels.put("cameleer.tenant", slug); + labels.put("cameleer.role", "server-ui"); List env = List.of( - "BASE_PATH=/t/" + slug, + "BASE_PATH=" + prefix, "API_URL=http://" + serverName + ":8081" );