OpenClaw EnterpriseDOCSGitHub

Compose development flow

Overview

./bin/occ dev up starts local OpenClaw Enterprise development from a checkout. The scripts/dev-up entry point selects the same profile. By default it starts the local k3d-only profile: Kubernetes Compute with the Kubernetes control plane (internal/occdev/profile.go:ResolveProfile). Operators can select the Compose control plane, or Docker Compute with OCC and PostgreSQL in Compose. The Kubernetes profile can select the OpenShell Sandbox Driver for its fail-closed path; it prepares pinned OpenShell infrastructure and reconciles rendered workspace-chart resources before reporting readiness. Compose-backed profiles perform host preflight, select Docker Engine or Podman, prepare runtime images, start Compose, and wait for PostgreSQL migration, Installation bootstrap, API health, and worker readiness. The Kubernetes-only profile performs the equivalent readiness checks inside its owned cluster. All profiles prepare HTTPS Console and Agent DNS hosts and use the existing loopback TLS proxy, even when Agent native access is disabled. Session cookie scope derives from those hosts; cookie-domain settings are refused. For Docker Compute, scripts/dev-up calls the hidden CLI adapter backed by internal/occdev/docker_browser.go:DockerBrowserEnvironment. It reuses the TLS generator and keeps private per-checkout certificates in the system temporary directory across stop/start. The launcher prints the Console URL and CA certificate path. occ dev down reloads the same proxy settings. Startup proves authenticated Installation access with a protected local bootstrap service key; it does not create an Agent or prove model execution.

For Docker Compute, the worker can reconcile Namespace infrastructure, but Agent deployment stops at harness authentication admission because Docker Compute rejects bindings. Kubernetes Agent execution continues through the selected Kubernetes Compute Driver and the authenticated Agent deployment procedure.

Entry Points

The Kubernetes profile additionally uses compose.kubernetes.yaml, internal/occdev, k3d, and kubectl. ./bin/occ dev down owns profile cleanup; scripts/dev-down dispatches to it. Without a Compute Driver selection, internal/occdev/profile.go:chooseDownStack sends cleanup to the stack recorded in the state directory, and to Docker only when that directory is absent or Compose options follow --; it refuses another checkout's stack. The local Kubernetes development guide owns the operator procedure and destructive cleanup boundary.

Flow

graph TD
  A["./bin/occ dev up"] --> Checkout{"Valid checkout markers<br/>and LF module?"}
  Checkout -->|No| Refuse["Refuse before startup"]
  Checkout -->|Yes| Guard{"Development settings valid?"}
  Guard -->|No| Refuse
  Guard -->|Yes| Profile{"Compute profile"}
  Profile -->|Docker| B["Preflight host tools, resolve Podman machine connection,<br/>and inspect Compose config"]
  Profile -->|Kubernetes| KPre["Pin local engine endpoint<br/>and reject existing resources"]
  KPre --> KControl{"Control-plane profile"}
  KControl -->|Kubernetes| KOwned["Create owned cluster,<br/>verify DNS/API and select kubeconfig"]
  KOwned --> KDriver{"Sandbox Driver"}
  KDriver -->|none| KCodex["Import runtime and verify<br/>the normal Codex sandbox"]
  KCodex -->|success| KRoutingFirst["Install routing controllers<br/>and remaining images"]
  KCodex -->|failure| KRollback
  KDriver -->|OpenShell| KOpenFirst["Prepare OpenShell assets,<br/>install routing, then import runtime"]
  KRoutingFirst --> KOnly["Install selected control-plane profile<br/>and retain all readiness gates"]
  KOpenFirst --> KGateway["Install authenticated Gateway<br/>and wait for readiness"]
  KGateway --> KOnly
  KOnly --> KWait["Complete control-plane readiness gates"]
  KWait --> KWorkspace
  KControl -->|Compose| KConfig["Validate Compose and claim<br/>private state with snapshot"]
  KConfig --> KStart["Bootstrap OCC and create<br/>the owned k3d cluster"]
  KStart --> KReady["Import runtime and start<br/>API and Kubernetes worker"]
  KReady --> KSandbox{"Sandbox profile"}
  KSandbox -->|none| KProof
  KSandbox -->|OpenShell| KOpenShell["Install pinned Agent Sandbox, RuntimeClass,<br/>Gateway, and render workspace resources"]
  KOpenShell -->|Compose| KRouting["Install private Envoy route and project<br/>its service key and public CA"]
  KRouting --> KWorkspace["Driver applies workspace resources and creates<br/>the default Namespace's owned Workspace"]
  KOpenShell -->|Kubernetes| KWorkspace
  KWorkspace --> KFailClosed["Default Namespace ready;<br/>Agent projection remains fail closed"]
  KFailClosed --> KProof
  KProof["Prove authenticated<br/>Installation access"]
  KProof --> KDown["./bin/occ dev down reuses<br/>recorded endpoint and project"]
  KStart -->|failure| KRollback["Roll back owned resources<br/>retain state if cleanup fails"]
  KReady -->|failure| KRollback
  KProof -->|failure| KRollback
  KDown --> KRemove["Stop reconcilers and delete<br/>owned cluster and volumes"]
  KRemove -->|success| KDone["Remove private state"]
  KRemove -->|failure| KRetain["Keep state for recovery"]
  B --> C["Select quickstart runtime image or validate custom images"]
  C --> D["Selected Compose starts PostgreSQL, migrate, bootstrap, API, and worker"]
  D --> E["Copy bootstrap service-key response to private local file"]
  E --> F["./bin/occ installation get proves authenticated access"]
  F --> G["Operator creates Namespace through authenticated API"]
  G --> H["Worker claims durable Namespace operation"]
  H --> I["Docker Driver ensures owned tenant network"]
  I --> J["Namespace becomes ready"]
  F --> K["Operator requests Agent deployment on Docker Compute"]
  K --> L["Admission rejects missing or unsupported harness binding"]
  J --> M["Authorized deletion removes owned Namespace resources"]
  D -->|operator cleanup| CDown["occ dev down keeps<br/>selected engine connection"]
  M --> CDown
  CDown --> CVolumes{"--volumes?"}
  CVolumes -->|No| CKeep["Remove Compose containers and network;<br/>retain named volumes"]
  CVolumes -->|Yes| CDelete["Remove Compose containers,<br/>network and named volumes"]

