Deploy to local Docker Desktop with a ComposeHost target

Audience: operators deploying a Stella Ops release onto the same Windows + Docker Desktop host that runs the enrolled agent-core container.

Use this path when the workload belongs on the local Docker Desktop engine. The deployment does not use SSH, WinRM, or an exposed Docker TCP daemon. agent-core runs docker compose through its mounted /var/run/docker.sock.

Prerequisites

Run the commands from the repository root in PowerShell 7. They use the public Release Orchestrator API through the gateway.

$baseUrl = "https://stella-ops.local"
$tenant = "internal"
$headers = @{
    Authorization = "Bearer $token"
    "X-Stella-Tenant" = $tenant
}

1. Confirm the local agent

List active agents and select the agent running on this Docker Desktop host:

$agents = Invoke-RestMethod `
    -Uri "$baseUrl/api/v1/release-orchestrator/agents?status=Active" `
    -Headers $headers `
    -SkipCertificateCheck

$agents | Select-Object id, name, status, capabilities, lastHeartbeatAt

The selected row must be Active, have a recent lastHeartbeatAt, and include both Docker and Compose in capabilities.

The repository Compose profile gives agent-core local-engine access by mounting the configured Docker socket at /var/run/docker.sock; its normal endpoint is unix:///var/run/docker.sock. Do not expose Docker Desktop on tcp://localhost:2375 for this workflow.

$agentId = "<active-local-agent-guid>"

If no suitable agent exists, stop and enroll or repair it. Do not substitute an agent on another host: a local ComposeHost deployment always lands on the Docker engine visible to the assigned agent.

2. Resolve the environment

$environmentName = "partner-local"
$environment = Invoke-RestMethod `
    -Uri "$baseUrl/api/v1/release-orchestrator/environments/by-name/$environmentName" `
    -Headers $headers `
    -SkipCertificateCheck

$environmentId = $environment.id

3. Create the local ComposeHost target

The absence of sshUsername is the local-execution switch. Do not add sshUsername, sshPort, or sshPrivateKeySecretRef.

$targetName = "docker-desktop-local"
$connectionConfig = @{
    type = "compose_host"
    host = "host.docker.internal"
    port = 2375
    useRemoteDockerEndpoint = $false
    useTls = $false
    composeProjectPath = "/var/lib/stella-agent/deployments"
    composeFile = "docker-compose.yml"
}
$createBody = @{
    name = $targetName
    displayName = "Local Docker Desktop"
    type = "ComposeHost"
    agentId = $agentId
    connectionConfig = $connectionConfig
} | ConvertTo-Json -Depth 5

$target = Invoke-RestMethod `
    -Method Post `
    -Uri "$baseUrl/api/v1/release-orchestrator/environments/$environmentId/targets" `
    -Headers $headers `
    -ContentType "application/json" `
    -Body $createBody `
    -SkipCertificateCheck

$targetId = $target.id

host is retained as target/probe metadata. It does not become DOCKER_HOST while useRemoteDockerEndpoint=false. The orchestrator injects DOCKER_HOST only for an SSH-mode ComposeHost (ssh://...) or an explicitly remote TCP target, so this target uses the agent’s configured local Docker socket. composeProjectPath is a Linux path visible inside agent-core, not a Windows C:\... path.

If the target already exists, read it by natural key and update it instead of creating a duplicate:

$target = Invoke-RestMethod `
    -Uri "$baseUrl/api/v1/release-orchestrator/environments/$environmentId/targets/by-name/$targetName" `
    -Headers $headers `
    -SkipCertificateCheck

$targetId = $target.id
$updateBody = @{
    displayName = "Local Docker Desktop"
    connectionConfig = $connectionConfig
    agentId = $agentId
} | ConvertTo-Json -Depth 5

Invoke-RestMethod `
    -Method Put `
    -Uri "$baseUrl/api/v1/release-orchestrator/targets/$targetId" `
    -Headers $headers `
    -ContentType "application/json" `
    -Body $updateBody `
    -SkipCertificateCheck

4. Check target readiness

$check = Invoke-RestMethod `
    -Method Post `
    -Uri "$baseUrl/api/v1/release-orchestrator/targets/$targetId/health-check" `
    -Headers $headers `
    -SkipCertificateCheck

$check

Expected: success is true. For an agent-backed ComposeHost this check proves that the assigned agent is active, its heartbeat is fresh, and it advertises Docker + Compose. The deployment runtime preflight separately probes the local Docker daemon, Compose availability, work directory, image platform, disk, and configured registry mode.

5. Deploy the release

Confirm that the release targets $environmentId, then use the normal governed deploy action:

stella release show <release-id>
stella release deploy <release-id> --reason "Deploy to local Docker Desktop ComposeHost" --json

The deploy can still stop at evidence, approval, policy, digest, preflight, or access gates. Resolve the reported gate; do not weaken it merely to make the local path run.

For a component-level post-deploy probe, prefer an explicit healthProbe.http.url or healthProbe.host that is reachable from the Release Orchestrator container. The target’s host value is only the default probe host; it does not control which Docker engine executes the deployment.

6. Verify independently

Do not treat an accepted HTTP request or a healthy agent as proof that the workload landed. Wait for the deployment to reach succeeded, then independently query Docker Desktop:

$deploymentId = "<deployment-id-from-deploy-response>"

$deployment = Invoke-RestMethod `
    -Uri "$baseUrl/api/v1/release-orchestrator/deployments/$deploymentId" `
    -Headers $headers `
    -SkipCertificateCheck

$deployment.status
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"

Required evidence:

Failure modes

Health check says the assigned agent is missing or stale

Verify agent-core enrollment and heartbeat first. Recreating the container must preserve the identity from devops/compose/env/agent-core.local.env; do not register a replacement identity just to clear a stale target assignment.

docker compose cannot connect to the daemon

Check Docker Desktop and the agent-core socket contract. The Compose service mounts ${AGENT_CORE_DOCKER_SOCKET:-/var/run/docker.sock} to /var/run/docker.sock and normally configures Agent__DockerEndpoint=unix:///var/run/docker.sock. Do not fix this by enabling unauthenticated TCP port 2375.

Deployment ran on the wrong engine

The assigned agent defines the local engine. Confirm the target’s agentId. Also confirm that sshUsername is absent; if it is present, the target is docker-over-SSH and intentionally remote.

Work-directory preflight fails

Use a Linux container path writable by agent-core, normally /var/lib/stella-agent/deployments. Do not supply a Windows filesystem path.

Application is running but post-deploy probe fails

Inspect the explicit healthProbe.* configuration and Docker networking. Deployment-engine success and application reachability are separate assertions; point the probe at an address reachable from the orchestrator rather than assuming its localhost is the Windows host.

Contract references