# Agent-Based Deployment > Agent-based deployment using Docker and Compose agents for executing tasks on targets. **Status:** Planned (not yet implemented) **Source:** [Architecture Advisory Section 10.3](../../../product/advisories/09-Jan-2026%20-%20Stella%20Ops%20Orchestrator%20Architecture.md) **Related Modules:** [Agents Module](../modules/agents.md), [Deploy Orchestrator](../modules/deploy-orchestrator.md) **Sprints:** [108_002 Docker Agent](../../../../implplan/SPRINT_20260110_108_002_AGENTS_docker.md), [108_003 Compose Agent](../../../../implplan/SPRINT_20260110_108_003_AGENTS_compose.md) ## Overview Agent-based deployment uses lightweight agents installed on target hosts to execute deployment tasks. Agents communicate with the orchestrator over mTLS and receive tasks through heartbeat polling or WebSocket streams. --- ## Agent Task Protocol ### Task Payload Structure ```typescript // Task assignment (Core -> Agent) interface AgentTask { id: UUID; type: TaskType; targetId: UUID; payload: TaskPayload; credentials: EncryptedCredentials; timeout: number; priority: TaskPriority; idempotencyKey: string; assignedAt: DateTime; expiresAt: DateTime; } type TaskType = | "deploy" | "rollback" | "health-check" | "inspect" | "execute-command" | "upload-files" | "write-sticker" | "read-sticker"; interface DeployTaskPayload { image: string; digest: string; config: DeployConfig; artifacts: ArtifactReference[]; previousDigest?: string; hooks: { preDeploy?: HookConfig; postDeploy?: HookConfig; }; } ``` ### Task Result Structure ```typescript // Task result (Agent -> Core) interface TaskResult { taskId: UUID; success: boolean; startedAt: DateTime; completedAt: DateTime; // Success details outputs?: Record; artifacts?: ArtifactReference[]; // Failure details error?: string; errorType?: string; retriable?: boolean; // Logs logs: string; // Metrics metrics: { pullDurationMs?: number; deployDurationMs?: number; healthCheckDurationMs?: number; }; } ``` --- ## Docker Agent Implementation The Docker agent deploys single containers to Docker hosts with digest verification. ### Docker Agent Capabilities - Pull images with digest verification - Create and start containers - Stop and remove containers - Health check monitoring - Version sticker management - Rollback to previous container ### Deploy Task Flow ```typescript class DockerAgent implements TargetExecutor { private docker: Docker; async deploy(task: DeployTaskPayload): Promise { const { image, digest, config, previousDigest } = task; const containerName = config.containerName; // 1. Pull image and verify digest this.log(`Pulling image ${image}@${digest}`); await this.docker.pull(image, { digest }); const pulledDigest = await this.getImageDigest(image); if (pulledDigest !== digest) { throw new DigestMismatchError( `Expected digest ${digest}, got ${pulledDigest}. Possible tampering detected.` ); } // 2. Run pre-deploy hook if (task.hooks?.preDeploy) { await this.runHook(task.hooks.preDeploy, "pre-deploy"); } // 3. Stop and rename existing container const existingContainer = await this.findContainer(containerName); if (existingContainer) { this.log(`Stopping existing container ${containerName}`); await existingContainer.stop({ t: 10 }); await existingContainer.rename(`${containerName}-previous-${Date.now()}`); } // 4. Create new container this.log(`Creating container ${containerName} from ${image}@${digest}`); const container = await this.docker.createContainer({ name: containerName, Image: `${image}@${digest}`, // Always use digest, not tag Env: this.buildEnvVars(config.environment), HostConfig: { PortBindings: this.buildPortBindings(config.ports), Binds: this.buildBindMounts(config.volumes), RestartPolicy: { Name: config.restartPolicy || "unless-stopped" }, Memory: config.memoryLimit, CpuQuota: config.cpuLimit, }, Labels: { "stella.release.id": config.releaseId, "stella.release.name": config.releaseName, "stella.digest": digest, "stella.deployed.at": new Date().toISOString(), }, }); // 5. Start container this.log(`Starting container ${containerName}`); await container.start(); // 6. Wait for container to be healthy (if health check configured) if (config.healthCheck) { this.log(`Waiting for container health check`); const healthy = await this.waitForHealthy(container, config.healthCheck.timeout); if (!healthy) { // Rollback to previous container await this.rollbackContainer(containerName, existingContainer); throw new HealthCheckFailedError(`Container ${containerName} failed health check`); } } // 7. Run post-deploy hook if (task.hooks?.postDeploy) { await this.runHook(task.hooks.postDeploy, "post-deploy"); } // 8. Cleanup previous container if (existingContainer && config.cleanupPrevious !== false) { this.log(`Removing previous container`); await existingContainer.remove({ force: true }); } return { success: true, containerId: container.id, previousDigest: previousDigest, logs: this.getLogs(), durationMs: this.getDuration(), }; } } ``` ### Rollback Implementation ```typescript async rollback(task: RollbackTaskPayload): Promise { const { containerName, targetDigest } = task; // Find previous container or use specified digest if (targetDigest) { // Deploy specific digest return this.deploy({ ...task, digest: targetDigest, }); } // Find and restore previous container const previousContainer = await this.findContainer(`${containerName}-previous-*`); if (!previousContainer) { throw new RollbackError(`No previous container found for ${containerName}`); } // Stop current, rename, start previous const currentContainer = await this.findContainer(containerName); if (currentContainer) { await currentContainer.stop({ t: 10 }); await currentContainer.rename(`${containerName}-failed-${Date.now()}`); } await previousContainer.rename(containerName); await previousContainer.start(); return { success: true, containerId: previousContainer.id, logs: this.getLogs(), durationMs: this.getDuration(), }; } ``` ### Version Sticker Management ```typescript async writeSticker(sticker: VersionSticker): Promise { const stickerPath = this.config.stickerPath || "/var/stella/version.json"; const stickerContent = JSON.stringify(sticker, null, 2); // Write to host filesystem or container volume if (this.config.stickerLocation === "volume") { // Write to shared volume await this.docker.run("alpine", [ "sh", "-c", `echo '${stickerContent}' > ${stickerPath}` ], { HostConfig: { Binds: [`${this.config.stickerVolume}:/var/stella`] } }); } else { // Write directly to host fs.writeFileSync(stickerPath, stickerContent); } } ``` --- ## Compose Agent Implementation The Compose agent deploys multi-container applications defined in Docker Compose files. ### Compose Agent Capabilities - Pull images for all services - Verify digests for all services - Deploy using compose lock files - Health check all services - Rollback to previous deployment - Version sticker management ### Deploy Task Flow ```typescript class ComposeAgent implements TargetExecutor { async deploy(task: DeployTaskPayload): Promise { const { artifacts, config } = task; const deployDir = config.deploymentDirectory; // 1. Write compose lock file const composeLock = artifacts.find(a => a.type === "compose_lock"); const composeContent = await this.fetchArtifact(composeLock); const composePath = path.join(deployDir, "compose.stella.lock.yml"); await fs.writeFile(composePath, composeContent); // 2. Write any additional config files for (const artifact of artifacts.filter(a => a.type === "config")) { const content = await this.fetchArtifact(artifact); await fs.writeFile(path.join(deployDir, artifact.name), content); } // 3. Run pre-deploy hook if (task.hooks?.preDeploy) { await this.runHook(task.hooks.preDeploy, deployDir); } // 4. Pull images this.log("Pulling images..."); const pullResult = await this.runCompose(deployDir, ["pull"]); if (!pullResult.success) { throw new Error(`Failed to pull images: ${pullResult.stderr}`); } // 5. Verify digests await this.verifyDigests(composePath, config.expectedDigests); // 6. Deploy this.log("Deploying services..."); const upResult = await this.runCompose(deployDir, [ "up", "-d", "--remove-orphans", "--force-recreate" ]); if (!upResult.success) { throw new Error(`Failed to deploy: ${upResult.stderr}`); } // 7. Wait for services to be healthy if (config.healthCheck) { this.log("Waiting for services to be healthy..."); const healthy = await this.waitForServicesHealthy( deployDir, config.healthCheck.timeout ); if (!healthy) { // Rollback await this.rollbackToBackup(deployDir); throw new HealthCheckFailedError("Services failed health check"); } } // 8. Run post-deploy hook if (task.hooks?.postDeploy) { await this.runHook(task.hooks.postDeploy, deployDir); } // 9. Write version sticker await this.writeSticker(config.sticker, deployDir); return { success: true, logs: this.getLogs(), durationMs: this.getDuration(), }; } } ``` ### Digest Verification ```typescript private async verifyDigests( composePath: string, expectedDigests: Record ): Promise { const composeContent = yaml.parse(await fs.readFile(composePath, "utf-8")); for (const [service, expectedDigest] of Object.entries(expectedDigests)) { const serviceConfig = composeContent.services[service]; if (!serviceConfig) { throw new Error(`Service ${service} not found in compose file`); } const image = serviceConfig.image; if (!image.includes("@sha256:")) { throw new Error(`Service ${service} image not pinned to digest: ${image}`); } const actualDigest = image.split("@")[1]; if (actualDigest !== expectedDigest) { throw new DigestMismatchError( `Service ${service}: expected ${expectedDigest}, got ${actualDigest}` ); } } } ``` --- ## Security Considerations 1. **Digest Verification:** All deployments verify image digests before execution 2. **Credential Encryption:** Credentials are encrypted in transit and at rest 3. **mTLS Communication:** All agent-server communication uses mutual TLS 4. **Hook Sandboxing:** Pre/post-deploy hooks run in isolated environments 5. **Audit Logging:** All deployment actions are logged with actor context --- ## See Also - [Agents Module](../modules/agents.md) - [Agent Security](../security/agent-security.md) - [Deployment Orchestrator](../modules/deploy-orchestrator.md) - [Agentless Deployment](agentless.md)