Docker Compute Driver
Contents
Overview
This implementation follows the ComputeDriver contract.
DockerComputeDriver is the default Compute Driver for local control-plane
development. Compose runs PostgreSQL, migrations, the initializer, the OCC API,
and the worker on Docker Engine or Podman. The API uses filesystem
Configuration, and the Driver creates isolated Namespace networks.
This Driver cannot deploy a new Agent under the current authentication
contract. It rejects every harnessAuth binding at deployment. Its code
contains embedded OpenClaw and dedicated Codex container topologies, but those
components do not provide a currently usable Agent or model-response workflow.
Use Kubernetes to deploy Agents locally with OCC-managed credentials, or SSH for embedded OpenClaw with operator-managed host credentials. Docker Compute cannot be selected for production.
Lifecycle
Namespace lifecycle
ensureNamespace(namespace) creates or verifies one Docker network for the
exact Namespace. The network is labeled with the driver, Installation,
Namespace, and ownership metadata. The method does not start a gateway or Agent
workload.
deleteNamespace(namespace) removes only driver-owned containers and the
driver-owned network for that exact Namespace. It refuses to adopt or delete a
foreign network with the same name but different ownership labels. If the network
was removed externally, deletion still removes remaining owned containers and
workspace volumes before reporting success.
Each Namespace maps to one Docker network. Runtime containers for one Namespace join only that network; they do not join the control-plane management network or another Namespace network.
AgentRevision lifecycle
prepareRevision(revision) validates the immutable Namespace, Agent, and AgentRevision,
Harness identity, execution mode, and selected Compute implementation before it
starts containers. The code implements the topologies below, but it rejects
the model-authentication bindings required for a newly deployed Agent:
openclawwithembeddedstarts one Agent-owned OpenClaw gateway container. That container also runs the embedded Harness and receives only that Agent's model credential.codexwithdedicatedstarts one Agent-owned OpenClaw gateway container and one Codex app-server container for that Agent revision. The gateway connects to Codex through authenticatedAPP_SERVER_URLandAPP_SERVER_TOKENtransport. Only the Codex container receives the model credential.
When recovery reuses a healthy Codex container, the driver reads its existing transport token after verifying exact ownership and supplies that token to the gateway. A surviving Codex container without a token fails closed. If Codex is recreated, the driver replaces a gateway whose token no longer matches; a healthy matching pair is reused.
The driver uses the same runtime entrypoint contract as production. It does not build, download, or publish runtime images. Missing image references, unsupported Harness combinations, unavailable container-engine access, failed transport authentication, or unready containers fail closed.
To upgrade OpenClaw, deploy the Agent with a different gateway image. As on
Kubernetes, the gateway starts OpenClaw and its state migration with
OPENCLAW_NO_AUTO_UPDATE=1: OpenClaw does not check for updates, send its
installation count request, or install updates.
Container image references must already resolve in the selected engine. The development Driver accepts tags as well as digests; production Kubernetes digest requirements do not imply that the local engine enforces immutable images.
Both topologies keep one gateway container per Agent, so a new version
replaces the active version's gateway. The Driver therefore declares
exclusive replacement
(requiresStoppedPredecessors) for every version: the worker stops all earlier
versions before preparing the new one, and stops a refused new version, so
the Agent is unavailable until a later version deploys. Redeployment interrupts
service and has no automatic rollback.
retireRevision(revision) removes only the runtime with the same Agent revision
ID. It preserves another Agent's containers and preserves a gateway still required by
a replacement version for the same Agent.
stopRevision(revision) removes the exact dedicated Agent container and a
gateway only when it still serves that version. Embedded execution removes the
combined gateway container. Repeating stop is safe; Docker stop does not retire
the persisted AgentRevision or remove credentials outside the containers. The
Driver retains the Agent-owned state and workspace volumes used for initial
workspace setup across stop and version replacement. Without initial workspace
inputs, writable container tmpfs remains ephemeral.
For a stateful embedded Gateway, the Driver can recover after an unclean stop if it observed the old container's private Linux PID namespace while it ran. Docker Engine must report that the same container instance exited, and the replacement must use the same local state volume. OpenClaw checks every recorded holder against that namespace before releasing its locks. If the Driver lost its observation or the holders do not match, follow operator recovery.
The Driver kills an observed embedded Gateway to record its exit. It stops every other container before removal: Docker sends SIGTERM and sends SIGKILL after 10 seconds. A Gateway that exits on SIGTERM releases its OpenClaw state lock, so its replacement needs no recovery. This includes a Gateway that the Driver did not observe, for example after a controller restart. If the stop fails, the Driver still removes the container and reports the stop failure.
State on that volume outlives runtime image upgrades. Before OpenClaw starts,
the gateway runs the same state migration as a
Kubernetes Gateway:
openclaw doctor --fix --non-interactive once, with the configuration
read-only, when its shared state or an agent database uses an older schema. If schemas remain
outdated or maintenance is refused, the container logs Gateway state migration failed and exits
without starting OpenClaw, and the deployment fails as an unready container.
A private pending marker keeps refused or interrupted maintenance eligible for retry
after restart, even if schemas advanced.
The Driver then removes the container, so Doctor's output survives only
through the Docker logging address. A slow migration can also outlast the
Driver's 120-second readiness budget; deploying again retries it.
Initial workspace storage
When an Agent has initial workspace contents,
the Driver creates two named volumes owned by its exact Namespace and Agent:
one for native state at /home/node/.openclaw and one for workspace files. The
same workspace volume is mounted at both /home/node/.openclaw/workspace and
/home/node/workspace, so the native default and dedicated Harness directory
refer to the same files. Initial setup accepts either path; other workspace
locations and disabled native bootstrap are rejected.
A separate setup container runs native initialization and writes the supplied
files and completion marker before execution starts. Later startup checks the
marker without replaying the original text. Missing initialized storage blocks
startup. Native setup holds the OpenClaw state lock on the state volume.
Preparation never stops a running setup container, because a killed setup
leaves its lock. A setup still running after 120 seconds keeps the deployment
pending within its convergence deadline. A failed Docker request while setup
starts or during that wait fails that attempt; if setup is still running, the
next attempt reports the deployment pending. Once setup exits, the next attempt
removes the container and continues. Stop, container replacement, and
redeployment retain both volumes; Agent or Namespace
deletion removes them after the owned runtimes are removed.
These Driver-created volumes are separate from Compose's control-plane volumes
and are not removed by compose down --volumes.
Agents without initial inputs retain the existing tmpfs storage behavior. Workspace setup owns the initialization and retry contract. The Agent admission limitation above still applies to these Driver capabilities.
Configuration
The environment factory reads these settings at process startup. Restart API and worker after changing them. Docker is the default development selection, not a bundled YAML selection.
| Setting | Type | Requirement | Value, default, and constraints |
|---|---|---|---|
OCC_DOCKER_GATEWAY_IMAGE |
string | Optional | Gateway image; falls back to OCC_DOCKER_RUNTIME_IMAGE. One must be set. |
OCC_DOCKER_AGENT_IMAGE |
string | Optional | Codex image; falls back to OCC_DOCKER_RUNTIME_IMAGE. One must be set. |
OCC_DOCKER_RUNTIME_IMAGE |
string | Optional | Shared image containing both entrypoints; no default. |
OCC_DOCKER_LOGGING_ADDRESS |
string | Optional | Loopback Fluentd host and port, 1–65535. Omission disables forwarding. |
For example, with an image already loaded in the engine:
OCC_DEVELOPMENT_COMPUTE_DRIVER=docker OCC_DOCKER_RUNTIME_IMAGE=oce-runtime:local scripts/dev-upDevelopment configuration and persistence
The deployment guide owns the Compose startup procedure. Settings owns the environment-variable reference; the development flow traces API, worker, and container startup.
Compose publishes the OCC API on host
127.0.0.1:${OPENCLAW_DEV_PORT:-3000}. PostgreSQL remains on a loopback host
port. The API and worker share the same application-role database connection
and select compute-docker-development with implementation docker-local
unless OCC_CONFIG_PATH explicitly selects another trusted Driver set.
Compose runs the shared initializer after migration and before the API or worker. Fresh setup creates the configured human administrator, service administrator, and singleton Installation; existing state is retained. Only the initializer mounts the protected initial-key output volume. The authentication reference owns credential creation and recovery; the Compose development flow traces ordering.
The cleanup command printed by dev-up keeps occ_postgres_data,
occ_configuration_data, and the bootstrap-only occ_bootstrap_data volume.
Adding --volumes deletes all three, including the initial service-key JSON.
Gateway authentication
The native listener must serve HTTP for readiness and private traffic.
gateway.tls.enabled must be omitted or false; native TLS enablement is refused
before backend work. External proxy TLS is independent of this listener setting.
A plugins.entries.codex.config.appServer.approvalPolicy other than never,
on-request or on-failure is refused the same way. So are listeners the
published port cannot reach while the healthcheck on loopback passes: a
gateway.bind other than auto, lan or custom, custom unless
gateway.customBindHost is 0.0.0.0, and any gateway.tailscale.mode but off.
The container implementation renders gateway.auth.mode: password when native
Configuration omits the mode. Explicit password and trusted-proxy are supported.
Only supported authentication fields and modes are admitted: deployment refuses
another mode, such as token or none, or an unsupported field, such as
gateway.auth.token, with 409 before it stops the running revision. These rules
do not remove the harness-authentication admission limit above.
With password mode, omitting gateway.auth.password selects the managed
OPENCLAW_GATEWAY_PASSWORD reference. An explicit reference to that variable also
selects a managed password, including optional loopback access with trusted proxy.
For either mode, a new container receives a new managed password; a reused healthy
container keeps its value. Other explicit password settings are preserved and
receive no Driver-generated password. Configuration API secret-reference rules
still apply: save and deployment refuse a password SecretRef that OpenClaw cannot
read, such as one without a provider. Trusted proxy without a password reference receives no managed
password. The dedicated Codex APP_SERVER_TOKEN remains a separate transport
credential with its existing recovery checks.
Limits
Requirements
- Docker Engine with Docker Compose, or Podman with
podman-composeandyqv4. - The full Compose development stack, not a process-local controller.
- Existing production-equivalent runtime images:
OCC_DOCKER_GATEWAY_IMAGEandOCC_DOCKER_AGENT_IMAGE, or one sharedOCC_DOCKER_RUNTIME_IMAGEcontaining both runtime entrypoints. - Explicit
OCC_DEVELOPMENT_TRUSTED_BRIDGE_CIDRwhen the Compose bridge range must be admitted as local development traffic. - The controller-only
OCC_DEVELOPMENT_CONFIGURATION_ROOT, which Compose sets to/app/.development/configurationsfrom theocc_configuration_datanamed volume.
scripts/dev-up is the supported Podman entry point. It auto-detects Podman
without a docker alias, requires and pins the standalone podman-compose
provider, and occ dev down stops the stack with that same provider. It mounts
the reported API socket through compose.podman.yaml, and preserves the
driver's existing /var/run/docker.sock contract inside the worker. The
Docker Compute worker disables SELinux process labeling because relabeling the
host engine socket could disrupt the engine; the API, database, initializer,
and migration services remain confined. Rootful Podman on Linux must expose its
API socket (for example, systemctl enable --now podman.socket); the Compose
provider reaches Podman through the DOCKER_HOST endpoint startup pins. The prior verified
baseline is Podman client 6.1.0, server 5.7.1, and podman-compose 1.6.0. Prior
Podman runtime proof covered control-plane startup, worker API preflight,
authenticated Installation access, isolated Namespace networks, embedded and
dedicated model responses, recovery after worker interruption, and exact test
cleanup. That proof predates required harness bindings; current Agent admission
prevents reaching those model and recovery scenarios. Interactive TUI execution
remains unverified on Podman. compose.logging.yaml remains Docker-only because this Podman baseline
does not provide the required Fluentd log driver.
Ordinary engine requests allow 10 seconds; container stop allows 20, including Docker's 10-second grace period. Both observe the current Compute cancellation signal. An incomplete response fails without retry: the engine operation may already have taken effect.
Engine response bodies have a 4 MiB budget. A larger declared content length is rejected before the body is read, and a response without that header is stopped when its received bytes cross the same limit. This fixed development boundary applies to all shared Driver requests, including image inspection and resource lists; unusually large legitimate metadata fails explicitly.
Credential and container-engine boundaries
Only the worker container receives Docker-compatible engine access. The OCC API,
PostgreSQL, the migration job, gateway containers, and Codex containers do
not receive the Docker socket. Only the controller receives the
occ_configuration_data volume at /app/.development/configurations; the
worker and workload containers do not mount it.
The underlying container code passes OPENAI_API_KEY only to the component
that performs the model call: the combined embedded OpenClaw container or the
dedicated Codex container. Current Agent admission cannot reach this path. A
dedicated gateway never receives the key. It must not appear in command
arguments, API responses, audit events, logs, Compose output, Docker labels, or
persisted controller configuration.
Runtime containers receive isolated writable state and temporary directories; Agents with initial inputs use the owned volumes described above for durable state and workspace contents. The Docker driver does not mount host workspaces, personal OpenClaw/Codex homes, SSH-agent sockets, cloud credentials, or controller credentials into workload containers.
The driver detects Podman's Docker-compatible API during preflight. Docker
keeps UID/GID-owned 0700 tmpfs mount options. Podman receives the same size
limits: 1Gi for the home and 64Mi for temporary filesystems. It uses its
supported mount options. The non-root runtime creates its .openclaw and workspace directories as
0700 before writing configuration or state. For Agents with initial inputs,
the setup container prepares ownership and permissions on the named volumes.
Troubleshooting
In-flight request aborts identify the operation, method, elapsed time, deadline and whether the request timed out or its caller cancelled. Check the named operation, caller cancellation and engine health.
- Worker starts with the wrong Compute Driver: unset
OCC_CONFIG_PATHfor default Compose development, or inspect the trusted startup YAML if selecting another Driver intentionally. - Container-engine access denied: verify the worker service has access to
the selected Docker-compatible API socket. With Podman, use
dev-upso it supplies the reported socket and the narrow SELinux override. Do not mount the socket into the API or workload services. - Image not found: provide locally available images through
OCC_DOCKER_GATEWAY_IMAGEandOCC_DOCKER_AGENT_IMAGE, orOCC_DOCKER_RUNTIME_IMAGEwhen one image contains both entrypoints. - API rejects Compose traffic: set
OCC_DEVELOPMENT_TRUSTED_BRIDGE_CIDRto the exact Compose bridge range. Do not enable forwarded-header trust. - Agent deployment rejects model authentication: Docker Compute does not accept any current Agent authentication binding. Use local Kubernetes setup to deploy an Agent and verify a model response.
- Namespace cleanup leaves resources: inspect ownership labels before deleting anything manually. The driver removes only exact owned resources.
Inspect owned resources
Owned resources can be observed through the selected engine using the same driver labels:
docker network ls --filter label=org.openclaw.enterprise.compute-driver=dockerdocker ps --filter label=org.openclaw.enterprise.compute-driver=dockerdocker volume ls --filter label=org.openclaw.enterprise.compute-driver=dockerReplace docker with podman for a Podman-backed development stack.
After deleting a Namespace, its matching network, containers, and Agent workspace volumes should be gone while other Namespaces remain. Agent deletion removes only that Agent's runtimes and volumes.