Execution Trace

1. Start and initialize the local stack

internal/occcli/dev.go:developmentCommand, internal/occdev/egress_k3d.go:CheckDevelopmentProfile, scripts/dev-up

internal/occcli/dev.go:developmentRepository selects the nearest enclosing checkout with an OCE module declaration and a regular compose.yaml file, including a symlink to one. An absent Compose marker allows the ancestor search to continue. A present marker that is not a regular file or cannot be resolved stops both up and down with its exact path before profile dispatch. CRLF in go.mod also stops both commands with an LF repair error. These invalid markers cannot fall through to an outer checkout and target its stack or volumes.

Before Compute dispatch, the CLI checks protected-egress, database, and host-gateway selectors through the existing development validators. Direct Docker script startup calls the same check through occ dev check-profile before temporary-state creation or engine probes; Kubernetes script startup dispatches to the guarded CLI. Invalid or incompatible selections stop startup. Unset or 0 protected egress preserves ordinary startup when its related selectors are absent.

Both shell wrappers locate the checkout with command-local CDPATH="" cd, so shell search paths cannot change the selected repository or contaminate the captured path; the delegated CLI retains the caller's original CDPATH.

Docker or Podman Compose startup owns engine selection, database initialization, local API admission and worker startup.

2. Prepare Namespace infrastructure and enforce Agent admission

apps/controller/src/drivers/compute/docker/index.ts:DockerComputeDriver

Namespace execution and Agent admission traces API authorization, queued controller operations, tenant network ownership, unsupported Agent authentication and exact resource removal. The Driver rejects Docker API responses above 4 MiB, by declared length or bytes received, before buffering the body. For in-flight aborts, DockerComputeDriver.request preserves the error and adds request diagnostics to its message. Preflight cancellation remains unchanged.

3. Clean up Docker or Podman Compose

internal/occdev/down.go:Down, internal/occdev/down.go:podmanComposeArgs

occ dev down uses the selected engine and forwarded Compose project and file options, including after partial startup. Podman keeps the caller's connection; its reported API socket sets OCC_CONTAINER_ENGINE_SOCKET only to resolve the worker mount, never replacing the host connection with a macOS VM socket. An unavailable engine or invalid socket fails cleanup.

Compose removes its project containers and network. With --volumes, it also removes project volumes. Down leaves local service-key copies and Docker Compute Namespace networks in place because the Compose project does not own them. See safe development shutdown.

4. Start and clean up Kubernetes development

internal/occdev/up.go:Up, internal/occdev/down.go:Down.

The Kubernetes startup and cleanup trace follows profile selection, the private Compose snapshot, k3d creation, runtime import, authenticated readiness, and cleanup through the recorded engine.

For the Kubernetes-only profile, internal/occdev/openshell_k3d.go:upK3d creates the owned cluster, checks node DNS, and writes and selects its kubeconfig. With Sandbox Driver none, it then imports the runtime image and runs prepareDevelopmentCodexSandbox before installing private routing controllers. Meanwhile, internal/occdev/concurrent.go:concurrently imports the controller image, then checks network isolation while importing PostgreSQL and, when configured, repository images. The first independent failure cancels the others and is reported. A sandbox refusal or failed check rolls back the owned cluster before Installation setup or credential delivery. Both paths install the platform only after the check, then gate on authenticated readiness.

