Agent host bootstrap

Use this procedure to place a Stella Ops deployment agent (stellaops/agent-core) beside the control plane or directly on a Docker host. It covers target-host enrollment, not a redeploy of a Stella Ops service. The API surface it uses is documented in Release Orchestrator agents API; the fleet commands are in the agent operations quickstart.

Starting or enrolling an agent changes the live deployment executor fleet. Confirm the target, tenant, agent name, and operator intent before creating a token or starting a container. A token is one-use; the returned private key is a secret.

Choose the topology

Central agent plus SSH (default)

Run one stellaops/agent-core beside Release Orchestrator and let it reach multiple targets over SSH. This is the proven default when the control-plane network can initiate SSH to every host.

The central agent normally advertises docker,compose,ssh,winrm and may advertise ansible when the image includes that capability. Each target host needs:

For the local compose control plane, the safe launcher is:

cd devops\compose
.\scripts\launch-agent-core.ps1

It reuses existing enrollment material, or calls agent-bootstrap.ps1 to create and immediately consume a real one-time registration token, writes the issued mTLS certificate/key to ignored local paths, starts the compose service, and waits for its heartbeat. Do not run it merely to inspect the configuration; use -DryRun for that. See devops/compose/agent-bootstrap.md and docs/runbooks/general/local-vm-targets.md for the compose and lab-target details.

Per-host agent

Use one agent per target when the host should execute Docker/Compose locally or the network cannot support central-agent-to-host SSH. The agent needs outbound HTTPS reachability to Release Orchestrator’s agent-runtime listener and access to the local Docker socket.

Use a digest-pinned image for a released installation. The :dev examples below are only for an image built from the current working tree.

Move the OCI image to the host

On the connected build/operator machine:

# Build from this working tree when a suitable image is not already present.
IMAGE_TAG=dev bash devops/docker/build-service-publish.sh agent-core

docker image inspect stellaops/agent-core:dev --format '{{.Id}}'
docker save --output stellaops-agent-core.tar stellaops/agent-core:dev
scp stellaops-agent-core.tar operator@target-host:/var/tmp/

On the target host:

docker load --input /var/tmp/stellaops-agent-core.tar
docker image inspect stellaops/agent-core:dev --format '{{.Id}}'
rm /var/tmp/stellaops-agent-core.tar

For a product release, record the expected sha256: digest before transfer and verify the loaded image matches it. Do not replace the digest with a mutable tag in a release bundle.

Enroll once and save the mTLS material

The supported API/script flow is:

  1. POST /api/v1/release-orchestrator/agents/registration-tokens creates a one-time token.
  2. POST /api/v1/release-orchestrator/agents consumes that token and returns the agent id, certificate PEM, and private-key PEM.
  3. The agent starts with that identity and polls the HTTPS agent-runtime endpoint using mTLS.

The compose bootstrap scripts implement all three steps and avoid persisting the plaintext token. Copy the appropriate script to the host together with its compose helper, or run it from a checked out offline kit:

export AGENT_CORE_BOOTSTRAP_ORCHESTRATOR_URL='https://stella.example'
export AGENT_CORE_ORCHESTRATOR_URL='https://release-orchestrator.example:8443'
export AGENT_CORE_AGENT_NAME='target-host-01'
export AGENT_CORE_CAPABILITIES='docker,compose'
export STELLAOPS_TENANT='production'
export STELLAOPS_BEARER_TOKEN='<operator token with orch:operate and orch:read>'

cd devops/compose
./scripts/agent-bootstrap.sh --no-compose-up --strict-orchestrator-certificate

PowerShell uses the equivalent parameters:

