diff --git a/.claude/rules/docker-orchestration.md b/.claude/rules/docker-orchestration.md index 9c9a83d0..b63545e0 100644 --- a/.claude/rules/docker-orchestration.md +++ b/.claude/rules/docker-orchestration.md @@ -48,13 +48,15 @@ When deployed via the cameleer-saas platform, this server orchestrates customer `DeploymentExecutor` generates the signed URL via `ArtifactDownloadTokenSigner.sign(appVersion.id(), Duration.ofSeconds(artifactTokenTtlSeconds))` and passes `appVersion.id()`, the URL, `appVersion.jarSizeBytes()`, and the loader image into `ContainerRequest`. The host filesystem is no longer involved at deploy time. -**Loader → server reachability**: the loader container hits the Cameleer server over HTTP from inside its -own Docker network. The signed URL is built from `cameleer.server.runtime.artifactbaseurl` (preferred), falling -back to `cameleer.server.runtime.serverurl`, falling back to `http://cameleer-server:8081`. The default works -in SaaS mode because `DockerNetworkManager` adds `cameleer-traefik` as an additional network for tenant -containers, and the server is reachable on that network via the `cameleer-server` DNS alias. For non-SaaS -topologies (server on a different network than tenants), set `CAMELEER_SERVER_RUNTIME_ARTIFACTBASEURL` -explicitly to a URL the loader can reach. +**Loader → server reachability**: the loader hits the Cameleer server from its **primary** Docker +network only (`request.network()`, set from `CAMELEER_SERVER_RUNTIME_DOCKERNETWORK`). Additional networks +(`cameleer-traefik`, per-env) are attached by `DockerNetworkManager.connectContainer` AFTER `startContainer` +returns — by which time the loader has already exited. The loader cannot use them. The signed URL is built +from `cameleer.server.runtime.artifactbaseurl` (preferred), falling back to `cameleer.server.runtime.serverurl`, +falling back to `http://cameleer-server:8081`. The default works in SaaS mode because the tenant's primary +network (`cameleer-tenant-{slug}`) hosts the tenant's own server — same `CAMELEER_SERVER_RUNTIME_DOCKERNETWORK` +on both. For non-SaaS topologies, set `CAMELEER_SERVER_RUNTIME_ARTIFACTBASEURL` to a URL the loader can reach +on its primary network. ## DeploymentExecutor Details diff --git a/HOWTO.md b/HOWTO.md index 932e97d6..be0f8076 100644 --- a/HOWTO.md +++ b/HOWTO.md @@ -495,8 +495,10 @@ Key settings in `cameleer-server-app/src/main/resources/application.yml`. All cu | `cameleer.server.runtime.baseimage` | `cameleer-runtime-base:latest` | `CAMELEER_SERVER_RUNTIME_BASEIMAGE` | Base Docker image for app containers | | `cameleer.server.runtime.dockernetwork` | `cameleer` | `CAMELEER_SERVER_RUNTIME_DOCKERNETWORK` | Primary Docker network | | `cameleer.server.runtime.dockerruntime` | *(empty = auto)* | `CAMELEER_SERVER_RUNTIME_DOCKERRUNTIME` | Container runtime override. Empty auto-detects gVisor (`runsc`) when registered with the daemon and falls back to the daemon default. Set to e.g. `kata` to force a specific runtime, or `runc` to force the default even if `runsc` is installed. | -| `cameleer.server.runtime.jarstoragepath` | `/data/jars` | `CAMELEER_SERVER_RUNTIME_JARSTORAGEPATH` | JAR file storage directory | -| `cameleer.server.runtime.jardockervolume` | *(empty)* | `CAMELEER_SERVER_RUNTIME_JARDOCKERVOLUME` | Docker volume for JAR sharing | +| `cameleer.server.runtime.jarstoragepath` | `/data/jars` | `CAMELEER_SERVER_RUNTIME_JARSTORAGEPATH` | JAR file storage directory (used by `FilesystemArtifactStore`) | +| `cameleer.server.runtime.loaderimage` | `gitea.siegeln.net/cameleer/cameleer-runtime-loader:latest` | `CAMELEER_SERVER_RUNTIME_LOADERIMAGE` | Init-container image that fetches the JAR via signed URL | +| `cameleer.server.runtime.artifacttokenttlseconds` | `600` | `CAMELEER_SERVER_RUNTIME_ARTIFACTTOKENTTLSECONDS` | TTL (seconds) for HMAC-signed artifact-download URLs | +| `cameleer.server.runtime.artifactbaseurl` | *(empty)* | `CAMELEER_SERVER_RUNTIME_ARTIFACTBASEURL` | Base URL the loader uses to reach the server. Blank falls back to `serverurl`, then `http://cameleer-server:8081`. Must be reachable from the loader container's primary Docker network. | | `cameleer.server.runtime.routingmode` | `path` | `CAMELEER_SERVER_RUNTIME_ROUTINGMODE` | `path` or `subdomain` Traefik routing | | `cameleer.server.runtime.routingdomain` | `localhost` | `CAMELEER_SERVER_RUNTIME_ROUTINGDOMAIN` | Domain for Traefik routing labels | | `cameleer.server.runtime.serverurl` | *(empty)* | `CAMELEER_SERVER_RUNTIME_SERVERURL` | Server URL injected into app containers | diff --git a/cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/DeploymentExecutor.java b/cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/DeploymentExecutor.java index 83757a4c..73e1ed19 100644 --- a/cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/DeploymentExecutor.java +++ b/cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/DeploymentExecutor.java @@ -111,9 +111,12 @@ public class DeploymentExecutor { if (artifactBaseUrl.isBlank() && globalServerUrl.isBlank()) { log.warn("Neither cameleer.server.runtime.artifactbaseurl nor cameleer.server.runtime.serverurl is set. " + "Loader containers will fall back to http://cameleer-server:8081 — this requires the loader's " - + "Docker network to resolve `cameleer-server`. In SaaS mode the server is on `cameleer-traefik` " - + "which is added as an additional network for tenant containers, so this works. For other " - + "deployment topologies, set CAMELEER_SERVER_RUNTIME_ARTIFACTBASEURL explicitly."); + + "PRIMARY Docker network (CAMELEER_SERVER_RUNTIME_DOCKERNETWORK) to resolve `cameleer-server`. " + + "Additional networks (e.g. cameleer-traefik) are attached AFTER startContainer returns, by " + + "which time the loader has already exited — they are not available to the loader. In SaaS " + + "mode the tenant primary network (cameleer-tenant-{slug}) hosts the tenant's server, so this " + + "works. For other topologies, set CAMELEER_SERVER_RUNTIME_ARTIFACTBASEURL to a URL the loader " + + "can reach over the primary network."); } } diff --git a/docs/handoff/2026-04-27-init-container-jar-fetch.md b/docs/handoff/2026-04-27-init-container-jar-fetch.md index 9e169ce9..51b963ee 100644 --- a/docs/handoff/2026-04-27-init-container-jar-fetch.md +++ b/docs/handoff/2026-04-27-init-container-jar-fetch.md @@ -90,11 +90,15 @@ New env vars (`application.yml` defaults shown): Removed: `CAMELEER_SERVER_RUNTIME_JARDOCKERVOLUME` — no longer needed (loader downloads via HTTP, not bind-mount). -`@PostConstruct` WARN logs at server startup if neither `artifactbaseurl` nor `serverurl` is set, pointing at the implicit `cameleer-server` Docker DNS dependency that only works on `cameleer-traefik`. +`@PostConstruct` WARN logs at server startup if neither `artifactbaseurl` nor `serverurl` is set, pointing at the implicit `cameleer-server` Docker DNS dependency on the loader's primary network. ## Network reachability requirement -The loader container must be able to reach the Cameleer server over HTTP. In SaaS mode this works because `DockerNetworkManager` adds `cameleer-traefik` as an additional network for tenant containers, and the server is reachable on that network via the `cameleer-server` DNS alias. For non-SaaS topologies, set `CAMELEER_SERVER_RUNTIME_ARTIFACTBASEURL` to a URL the loader can reach. +The loader container reaches the server over the **primary** Docker network only — `request.network()` in `ContainerRequest`, set from `CAMELEER_SERVER_RUNTIME_DOCKERNETWORK`. Additional networks (`cameleer-traefik`, per-env, etc.) are attached by `DockerNetworkManager.connectContainer` AFTER `startContainer` returns, by which time the loader has already exited — they are NOT available to the loader. + +In SaaS mode this works because the tenant's primary network is `cameleer-tenant-{slug}` and the tenant's own `cameleer-server` instance is configured to run on that same network (`CAMELEER_SERVER_RUNTIME_DOCKERNETWORK=cameleer-tenant-{slug}` on the server's compose/manifest). The loader resolves `cameleer-server` via Docker DNS on the primary network and pulls the artifact directly. + +For non-SaaS topologies (e.g. server on a different network from tenant containers), set `CAMELEER_SERVER_RUNTIME_ARTIFACTBASEURL` to a URL the loader can reach over its primary network. ## Documented but skipped