Docker and containers
Kheish can run cleanly in containers, but the production shape should stay explicit:- one daemon container per instance
- one persistent state volume
- one dedicated workspace volume or bind mount
- one explicit routes file
- one explicit control-plane auth boundary
- one explicit auth-store master key
Supported production topology
The recommended production topology is:- one
kheish-daemoncontainer - model routes defined through
--routes-file - daemon-managed route secrets stored under the persistent
state_root - external connectors run as separate services and talk to the daemon through
remote_http
What the official image includes
The daemon image is built for the actual runtime surface the daemon expects today:- the
kheish-daemonbinary bashfor shell executionripgrepfor search toolsprocpsfor MCP child-process managementgitfor common code workflowsca-certificatesfor outbound HTTPStinifor init and signal forwarding
KHEISH_BIND=0.0.0.0:4000KHEISH_STATE_ROOT=/var/lib/kheish/stateKHEISH_WORKSPACE_ROOT=/workspaceKHEISH_HTTP_AUTH_MODE=bearerKHEISH_MCP_DISCOVERY=disabled
Why MCP discovery defaults to disabled
Container workloads should not implicitly import developer-local MCP config from$HOME/.codex.
The image therefore defaults to:
--mcp-config--mcp-credentials
KHEISH_MCP_CONFIGKHEISH_MCP_CREDENTIALS
child_process connectors. If your MCP config points at those runtimes, build a custom image or run them as separate services.
Secret files
In container environments, prefer file-backed secrets over inline environment variables. The key file inputs are:KHEISH_DAEMON_ADMIN_TOKEN_FILEKHEISH_DAEMON_READONLY_TOKEN_FILEKHEISH_AUTH_STORE_MASTER_KEY_FILE
KHEISH_AUTH_STORE_MASTER_KEY. The daemon and any offline secrets set --offline writes that touch the same state_root must use the same key.
The shipped Compose example uses Docker-managed secrets mounted under /run/secrets/... instead of bind-mounting the host secret directory. That keeps host-side 0600 permissions compatible with the non-root container user.
Probes
The daemon now exposes unauthenticated liveness and readiness endpoints outside the control-plane auth middleware:GET /healthzGET /readyz
/readyz returns 503 Service Unavailable once daemon shutdown begins. Use those endpoints for Docker health checks, Kubernetes probes, or external monitoring. Do not reuse authenticated /v1/* endpoints for liveness.
Example compose workflow
The repository includes: The shipped Compose file uses named volumes for both the daemon state and the workspace. That is the safer production default because it avoids host UID and permission mismatches from a fixed non-root container user. Build the image first:/run/secrets/.
Bootstrap the route secret into the daemon-owned auth store before the long-running container starts:
/workspace will be owned by the container user and can become awkward to edit from the host.
Entrypoint guardrails
The official image entrypoint rejects obvious misconfiguration early. In particular, it refuses:- relative
state_rootorworkspace_root - identical
state_rootandworkspace_root - non-loopback binds without bearer auth, unless you explicitly opt into insecure local testing
- unreadable token or master-key files
- conflicting inline and file-backed secret inputs for the same setting
Shutdown behavior
Container runtimes sendSIGTERM, not Ctrl+C.
The daemon now treats both SIGTERM and SIGINT as graceful shutdown signals, so:
- ingress tasks receive the normal shutdown signal
- MCP shutdown still runs
- the process exits cleanly under normal orchestrator termination
Connector guidance in containers
The recommended production boundary is:- daemon container
- connector containers or services
remote_httpexternal connector mode between them
child_process inside the daemon container. That makes dependency management and blast radius worse for no operational gain.
Use child_process inside containers only when you intentionally build a custom image for a tightly controlled environment.