diff --git a/.claude/rules/docker-orchestration.md b/.claude/rules/docker-orchestration.md index 37b147dc..da22d9c8 100644 --- a/.claude/rules/docker-orchestration.md +++ b/.claude/rules/docker-orchestration.md @@ -41,7 +41,7 @@ When deployed via the cameleer-saas platform, this server orchestrates customer `startContainer` is now a two-phase op per replica: 1. **Volume create** — `cameleer-jars-{containerName}` named volume (per-replica, deterministic so cleanup in `removeContainer` can derive it). -2. **Loader container** — `loaderImage` (default `gitea.siegeln.net/cameleer/cameleer-runtime-loader:latest`, **built and published by the cameleer-saas repo** at `docker/runtime-loader/`), name `{containerName}-loader`, mount the volume **RW at `/app/jars`**, env vars `ARTIFACT_URL` + `ARTIFACT_EXPECTED_SIZE`. Loader downloads the JAR from the signed URL into the volume and exits 0. Orchestrator blocks on `waitContainerCmd().exec(WaitContainerResultCallback).awaitStatusCode(120, SECONDS)`. Loader container is removed in a `finally` block; on non-zero exit the volume is also removed and `RuntimeException` propagates so `DeploymentExecutor` marks the deployment FAILED. **Loader logs are captured before removal** (`captureLoaderLogs` — `logContainerCmd` with `withTail(50)`, capped at 4096 chars, 5s timeout) and appended to the thrown `RuntimeException` message as `". loader output: "`. Best-effort: log-capture failures are swallowed and don't mask the original exit. The loader image's Dockerfile pre-creates `/app/jars` owned by `loader:loader` (UID 1000) so the orchestrator's fresh named volume initialises with that ownership — without it the empty volume comes up as `root:root 0755` and wget exits 1 with "Permission denied". `LoaderHardeningIT` is the cross-repo contract test (pulls the published `:latest` and asserts exit 0 under the orchestrator's hardening shape). +2. **Loader container** — `loaderImage` (default `registry.cameleer.io/cameleer/cameleer-runtime-loader:latest`, **built and published by the cameleer-saas repo** at `docker/runtime-loader/`; CI pushes to `gitea.siegeln.net` under the same path — both names resolve to the same registry), name `{containerName}-loader`, mount the volume **RW at `/app/jars`**, env vars `ARTIFACT_URL` + `ARTIFACT_EXPECTED_SIZE`. Loader downloads the JAR from the signed URL into the volume and exits 0. Orchestrator blocks on `waitContainerCmd().exec(WaitContainerResultCallback).awaitStatusCode(120, SECONDS)`. Loader container is removed in a `finally` block; on non-zero exit the volume is also removed and `RuntimeException` propagates so `DeploymentExecutor` marks the deployment FAILED. **Loader logs are captured before removal** (`captureLoaderLogs` — `logContainerCmd` with `withTail(50)`, capped at 4096 chars, 5s timeout) and appended to the thrown `RuntimeException` message as `". loader output: "`. Best-effort: log-capture failures are swallowed and don't mask the original exit. The loader image's Dockerfile pre-creates `/app/jars` owned by `loader:loader` (UID 1000) so the orchestrator's fresh named volume initialises with that ownership — without it the empty volume comes up as `root:root 0755` and wget exits 1 with "Permission denied". `LoaderHardeningIT` is the cross-repo contract test (pulls the published `:latest` and asserts exit 0 under the orchestrator's hardening shape). 3. **Main container** — same hardening contract, mount the same volume **RO at `/app/jars`**, entrypoint reads `/app/jars/app.jar` (Spring Boot/Quarkus: `-jar /app/jars/app.jar`; plain Java: `-cp /app/jars/app.jar `; native: `exec /app/jars/app.jar`). `removeContainer(id)` derives the volume name from the inspected container name (Docker prefixes it with `/`) and removes the volume after the container removes — blue/green doesn't leak volumes. diff --git a/cameleer-server-app/src/main/java/io/cameleer/server/app/runtime/DeploymentExecutor.java b/cameleer-server-app/src/main/java/io/cameleer/server/app/runtime/DeploymentExecutor.java index 88c34e33..5d2c054a 100644 --- a/cameleer-server-app/src/main/java/io/cameleer/server/app/runtime/DeploymentExecutor.java +++ b/cameleer-server-app/src/main/java/io/cameleer/server/app/runtime/DeploymentExecutor.java @@ -36,7 +36,7 @@ public class DeploymentExecutor { @Autowired(required = false) private DockerNetworkManager networkManager; - @Value("${cameleer.server.runtime.baseimage:gitea.siegeln.net/cameleer/cameleer-runtime-base:latest}") + @Value("${cameleer.server.runtime.baseimage:registry.cameleer.io/cameleer/cameleer-runtime-base:latest}") private String baseImage; @Value("${cameleer.server.runtime.dockernetwork:cameleer}") @@ -69,7 +69,7 @@ public class DeploymentExecutor { @Value("${cameleer.server.runtime.certresolver:}") private String globalCertResolver; - @Value("${cameleer.server.runtime.loaderimage:gitea.siegeln.net/cameleer/cameleer-runtime-loader:latest}") + @Value("${cameleer.server.runtime.loaderimage:registry.cameleer.io/cameleer/cameleer-runtime-loader:latest}") private String loaderImage; @Value("${cameleer.server.runtime.artifacttokenttlseconds:600}") diff --git a/cameleer-server-app/src/main/resources/application.yml b/cameleer-server-app/src/main/resources/application.yml index 91d72085..88cca4b0 100644 --- a/cameleer-server-app/src/main/resources/application.yml +++ b/cameleer-server-app/src/main/resources/application.yml @@ -45,7 +45,7 @@ cameleer: runtime: enabled: ${CAMELEER_SERVER_RUNTIME_ENABLED:true} jarstoragepath: ${CAMELEER_SERVER_RUNTIME_JARSTORAGEPATH:/data/jars} - baseimage: ${CAMELEER_SERVER_RUNTIME_BASEIMAGE:gitea.siegeln.net/cameleer/cameleer-runtime-base:latest} + baseimage: ${CAMELEER_SERVER_RUNTIME_BASEIMAGE:registry.cameleer.io/cameleer/cameleer-runtime-base:latest} dockernetwork: ${CAMELEER_SERVER_RUNTIME_DOCKERNETWORK:cameleer} # Container runtime override. Empty (default) auto-detects: uses runsc # (gVisor) if the daemon has it registered, otherwise the daemon default @@ -65,7 +65,7 @@ cameleer: # short-lived sidecar that downloads the JAR from a signed URL into a # per-replica named volume, which the main container then mounts RO at # /app/jars. See issue #152 close-out + .claude/rules/docker-orchestration.md. - loaderimage: ${CAMELEER_SERVER_RUNTIME_LOADERIMAGE:gitea.siegeln.net/cameleer/cameleer-runtime-loader:latest} + loaderimage: ${CAMELEER_SERVER_RUNTIME_LOADERIMAGE:registry.cameleer.io/cameleer/cameleer-runtime-loader:latest} artifacttokenttlseconds: ${CAMELEER_SERVER_RUNTIME_ARTIFACTTOKENTTLSECONDS:600} artifactbaseurl: ${CAMELEER_SERVER_RUNTIME_ARTIFACTBASEURL:} indexer: