Worker, Compose, and PostgreSQL settings
This reference owns worker, compose, and postgresql settings. Start with the settings reference for startup configuration and precedence.
PostgreSQL connection authentication
Both PostgreSQL API modes, the worker, shared Installation bootstrap, and
both migration commands use the same connection pool factory.
OCC_DATABASE_AUTH defaults to password, which preserves PostgreSQL URL
credentials. The other supported mode is azure-workload-identity; unknown
modes fail before opening a pool.
| Variable | Requirement in Azure workload-identity mode |
|---|---|
OCC_DATABASE_AUTH |
Set to azure-workload-identity in each connecting process. |
AZURE_TENANT_ID |
Tenant for the process's workload identity. |
AZURE_CLIENT_ID |
Client ID for the process's workload identity. |
AZURE_FEDERATED_TOKEN_FILE |
Readable projected federation-token file, renewed by the deployment. |
This mode requires a password-free database URL with certificate and hostname
verification, such as
postgresql://occ_app@database.example/occ?sslmode=verify-full.
The factory rejects nested connection strings, URL passwords, missing TLS, and
parsed TLS options that disable certificate or hostname checks. Keep the
application and migrator database roles separate: API, worker, and bootstrap
use OCC_DATABASE_URL; migrations use OCC_MIGRATION_DATABASE_URL with the
migrator identity.
Each new pool connection requests an access token through the Azure SDK's
WorkloadIdentityCredential for
https://ossrdbms-aad.database.windows.net/.default. The SDK owns token caching
and renewal; OCC does not persist tokens or fall back to a developer login.
Use pnpm db:migrate or pnpm db:migrate:production for migrations in this mode.
See operator setup for deployment-owned identity inputs and chart limits, and PostgreSQL testing for the available connection proof and its limits.
Migration history
Set OCC_MIGRATION_DATABASE_URL privately to the intended database's dedicated
occ_migrator login. The application login is occ_app; neither role may be a
superuser, create roles or databases, replicate, or bypass row security. The
migrator needs database CREATE and owns the occ and drizzle schemas. The
application role must not inherit the migrator or have database/schema CREATE.
Classify a database without applying migrations:
pnpm db:migrate --checkAn exit-0 migration.checked record reports one reviewed history shape:
empty, prePresetsMain, main, repositoryCredentials,
repositoryRetention, workspaceSetup, agentProvisioning,
backendCompleted, providerCompleted, backendTerminology, prePluginApprovers,
preBrokerReceipts, preAgentDeletion, preDeploymentProgress,
preHumanAuthentication, preAgentDeletionTakeover,
preNamespaceDeletionTakeover, preRepositoryAccess, or completed.
prePresetsMain means
the exact canonical history through 0023_runtime_failure_timestamp_validation;
main also includes 0024_agent_presets. repositoryCredentials adds
0025_repository_credentials and 0026_privileged_function_search_paths.
repositoryRetention, workspaceSetup, and agentProvisioning add migrations
through 0029_agent_provisioning_work. backendCompleted is the exact
31-receipt Backend terminology history published before the compatibility
migration. providerCompleted is the exact 31-receipt Provider terminology
history published before the rename. backendTerminology has the 32 receipts
through 0031_backend_terminology_compatibility; prePluginApprovers adds
0032_credential_sources for 33 receipts. preBrokerReceipts has 34 receipts
through 0033_agent_plugin_approvers; preAgentDeletion has 35 through
0034_repository_broker_receipts; preDeploymentProgress has 36 through
0035_agent_deletion_repository_session_evidence. preHumanAuthentication has
the 37 receipts through 0036_deployment_progress; preAgentDeletionTakeover
has 38 through 0037_human_authentication; preNamespaceDeletionTakeover has
39 through 0038_agent_deletion_takeover; preRepositoryAccess has 40 through
0039_namespace_deletion_takeover. completed is the current canonical
history with all receipts, including 0040_repository_access.
The source manifest is
migrations/meta/canonical-history.json.
Empty schemas may be absent or have only their owner's ordinary CREATE and
USAGE privileges, with no objects or unexpected default privileges. An empty
stock Drizzle ledger left by a rolled-back first migration is also supported.
Run pnpm db:migrate for development or pnpm db:migrate:production for
production after a successful check. Both commands validate source hashes,
receipts, catalog definitions, effective application privileges, and role
separation under one PostgreSQL advisory lock. Drizzle applies the pending SQL
and receipts in its normal transaction on that same connection. Existing
canonical receipts remain unchanged. --check is also accepted by the
production command.
The compatibility migration appends a new receipt instead of rewriting either
published 31-receipt history. On a Provider terminology database it renames the
owned relational columns, admitted harness and repository bindings, preset
templates, retained repository cleanup contexts, accepted provisioning plans,
validation functions, and trigger definitions to Backend terminology. It
preserves unrelated Provider terms such as Better Auth account.provider_id,
model Provider catalogs, gateway authentication providers, repository grant
providerInstanceId, and historical audit payloads. Existing terminal
provisioning rows keep their request_fingerprint as historical idempotency
evidence while the accepted plan JSON is rewritten from providerId to
backendId. Replaying the old public Provider-shaped request against the
renamed API is a distinct request and receives a distinct fingerprint. The
preflight and migration both refuse a persisted object that contains both old
and new keys at one of these owned paths.
MIGRATION_HISTORY_UNSUPPORTED means the command refused before migration DDL.
Mixed Provider and Backend receipt histories, partial manual edits, and the
earlier development history that installed repository credentials at index 24
without Agent presets are unsupported, even if all of their own migrations
completed. They cannot be converted by renaming or rewriting applied receipts.
Do not edit the ledger, run Drizzle directly to bypass the check, or restore an
old schema over the canonical one. A failed or disconnected migration is not
proof of rollback: reconnect, run --check, and inspect the retained database
before deciding whether another attempt is appropriate.
Recreate an unsupported disposable development installation
First match the target to the startup output: container engine and connection,
Compute profile, Compose project/files or Kubernetes state directory, database
host/port/name, and Installation ID. The database-only
compose.postgres.yaml helper and the full development stack have different
default project names. pnpm db:down stops only its selected database-only
project and retains its volume.
For an unsupported premerge history, choose either to retain the old installation and start a separately configured fresh one, or to discard that specific disposable installation. Preserve any needed database/storage archive and protected bootstrap output first. An archive is not a supported import into the canonical history; a whole-database restore would restore the unsupported ledger too. There is no general reset, conversion, or reseed command. Recreate selected business data through its owning supported API or import procedure. Account for outstanding Agent resources and credential cleanup before disposal; removing control-plane storage does not retire external resources or grants.
For Docker or Podman, copy the exact Cleanup command printed by the original
startup. Add --volumes before its -- only when intentionally deleting that
installation's PostgreSQL, Configuration, and bootstrap volumes. Preserve its
engine connection, environment, project, Compose files, and overrides. For
example, after setting DEV_COMPOSE_PROJECT to your own recorded project:
./bin/occ dev down --volumes -- -p "$DEV_COMPOSE_PROJECT" -f compose.yaml./bin/occ dev up --key-output /absolute/private/new-installation-key.json -- \ -p "$DEV_COMPOSE_PROJECT" -f compose.yamlThe recorded Kubernetes cleanup is already destructive: it removes that profile's cluster and Compose volumes, then removes its private state. Use its exact printed command and state directory:
OCC_DEVELOPMENT_COMPUTE_DRIVER=kubernetes \OCC_DEVELOPMENT_STATE_DIRECTORY="$DEV_STATE_DIRECTORY" ./bin/occ dev downStart the chosen fresh profile from the canonical source using a new protected
key-output path. occ dev up applies migrations, bootstraps the initial
administrators/Installation/default Namespace, and checks authenticated
Installation access. Fresh bootstrap refuses existing output files; an old
bootstrap volume or key file is not valid output for the new database. Repeating
bootstrap against an existing Installation verifies it and issues no new key.
Use the startup's printed authenticated-check command, or privately select
OCC_URL and OCC_SERVICE_KEY_FILE and run:
./bin/occ installation get --output jsonIts Installation ID must match the protected key response's
meta.installationId. Stop on mismatches, missing output, failed migration,
uncertain bootstrap outcome, or incomplete cleanup. This proves control-plane
access; qualify worker, Agent, repository, and provider behavior separately.
Controller worker environment
The controller worker runs separately from the HTTP API. It requires a bootstrapped Installation and the same migrated PostgreSQL database; it does not use the API's listener or authentication settings.
| Variable | Requirement or default | Behavior |
|---|---|---|
NODE_ENV |
development or production. |
Both modes process Namespace and selected AgentRevision work through the selected Compute Driver. |
OCC_DATABASE_URL |
Required application-role URL. | Must use a postgresql: or postgres: connection to the API's PostgreSQL database. |
OCC_CONFIG_PATH |
Required in production. | Absolute startup YAML shared with the API; development omits it to use the Docker Compute default or sets it to explicitly select another trusted Driver set. |
OCC_WORKER_POLL_INTERVAL_MS |
250. |
Positive safe integer controlling the delay between idle polling attempts. |
OCC_WORKER_LEASE_DURATION_MS |
5000. |
Positive safe integer controlling the claim lease in milliseconds. |
OCC_WORKER_MAX_ATTEMPTS |
5. |
Positive safe integer limiting attempts before permanent failure. |
OCC_WORKER_CONVERGENCE_TIMEOUT_MS |
900000. |
Positive safe integer bounding Namespace convergence from operation creation. |
OCC_WORKER_READINESS_PATH |
Optional absolute path. | Writes a private freshness marker after real queue-health observations; required by packaged worker probes. |
Start the worker only after the controller is healthy and the Installation exists:
NODE_ENV=development \OCC_DATABASE_URL=postgresql://occ_app:occ-app-local@127.0.0.1:55432/openclaw_enterprise \OCC_DOCKER_RUNTIME_IMAGE=openclaw-enterprise-runtime:quickstart \OPENAI_API_KEY=${OPENAI_API_KEY:?set OPENAI_API_KEY} \node apps/controller/src/worker.mjsFor production, set the shared PostgreSQL connection and absolute Installation startup YAML path first, then start the API and worker as separate processes:
NODE_ENV=production node apps/controller/src/server.mjsNODE_ENV=production node apps/controller/src/worker.mjsThe worker never handles controller API sessions. In production it claims, recovers, and counts Namespace work plus embedded OpenClaw and dedicated Codex AgentRevision work. Each Agent has its own active revision; the worker does not impose a Namespace-wide Agent limit.
Local Compose and PostgreSQL configuration
The root Compose development stack starts PostgreSQL 18.6, migrations,
bootstrap, API, and worker. occ dev up starts the supported development
profile; the checkout-local scripts/dev-up entry point uses the same path.
Startup validates Compose configuration, waits for services, copies
the bootstrap service-key response to a private file, and proves authenticated
access. OCC_DEVELOPMENT_COMPUTE_DRIVER selects Docker Compute or Kubernetes
Compute. Kubernetes Compute can use a Compose control plane or the
Kubernetes-only profile.
The helper prefers a usable Docker Engine and otherwise selects Podman directly,
even when no docker compatibility alias exists. Podman requires the standalone
podman-compose provider; Docker Compute also requires yq v4. The helper
pins that provider for consistent behavior. Docker Compute mounts the selected engine socket into its
worker. That socket-owning worker disables SELinux process labeling because the
host engine socket must not be relabeled; all other services retain SELinux
confinement. Direct docker compose commands remain supported. The Podman
override supplies the reported Podman socket and is not used with Docker Engine.
compose.postgres.yaml remains the focused
database-only helper for tests and manual PostgreSQL debugging.
compose.logging.yaml enables Docker development collection
and mounts deploy/logging/occ.yaml as
/etc/openclaw/occ.yaml in OCC services. Follow the
Docker observability procedure
for receiver/exporter setup, persistent queue storage, and verification. Podman
development rejects this override because the supported Podman runtime does not
provide Docker's Fluentd logging driver and options.
Development logging override variables:
| Variable | Default or requirement | Behavior |
|---|---|---|
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT |
Required when compose.logging.yaml is used. |
Passed only to the Collector exporter; use HTTPS for real backends and HTTP only for local test receivers. |
OTEL_COLLECTOR_PORT |
24224. |
Publishes the Collector Fluent Forward receiver on 127.0.0.1:<port>. |
OTEL_COLLECTOR_METRICS_PORT |
8888. |
Publishes Collector self-metrics on 127.0.0.1:<port>. |
OCC_DOCKER_LOGGING_ADDRESS |
127.0.0.1:24224 when the override is active. |
Tells Docker Compute where the Engine should forward managed gateway and Codex container logs; keep it loopback. |
Both Compose files bind the PostgreSQL host port to loopback only. The following value controls Compose port substitution:
| Variable | Default | Behavior |
|---|---|---|
OCC_POSTGRES_PORT |
55432 |
Maps 127.0.0.1:<port> to container port 5432. Update every PostgreSQL connection URL to match it. |
The fixed bridge CIDR must not overlap another local container network. For a
second isolated Compose project, select an unused value through
OCC_DEVELOPMENT_TRUSTED_BRIDGE_CIDR; the same value configures the controller's
explicitly trusted development bridge.
The Compose service fixes POSTGRES_DB=openclaw_enterprise,
POSTGRES_USER=postgres, and POSTGRES_PASSWORD=openclaw-local-admin. These
values describe a local-only disposable service; they are not controller
environment variables or production credentials.
migrations/init-local.sql creates separate
least-privilege local roles:
| Role | Local-only password | Purpose |
|---|---|---|
occ_app |
occ-app-local |
Controller runtime and application-role integration tests. |
occ_migrator |
occ-migrator-local |
Reviewed Drizzle migrations and migration-history ownership. |
postgres |
openclaw-local-admin |
Local Compose administration only; do not use this role as the controller application role. |
Never give the controller the migrator or administrator URL. The application role cannot create schema objects, modify migration history, or rewrite immutable audit and revision records.
For database-only debugging, start PostgreSQL and apply migrations using its dedicated role:
docker compose -f compose.postgres.yaml up -d --wait export OCC_MIGRATION_DATABASE_URL=postgresql://occ_migrator:occ-migrator-local@127.0.0.1:55432/openclaw_enterprisenode_modules/.bin/drizzle-kit migrateAdd the application connection URL to the required development controller environment, then initialize before starting the same server manually. Use an existing private output directory and an unused absolute filename for fresh setup. Retain that directory for credential recovery; subsequent initialization does not reissue a key. This path is only for intentional host-process debugging:
export OCC_DATABASE_URL=postgresql://occ_app:occ-app-local@127.0.0.1:55432/openclaw_enterpriseexport OCC_DATABASE_POOL_MAX=10export OCC_DEVELOPMENT_CONFIGURATION_ROOT="$(pwd)/.development/configurations"export OCC_BOOTSTRAP_SERVICE_KEY_FILE='/absolute/private-directory/initial-admin-service-key.json'NODE_ENV=development node scripts/bootstrap-installation.mjsNODE_ENV=development node apps/controller/src/server.mjsInstallation, Namespace, Agent, native IAM, audit, and controller-work state survive restart in both Compose and database-only modes. The full Compose path also starts the worker and selects Docker Compute-backed Namespace and AgentRevision execution by default.
Compose keeps relational OCC metadata in the occ_postgres_data named volume
and native development Configuration documents in the occ_configuration_data
named volume. The configuration volume is mounted only into the controller at
/app/.development/configurations; it is not mounted into the worker or
runtime containers. Initial service-key output uses a third bootstrap-only
volume, occ_bootstrap_data, at /var/lib/openclaw/bootstrap; the API and
worker do not mount it. For Docker Compute, the printed cleanup command retains
all three volumes and uses the selected container engine. Podman cleanup retains
the caller's CONTAINER_CONNECTION or CONTAINER_HOST selection.
Add --volumes before any -- separator only when
intentionally deleting them, including the initial credential delivery copy.
Kubernetes development cleanup
always deletes the selected profile's volumes.
Migration environment
| Variable | Required by | Behavior |
|---|---|---|
OCC_MIGRATION_DATABASE_URL |
Drizzle configuration and tools. | Must contain the dedicated occ_migrator connection URL. Drizzle commands fail when the variable is absent. |
drizzle.config.ts fixes the PostgreSQL dialect,
packages/occ/src/state/postgres-schema.ts
as the relational schema, migrations/ as the migration
directory, the occ application schema, and
drizzle.__drizzle_migrations as the migration-history table. strict and
verbose are enabled. There are no environment overrides for these settings.