$env:STELLAOPS_BEARER_TOKEN = '<operator token with orch:operate and orch:read>'
.\devops\compose\scripts\agent-bootstrap.ps1 `
  -BootstrapOrchestratorUrl 'https://stella.example' `
  -AgentOrchestratorUrl 'https://release-orchestrator.example:8443' `
  -AgentName 'target-host-01' `
  -Capabilities 'docker,compose' `
  -Tenant 'production' `
  -NoComposeUp `
  -StrictOrchestratorCertificate

Both scripts write env/agent-core.local.env and offline/agent-core/{agent.crt,agent.key}. Those files are enrollment material: copy them only over an authenticated channel, restrict the private key to the operator account, and never commit them. The registration token has already been consumed when the script returns.

stella agent token create --name <name> --capability docker --capability compose is the real, wired lower-level token command for an external installer or a manual API flow. It prints the plaintext exactly once. The bootstrap scripts normally mint and consume their own token, so do not create an extra CLI token when using those scripts.

Run the per-host container

The bootstrap env file contains compose interpolation names as well as the canonical agent id/name. Return to the repository/offline-kit root, read the generated values, then run the image with the issued certificate directory and Docker socket mounted:

cd ../..

agent_id="$(sed -n 's/^AGENT_CORE_AGENT_ID=//p' devops/compose/env/agent-core.local.env)"
agent_name="$(sed -n 's/^AGENT_CORE_AGENT_NAME=//p' devops/compose/env/agent-core.local.env)"
orchestrator_url="$(sed -n 's/^AGENT_CORE_ORCHESTRATOR_URL=//p' devops/compose/env/agent-core.local.env)"
capabilities="$(sed -n 's/^AGENT_CORE_CAPABILITIES=//p' devops/compose/env/agent-core.local.env)"

docker run --detach \
  --name stellaops-agent-core-target-host-01 \
  --restart unless-stopped \
  --group-add "$(stat -c '%g' /var/run/docker.sock)" \
  --volume /var/run/docker.sock:/var/run/docker.sock \
  --volume "$PWD/devops/compose/offline/agent-core:/app/etc/agent:rw" \
  --volume stella-agent-target-host-01:/var/lib/stella-agent/deployments \
  --env "Agent__AgentId=${agent_id}" \
  --env "Agent__AgentName=${agent_name}" \
  --env "Agent__OrchestratorUrl=${orchestrator_url}" \
  --env 'Agent__CertificatePath=/app/etc/agent/agent.crt' \
  --env 'Agent__PrivateKeyPath=/app/etc/agent/agent.key' \
  --env 'Agent__CaCertificatePath=/etc/ssl/certs/ca-certificates.crt' \
  --env 'Agent__AllowUntrustedOrchestratorCertificate=false' \
  --env "Agent__Capabilities=${capabilities}" \
  --env 'Agent__DockerEndpoint=unix:///var/run/docker.sock' \
  --env 'Agent__ComposeDeploymentRoot=/var/lib/stella-agent/deployments' \
  stellaops/agent-core:dev

Production must trust the server certificate. If the orchestrator uses a private CA that is not in the image trust store, mount its public CA PEM read-only and point Agent__CaCertificatePath at that file. Agent__AllowUntrustedOrchestratorCertificate=true is a local-development escape hatch, not a production bootstrap method. Docker-socket access is root-equivalent; run this agent only on a host whose deployment authority matches the Stella operator’s authority.

Verify mTLS and heartbeat

First inspect the process, then verify the control-plane record. Registration alone is not success:

docker ps --filter name=stellaops-agent-core-target-host-01
docker logs --tail 100 stellaops-agent-core-target-host-01

stella agent list --status Active
stella agent show "$agent_id"

The record must show status=Active, a recent lastHeartbeatAt, the expected certificate thumbprint/expiry, and the capabilities needed by the assigned target. The equivalent API check is GET /api/v1/release-orchestrator/agents with orch:read in the same tenant. A created agent row without a fresh heartbeat is not an available executor.

Air-gap boundary

docker save/scp/docker load moves the image without a registry, but current enrollment is not fully disconnected. Creating and consuming the one-time token and the first mTLS poll still require network reachability from the host to Release Orchestrator. For a sealed host with no enrollment route, stage the image now and defer registration until a controlled link is available. Pre-issued offline agent certificates are not an implemented fallback; do not fabricate an agent id or copy another agent’s certificate.

Definition of done