diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DeploymentExecutor.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DeploymentExecutor.java index f1233f89..22e04669 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DeploymentExecutor.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DeploymentExecutor.java @@ -58,6 +58,12 @@ public class DeploymentExecutor { @Value("${cameleer.runtime.server-url:}") private String globalServerUrl; + @Value("${cameleer.runtime.jar-docker-volume:}") + private String jarDockerVolume; + + @Value("${cameleer.runtime.jar-storage-path:/data/jars}") + private String jarStoragePath; + public DeploymentExecutor(RuntimeOrchestrator orchestrator, DeploymentService deploymentService, AppService appService, @@ -122,8 +128,11 @@ public class DeploymentExecutor { String containerName = env.slug() + "-" + app.slug() + "-" + i; Long cpuQuota = config.cpuLimit() != null ? (long) (config.cpuLimit() * 100_000) : null; + String volumeName = jarDockerVolume != null && !jarDockerVolume.isBlank() ? jarDockerVolume : null; ContainerRequest request = new ContainerRequest( - containerName, baseImage, jarPath, primaryNetwork, + containerName, baseImage, jarPath, + volumeName, jarStoragePath, + primaryNetwork, envNet != null ? List.of(envNet) : List.of(), baseEnvVars, labels, config.memoryLimitBytes(), config.memoryReserveBytes(), diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DockerRuntimeOrchestrator.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DockerRuntimeOrchestrator.java index c0830953..3896f656 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DockerRuntimeOrchestrator.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DockerRuntimeOrchestrator.java @@ -60,16 +60,23 @@ public class DockerRuntimeOrchestrator implements RuntimeOrchestrator { List envList = request.envVars().entrySet().stream() .map(e -> e.getKey() + "=" + e.getValue()).toList(); - Bind jarBind = new Bind(request.jarPath(), new Volume("/app/app.jar"), AccessMode.ro); - HostConfig hostConfig = HostConfig.newHostConfig() .withMemory(request.memoryLimitBytes()) .withMemorySwap(request.memoryLimitBytes()) .withCpuShares(request.cpuShares()) .withNetworkMode(request.network()) - .withBinds(jarBind) .withRestartPolicy(RestartPolicy.onFailureRestart(request.restartPolicyMaxRetries())); + // JAR mounting: volume mount (Docker-in-Docker) or bind mount (host path) + if (request.jarVolumeName() != null && !request.jarVolumeName().isBlank()) { + // Mount the named volume at the jar storage base path + Bind volumeBind = new Bind(request.jarVolumeName(), new Volume(request.jarVolumeMountPath()), AccessMode.ro); + hostConfig.withBinds(volumeBind); + } else { + Bind jarBind = new Bind(request.jarPath(), new Volume("/app/app.jar"), AccessMode.ro); + hostConfig.withBinds(jarBind); + } + if (request.memoryReserveBytes() != null) { hostConfig.withMemoryReservation(request.memoryReserveBytes()); } @@ -77,6 +84,12 @@ public class DockerRuntimeOrchestrator implements RuntimeOrchestrator { hostConfig.withCpuQuota(request.cpuQuota()); } + // When using volume mount, the JAR is at the original path, not /app/app.jar + if (request.jarVolumeName() != null && !request.jarVolumeName().isBlank()) { + envList = new ArrayList<>(envList); + envList.add("CAMELEER_APP_JAR=" + request.jarPath()); + } + var createCmd = dockerClient.createContainerCmd(request.baseImage()) .withName(request.containerName()) .withEnv(envList) @@ -90,6 +103,22 @@ public class DockerRuntimeOrchestrator implements RuntimeOrchestrator { .withRetries(3) .withStartPeriod(30_000_000_000L)); + // Override entrypoint to use the volume-mounted JAR path + if (request.jarVolumeName() != null && !request.jarVolumeName().isBlank()) { + createCmd.withEntrypoint("sh", "-c", + "exec java -javaagent:/app/agent.jar " + + "-Dcameleer.export.type=${CAMELEER_EXPORT_TYPE:-HTTP} " + + "-Dcameleer.export.endpoint=${CAMELEER_EXPORT_ENDPOINT} " + + "-Dcameleer.agent.name=${HOSTNAME} " + + "-Dcameleer.agent.application=${CAMELEER_APPLICATION_ID:-default} " + + "-Dcameleer.agent.environment=${CAMELEER_ENVIRONMENT_ID:-default} " + + "-Dcameleer.routeControl.enabled=${CAMELEER_ROUTE_CONTROL_ENABLED:-false} " + + "-Dcameleer.replay.enabled=${CAMELEER_REPLAY_ENABLED:-false} " + + "-Dcameleer.health.enabled=true " + + "-Dcameleer.health.port=9464 " + + "-jar ${CAMELEER_APP_JAR}"); + } + if (request.exposedPorts() != null && !request.exposedPorts().isEmpty()) { var ports = request.exposedPorts().stream() .map(p -> com.github.dockerjava.api.model.ExposedPort.tcp(p)) diff --git a/cameleer3-server-app/src/main/resources/application.yml b/cameleer3-server-app/src/main/resources/application.yml index 302ef4ac..1dbc2d9e 100644 --- a/cameleer3-server-app/src/main/resources/application.yml +++ b/cameleer3-server-app/src/main/resources/application.yml @@ -52,6 +52,7 @@ cameleer: routing-mode: ${CAMELEER_ROUTING_MODE:path} routing-domain: ${CAMELEER_ROUTING_DOMAIN:localhost} server-url: ${CAMELEER_SERVER_URL:} + jar-docker-volume: ${CAMELEER_JAR_DOCKER_VOLUME:} body-size-limit: ${CAMELEER_BODY_SIZE_LIMIT:16384} indexer: debounce-ms: ${CAMELEER_INDEXER_DEBOUNCE_MS:2000} diff --git a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/ContainerRequest.java b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/ContainerRequest.java index d29079d0..71c207bf 100644 --- a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/ContainerRequest.java +++ b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/ContainerRequest.java @@ -7,6 +7,8 @@ public record ContainerRequest( String containerName, String baseImage, String jarPath, + String jarVolumeName, + String jarVolumeMountPath, String network, List additionalNetworks, Map envVars,