-
Notifications
You must be signed in to change notification settings - Fork 4.4k
13 Deployment
Relevant source files
The following files were used as context for generating this wiki page:
This page provides an overview of the three primary deployment methods for ZeroClaw: Docker containers, native binary installation, and automated bootstrap scripts. It covers the build process, configuration management, networking setup, and production deployment considerations. For detailed instructions on specific deployment methods, see:
- Docker Deployment - Docker image structure, docker-compose configuration, and multi-stage builds
- Native Binary Deployment - Cargo installation, cross-compilation, and bootstrap script usage
- Production Configuration - Security hardening, resource limits, monitoring, and scaling best practices
ZeroClaw supports three deployment approaches, each optimized for different use cases:
| Deployment Method | Best For | Binary Size | Memory Footprint | Startup Time |
|---|---|---|---|---|
| Docker (release stage) | Production servers, cloud deployments | ~3.4 MB (stripped) | < 5 MB | < 10ms |
| Docker (dev stage) | Local development, testing with Ollama | ~8.8 MB | < 10 MB | < 100ms |
| Native Binary | Edge devices, embedded systems, CI/CD | 3.4-8.8 MB | < 5 MB | < 10ms |
| Bootstrap Script | Automated provisioning, fresh systems | N/A (builds native) | < 5 MB | < 10ms |
Sources: README.md:63-98, Cargo.toml:161-181
The Docker image uses a multi-stage build process with three distinct stages:
-
Builder stage (
rust:1.93-slim) - Compiles the release binary with dependency caching -
Dev stage (
debian:trixie-slim) - Development runtime with Ollama defaults and debugging tools -
Release stage (
gcr.io/distroless/cc-debian13:nonroot) - Minimal production runtime with distroless base
The release stage runs as non-root user 65534:65534 with a hardened configuration suitable for production deployments.
Sources: Dockerfile:1-113
Native binaries are built using Cargo with size-optimized profiles:
-
releaseprofile - Optimized for size (opt-level = "z") with serialized codegen (codegen-units = 1) for low-memory devices like Raspberry Pi -
release-fastprofile - Parallel codegen (codegen-units = 8) for faster builds on high-memory machines (16GB+ RAM) -
distprofile - Maximum size optimization with fat LTO for distribution
All release profiles use strip = true to remove debug symbols and panic = "abort" to reduce binary size.
Sources: Cargo.toml:161-181, README.md:195-198
The bootstrap script (bootstrap.sh) automates the complete setup process:
# Basic installation
./bootstrap.sh
# Install system dependencies and Rust toolchain
./bootstrap.sh --install-system-deps --install-rust
# Install with immediate onboarding
./bootstrap.sh --onboard --api-key "sk-..." --provider openrouterThe script handles dependency installation, Rust toolchain setup, compilation, and optional onboarding configuration.
Sources: README.md:172-192
flowchart TD
Source["Source Code<br/>(src/, crates/, firmware/)"] --> BuildChoice{"Build Method?"}
BuildChoice -->|Docker| DockerBuild["Multi-Stage Docker Build<br/>(Dockerfile)"]
BuildChoice -->|Native| CargoBuild["Cargo Build<br/>(--release --locked)"]
BuildChoice -->|Bootstrap| BootstrapScript["bootstrap.sh"]
DockerBuild --> Stage1["Stage 1: Builder<br/>rust:1.93-slim"]
Stage1 --> DepCache["Dependency Caching<br/>(Cargo.toml, Cargo.lock)"]
DepCache --> Compile["Compile Release Binary<br/>(target/release/zeroclaw)"]
Compile --> Strip["Strip Debug Symbols"]
Strip --> StageChoice{"Target Stage?"}
StageChoice -->|Dev| DevStage["Stage 2: Dev Runtime<br/>debian:trixie-slim<br/>+ Ollama config"]
StageChoice -->|Prod| ProdStage["Stage 3: Release Runtime<br/>distroless/cc-debian13<br/>+ OpenRouter config"]
CargoBuild --> ProfileChoice{"Profile?"}
ProfileChoice -->|Default| ReleaseProfile["release profile<br/>codegen-units=1<br/>opt-level='z'"]
ProfileChoice -->|Fast| FastProfile["release-fast profile<br/>codegen-units=8<br/>opt-level='z'"]
ProfileChoice -->|Dist| DistProfile["dist profile<br/>lto='fat'<br/>opt-level='z'"]
BootstrapScript --> SysDeps["Install System Deps<br/>(build-essential, pkg-config)"]
SysDeps --> RustInstall["Install Rust Toolchain<br/>(rustup)"]
RustInstall --> CargoInstall["cargo install --path ."]
ReleaseProfile --> Binary["zeroclaw binary<br/>(3.4-8.8 MB)"]
FastProfile --> Binary
DistProfile --> Binary
DevStage --> DevImage["zeroclaw:dev<br/>(Debian-based)"]
ProdStage --> ProdImage["zeroclaw:latest<br/>(Distroless)"]
CargoInstall --> Binary
Binary --> Deploy["Deployment Target"]
DevImage --> Deploy
ProdImage --> Deploy
Sources: Dockerfile:4-113, Cargo.toml:161-181, README.md:172-198
The build optimization strategy differs based on target hardware constraints:
Low-Memory Devices (< 2GB RAM)
[profile.release]
opt-level = "z" # Optimize for size
lto = "thin" # Lower memory use during builds
codegen-units = 1 # Serialized codegen for 1GB RAM devices
strip = true # Remove debug symbols
panic = "abort" # Reduce binary sizeHigh-Memory Machines (16GB+ RAM)
[profile.release-fast]
inherits = "release"
codegen-units = 8 # Parallel codegen for faster buildsThe default release profile is designed for Raspberry Pi 3 compatibility (1GB RAM) and can build successfully on constrained devices without OOM failures.
Sources: Cargo.toml:161-172, README.md:164
flowchart LR
subgraph "Stage 1: Builder"
S1A["rust:1.93-slim<br/>SHA-pinned"]
S1B["Copy Cargo.toml<br/>Cargo.lock<br/>rust-toolchain.toml"]
S1C["Create dummy targets<br/>(src/main.rs stub)"]
S1D["cargo build --release<br/>--locked<br/>(dependency layer)"]
S1E["Copy real source<br/>(src/, crates/, firmware/)"]
S1F["cargo build --release<br/>--locked<br/>(final build)"]
S1G["strip zeroclaw"]
S1A --> S1B --> S1C --> S1D --> S1E --> S1F --> S1G
end
subgraph "Stage 2: Dev"
S2A["debian:trixie-slim<br/>SHA-pinned"]
S2B["Install runtime deps<br/>(ca-certificates, curl)"]
S2C["Copy binary + config"]
S2D["Overwrite with<br/>dev/config.template.toml"]
S2E["ENV: PROVIDER=ollama<br/>MODEL=llama3.2"]
S2A --> S2B --> S2C --> S2D --> S2E
end
subgraph "Stage 3: Release"
S3A["distroless/cc-debian13<br/>SHA-pinned<br/>(nonroot)"]
S3B["Copy binary + config"]
S3C["ENV: PROVIDER=openrouter"]
S3D["USER 65534:65534"]
S3A --> S3B --> S3C --> S3D
end
S1G -->|Binary| S2C
S1G -->|Binary| S3B
S2E --> DevImage["zeroclaw:dev<br/>ENTRYPOINT zeroclaw<br/>CMD gateway"]
S3D --> ReleaseImage["zeroclaw:latest<br/>ENTRYPOINT zeroclaw<br/>CMD gateway"]
Sources: Dockerfile:4-113
The multi-stage build uses BuildKit cache mounts to optimize dependency compilation. The dummy target trick (lines 18-27) enables Docker to cache the dependency layer separately from application code, reducing rebuild times from minutes to seconds when only source code changes.
Key optimization: The builder stage creates placeholder files (src/main.rs, benches/agent_benchmarks.rs, crates/robot-kit/src/lib.rs) matching the Cargo.toml targets, runs cargo build --release --locked to compile dependencies, then deletes the placeholders and copies real source code for the final build. This ensures dependency compilation is cached independently.
Sources: Dockerfile:18-27
graph TD
subgraph "Configuration Hierarchy"
Base["Base Configuration<br/>~/.zeroclaw/config.toml"]
Env["Environment Variable Overrides<br/>(API_KEY, ZEROCLAW_*, etc.)"]
Workspace["Workspace Marker<br/>~/.zeroclaw/active_workspace.toml"]
AuthProfiles["Authentication Profiles<br/>~/.zeroclaw/auth-profiles.json<br/>(encrypted at rest)"]
SecretKey["Encryption Key<br/>~/.zeroclaw/.secret_key<br/>(ChaCha20Poly1305)"]
end
Base --> Runtime["Runtime Configuration"]
Env --> Runtime
Workspace --> Runtime
AuthProfiles --> Runtime
SecretKey -.encrypts.-> AuthProfiles
Runtime --> Agent["Agent Execution<br/>(zeroclaw agent)"]
Runtime --> Gateway["Gateway Server<br/>(zeroclaw gateway)"]
Runtime --> Daemon["Daemon Runtime<br/>(zeroclaw daemon)"]
Sources: README.md:258-262, README.md:493-599
Docker deployments manage configuration through three mechanisms:
-
Base config file - Baked into image at
/zeroclaw-data/.zeroclaw/config.toml - Environment variables - Override config values at runtime
- Volume mounts - Persist configuration and workspace across container restarts
Docker Compose Example:
services:
zeroclaw:
image: ghcr.io/zeroclaw-labs/zeroclaw:latest
environment:
- API_KEY=${API_KEY:-} # Required
- PROVIDER=${PROVIDER:-openrouter} # Optional override
- ZEROCLAW_ALLOW_PUBLIC_BIND=true # Container networking
volumes:
- zeroclaw-data:/zeroclaw-data # Persist workspace + config
ports:
- "${HOST_PORT:-3000}:3000"Sources: docker-compose.yml:10-50
The volume mount at /zeroclaw-data persists both workspace files and runtime configuration modifications. The ZEROCLAW_ALLOW_PUBLIC_BIND=true environment variable is required in Docker because containers need to bind to [::] (all interfaces) for cross-container networking, while the security model normally restricts public binding.
Sources: Dockerfile:99-112
Configuration follows a three-tier priority system:
| Priority | Source | Example |
|---|---|---|
| Highest | Environment variables | API_KEY=sk-... |
| Medium |
config.toml file |
api_key = "sk-..." |
| Lowest | Built-in defaults | default_provider = "openrouter" |
Common environment variable prefixes:
-
API_KEYorZEROCLAW_API_KEY- Provider API key -
OPENROUTER_API_KEY,OPENAI_API_KEY,ANTHROPIC_API_KEY- Provider-specific keys -
ZEROCLAW_WORKSPACE- Workspace directory override -
ZEROCLAW_GATEWAY_PORT- Gateway port override -
ZEROCLAW_ALLOW_PUBLIC_BIND- Allow binding to0.0.0.0/::
Sources: README.md:493-599, docker-compose.yml:18-32
flowchart TD
Start["zeroclaw gateway"] --> CheckConfig{"Config:<br/>gateway.host?"}
CheckConfig -->|"127.0.0.1"| LocalBind["Bind to localhost<br/>(secure default)"]
CheckConfig -->|"0.0.0.0 or [::]"| CheckPublic{"Config:<br/>allow_public_bind?"}
CheckConfig -->|"Custom IP"| CustomBind["Bind to specified IP"]
CheckPublic -->|true| CheckTunnel{"Tunnel<br/>active?"}
CheckPublic -->|false| Refuse["Refuse to start<br/>(security error)"]
CheckTunnel -->|Yes| PublicBind["Bind to public interface<br/>(via tunnel)"]
CheckTunnel -->|No| Warn["Bind to public interface<br/>(with warning)"]
LocalBind --> Serve["HTTP Server<br/>axum router"]
CustomBind --> Serve
PublicBind --> Serve
Warn --> Serve
Serve --> Pairing["POST /pair<br/>(6-digit code exchange)"]
Serve --> Webhook["POST /webhook<br/>(Bearer auth required)"]
Serve --> Health["GET /health<br/>(public)"]
Serve --> WhatsAppVerify["GET /whatsapp<br/>(Meta verification)"]
Serve --> WhatsAppWebhook["POST /whatsapp<br/>(Meta signature)"]
Sources: README.md:385-391, README.md:526-530
Docker deployments require special networking considerations:
Dev Stage (Debian-based):
ENV ZEROCLAW_GATEWAY_PORT=3000
# Binds to [::] (all interfaces) by default for dev convenienceRelease Stage (Distroless):
ENV ZEROCLAW_GATEWAY_PORT=3000
# Binds to [::] (all interfaces) with explicit allow_public_bind in config.tomlBoth stages set host = "[::]" and allow_public_bind = true in their respective config templates to enable container-to-host port mapping. The security model in containerized deployments relies on Docker's network isolation rather than localhost-only binding.
Sources: Dockerfile:52-56, Dockerfile:99-105
For production deployments exposing the gateway to the internet, ZeroClaw requires a tunnel:
| Tunnel Provider | Config Value | Use Case |
|---|---|---|
cloudflare |
tunnel.provider = "cloudflare" |
Zero-trust tunneling with Cloudflare Argo |
tailscale |
tunnel.provider = "tailscale" |
Private mesh networking |
ngrok |
tunnel.provider = "ngrok" |
Quick testing and demos |
custom |
tunnel.provider = "custom" |
Custom tunnel binary (e.g., rathole, frp) |
none |
tunnel.provider = "none" |
No tunnel (localhost-only or manual setup) |
The gateway refuses to bind to public interfaces without either an active tunnel or explicit allow_public_bind = true override.
Sources: README.md:385-391, README.md:553-554
flowchart TD
subgraph "Trigger Events"
PREvent["pull_request to main<br/>(Docker paths changed)"]
PushEvent["push to main<br/>(Docker paths changed)"]
TagEvent["push tag v*"]
Manual["workflow_dispatch"]
end
subgraph "PR Flow"
PREvent --> PRJob["Job: pr-smoke"]
PRJob --> BuildSmoke["Build local image<br/>(Blacksmith builder)"]
BuildSmoke --> VerifySmoke["docker run ... --version"]
VerifySmoke --> PRDone["No push<br/>(smoke test only)"]
end
subgraph "Publish Flow"
PushEvent --> PublishJob["Job: publish"]
TagEvent --> PublishJob
Manual --> PublishJob
PublishJob --> Login["docker login ghcr.io<br/>(github.actor + GITHUB_TOKEN)"]
Login --> ComputeTags{"Compute tags"}
ComputeTags -->|main| MainTags["latest<br/>sha-<12chars>"]
ComputeTags -->|tag v*| VersionTags["vX.Y.Z<br/>sha-<12chars>"]
ComputeTags -->|other| BranchTags["branch-name<br/>sha-<12chars>"]
MainTags --> BuildPush["Build and push<br/>(linux/amd64)"]
VersionTags --> BuildPushMulti["Build and push<br/>(linux/amd64,linux/arm64)"]
BranchTags --> BuildPush
BuildPush --> SetVisibility["Set package visibility<br/>(public)"]
BuildPushMulti --> SetVisibility
SetVisibility --> VerifyAnon["Verify anonymous pull"]
end
PRDone --> Complete["Workflow complete"]
VerifyAnon --> Complete
Sources: .github/workflows/pub-docker-img.yml:1-193
The Docker publishing workflow uses different platform strategies based on the trigger:
-
PR smoke tests:
linux/amd64only (fast verification) -
Main branch pushes:
linux/amd64only (quick iteration) -
Version tag pushes (
v*):linux/amd64,linux/arm64(full multi-arch)
This optimization reduces CI time for frequent main branch updates while ensuring production releases support ARM platforms like Raspberry Pi and Apple Silicon.
Sources: .github/workflows/pub-docker-img.yml:131
Published images use the following tag scheme:
| Git Ref | Image Tags |
|---|---|
main branch |
ghcr.io/zeroclaw-labs/zeroclaw:latestghcr.io/zeroclaw-labs/zeroclaw:sha-<12chars>
|
Tag v1.2.3
|
ghcr.io/zeroclaw-labs/zeroclaw:v1.2.3ghcr.io/zeroclaw-labs/zeroclaw:sha-<12chars>
|
| Feature branch |
ghcr.io/zeroclaw-labs/zeroclaw:branch-nameghcr.io/zeroclaw-labs/zeroclaw:sha-<12chars>
|
The SHA-based tags provide immutable references for reproducible deployments, while semantic version tags enable pinning to specific releases.
Sources: .github/workflows/pub-docker-img.yml:104-123
The default docker-compose.yml includes conservative resource limits suitable for small deployments:
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
cpus: '0.5'
memory: 512MThese limits are appropriate for:
- Single-user deployments
- Development/testing environments
- Low-traffic channel integrations
For production scale (multiple channels, high message volume), adjust limits based on workload:
Sources: docker-compose.yml:42-50
| Deployment Type | CPU Reservation | Memory Reservation | CPU Limit | Memory Limit |
|---|---|---|---|---|
| Development | 0.5 | 512M | 2 | 2G |
| Production (single channel) | 1 | 1G | 4 | 4G |
| Production (multi-channel) | 2 | 2G | 8 | 8G |
| High-traffic (10+ channels) | 4 | 4G | 16 | 16G |
Docker Compose includes a health check using zeroclaw status:
healthcheck:
test: ["CMD", "zeroclaw", "status"]
interval: 60s
timeout: 10s
retries: 3
start_period: 10sFor images with curl available (dev stage), the HTTP endpoint provides a faster check:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 5sThe GET /health endpoint is always public and returns 200 OK without authentication.
Sources: docker-compose.yml:52-59
Production deployments should implement these security measures:
-
Non-root execution - Both Docker stages run as user
65534:65534 - Distroless base - Release stage uses minimal distroless image with no shell
- Secret management - Use environment variables for API keys, never commit secrets
- Volume permissions - Ensure volume mounts have appropriate ownership
- Network isolation - Use Docker networks to isolate the gateway from other services
- Tunnel-only access - Configure a tunnel (Tailscale, Cloudflare) instead of exposing ports directly
Sources: Dockerfile:87-112, README.md:385-391
For production monitoring, consider:
-
Prometheus metrics - ZeroClaw includes a
prometheusdependency for metrics export -
OpenTelemetry traces - OTLP trace and metrics export available via
opentelemetry-otlpintegration -
Log aggregation - Structured logging via
tracing-subscriberwith JSON formatting support
The Observer trait provides pluggable observability backends (Noop, Log, Multi) for custom monitoring integrations.
Sources: Cargo.toml:44-125, README.md:314
# Enable USB device enumeration
cargo build --release --features hardware
# Enable Raspberry Pi GPIO support (Linux only)
cargo build --release --features peripheral-rpi
# Enable browser automation (Rust-native fantoccini)
cargo build --release --features browser-native
# Enable debug probe support (STM32, etc.)
cargo build --release --features probe
# Enable PDF ingestion for RAG
cargo build --release --features rag-pdfThe default feature set (hardware) includes USB and serial support but excludes platform-specific peripherals to maintain cross-compilation compatibility.
Sources: Cargo.toml:144-160, README.md:126-163
# Linux: Enable Landlock sandboxing
cargo build --release --features sandbox-landlock
# Enable Bubblewrap sandboxing (requires bubblewrap binary)
cargo build --release --features sandbox-bubblewrapSandbox features are optional and platform-specific. The runtime.kind = "docker" configuration provides containerized sandboxing without requiring build-time features.
Sources: Cargo.toml:151-156
For detailed deployment instructions and production best practices, see:
- Docker Deployment - Complete Docker setup guide including docker-compose customization and multi-arch builds
- Native Binary Deployment - Cross-compilation, bootstrap script usage, and system service installation
- Production Configuration - Security hardening, resource tuning, monitoring setup, and scaling strategies
For configuration details, see Configuration and its child pages.