internal/occdev/kubernetes.go:importDevelopmentImage exports each selected image to its own temporary archive, with an explicit Linux platform on Docker. internal/occdev/k3d_import.go:importArchiveDirect streams it into k3d, retrying only its known closed-stream race, three times at most. The launcher resolves the imported digest from containerd, rejects missing or ambiguous results, and removes the archive. Tagged builds share this path. internal/occdev/registry_k3d.go:pullDevelopmentImage pulls digest-pinned OCC_DEVELOPMENT_IMAGE_REGISTRY_CONTAINER images instead; state.json records the registry attachment for occ dev down to detach. Kubernetes-only uses its owned k3d network; Compose uses its exact recorded Compose network and selects only a runtime image with the current checkout label. Both detach after imports and on rollback, including recovery after interrupted startup. State rewrites set mode 0600 before writing so recovery can read state.json under a restrictive umask. For Kubernetes-only OpenShell, runtime import follows PostgreSQL/repository imports in their existing concurrent branch. OpenShell asset preparation follows those imports while network probes finish. This installs the Agent Sandbox controller and CRDs and imports images; platform installation and credential delivery still wait for every branch, including successful network verification. Image builds remain serial, and the large runtime import precedes smaller OpenShell assets.

5. Prepare the optional OpenShell development profile

internal/occdev/openshell_k3d.go:upK3d, internal/occdev/openshell.go:prepareOpenShell, apps/controller/src/drivers/sandbox/openshell.ts:ensureNamespace

With OCC_DEVELOPMENT_CONTROL_PLANE=kubernetes, OpenShell uses the Kubernetes-only lifecycle before Compose rendering. It pins K3s and OpenShell inputs, installs PostgreSQL, OCE, and one central Gateway in oce-system, and publishes only an admitted API proxy on host loopback. The source archive and all three OpenShell images select 0.1.8 together, including the paired sandbox/supervisor control protocol and compromised-key rejection. The observed-hook repository credential preview admits 0.1.6 through its existing opt-in, held-launcher and recipient checks. Repository-scoped access uses the separate protected Git enrollment.

With OCC_DEVELOPMENT_CONTROL_PLANE=compose, the Kubernetes lifecycle starts PostgreSQL, migration, bootstrap, the API, and the worker in Compose. It installs the central Gateway in openshell-system and exposes its fixed NodePort only to the owned container network. Before starting the API and worker, the launcher installs pinned cert-manager and Envoy Gateway controllers, renders only the Helm-owned private routing resources, and copies the generated public CA and a dedicated service key into the private state directory. The Installation uses the k3d node name and Envoy HTTPS NodePort. After Compose starts, the launcher reapplies Envoy ingress with the controller, Kubernetes worker, and k3d node /32 addresses. In both modes, the worker creates the bootstrap Namespace through the regular Compute workflow. The Sandbox Driver applies rendered workspace resources and the operator label before creating the corresponding Gateway Workspace. Startup waits until the OCC Namespace becomes ready. See the OpenShell flow.

6. Start authenticated OpenShell transport

internal/occdev/egress_k3d.go:protectedEgressSelection, internal/occdev/openshell_k3d.go:installDevelopmentPlatform, apps/controller/src/drivers/sandbox/openshell-gateway-client.ts:createClient

internal/occdev/openshell.go:installOpenShellGateway enables native PKI, TLS, mTLS and authentication for every Kubernetes-only OpenShell installation. openShellBackendConfiguration selects HTTPS and client certificates; installKubernetesControlPlane projects them into the API and worker through Helm. The default profile registers no static-inference middleware and waits for the Gateway before installing OCE. It needs no corrective Gateway upgrade or Pod deletion.

New protected static-inference launches are retired. CheckDevelopmentProfile rejects OCC_DEVELOPMENT_PROTECTED_EGRESS=1 before Compute dispatch, including the former Kubernetes/OpenShell selection. Direct Kubernetes startup also checks protectedEgressSelection before provisioning. Any presence of OCC_DEVELOPMENT_OPENSHELL_HOST_GATEWAY_IP, including an empty value, is rejected. Unset, empty and 0 protected-egress selections retain ordinary startup; malformed values still fail validation. Refusal does not select a fallback.

For an existing installation, keep the recorded state and ownership evidence for cleanup. Legacy protected fields remain readable in cleanup receipts but do not configure startup.

The chart projects the administrative client certificate into API and worker only. The Backend supplies it to verified gRPC TLS for DNS or exact-IP server identities. Service traffic keeps its separate scoped bearer identity. For separately prepared production installations with an authority, the API alone receives authority TLS and the issuer public key; selected Gateway and supervisor Pods can reach that listener through narrow NetworkPolicies. See the Backend authentication boundary.

selectedDevelopmentDatabase validates an optional disposable database name before effects. installKubernetesControlPlane uses that name in creation, readiness, migrator and application URLs. Bootstrap, API and worker share the limited application-role URL. PostgreSQL starts while the bootstrap volume is prepared; Helm waits for both. Unselected profiles retain the existing database and transport defaults. The retirement notice describes accepted selectors.

Debugging and Verification

Search documentation