Skip to content

13 Deployment

Nikolay Vyahhi edited this page Feb 19, 2026 · 2 revisions

Deployment

Relevant source files

The following files were used as context for generating this wiki page:

Purpose and Scope

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:

Deployment Options

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

Docker Deployment

The Docker image uses a multi-stage build process with three distinct stages:

  1. Builder stage (rust:1.93-slim) - Compiles the release binary with dependency caching
  2. Dev stage (debian:trixie-slim) - Development runtime with Ollama defaults and debugging tools
  3. 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 Binary Deployment

Native binaries are built using Cargo with size-optimized profiles:

  • release profile - Optimized for size (opt-level = "z") with serialized codegen (codegen-units = 1) for low-memory devices like Raspberry Pi
  • release-fast profile - Parallel codegen (codegen-units = 8) for faster builds on high-memory machines (16GB+ RAM)
  • dist profile - 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

Bootstrap Script

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 openrouter

The script handles dependency installation, Rust toolchain setup, compilation, and optional onboarding configuration.

Sources: README.md:172-192

Build Process and Optimization

Deployment Build Flow

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
Loading

Sources: Dockerfile:4-113, Cargo.toml:161-181, README.md:172-198

Build Profile Configuration

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 size

High-Memory Machines (16GB+ RAM)

[profile.release-fast]
inherits = "release"
codegen-units = 8       # Parallel codegen for faster builds

The 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

Docker Multi-Stage Build

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"]
Loading

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

Configuration Management

Configuration File Locations

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)"]
Loading

Sources: README.md:258-262, README.md:493-599

Docker Configuration

Docker deployments manage configuration through three mechanisms:

  1. Base config file - Baked into image at /zeroclaw-data/.zeroclaw/config.toml
  2. Environment variables - Override config values at runtime
  3. 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

Environment Variable Precedence

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_KEY or ZEROCLAW_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 to 0.0.0.0/::

Sources: README.md:493-599, docker-compose.yml:18-32

Gateway and Networking

Gateway Binding Strategy

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)"]
Loading

Sources: README.md:385-391, README.md:526-530

Docker Networking Configuration

Docker deployments require special networking considerations:

Dev Stage (Debian-based):

ENV ZEROCLAW_GATEWAY_PORT=3000
# Binds to [::] (all interfaces) by default for dev convenience

Release Stage (Distroless):

ENV ZEROCLAW_GATEWAY_PORT=3000
# Binds to [::] (all interfaces) with explicit allow_public_bind in config.toml

Both 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

Tunnel Integration

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

Docker Image Publishing

Publishing Workflow

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
Loading

Sources: .github/workflows/pub-docker-img.yml:1-193

Platform Strategy

The Docker publishing workflow uses different platform strategies based on the trigger:

  • PR smoke tests: linux/amd64 only (fast verification)
  • Main branch pushes: linux/amd64 only (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

Image Tags and Registry

Published images use the following tag scheme:

Git Ref Image Tags
main branch ghcr.io/zeroclaw-labs/zeroclaw:latest
ghcr.io/zeroclaw-labs/zeroclaw:sha-<12chars>
Tag v1.2.3 ghcr.io/zeroclaw-labs/zeroclaw:v1.2.3
ghcr.io/zeroclaw-labs/zeroclaw:sha-<12chars>
Feature branch ghcr.io/zeroclaw-labs/zeroclaw:branch-name
ghcr.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

Production Deployment Considerations

Resource Limits

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: 512M

These 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

Health Checks

Docker Compose includes a health check using zeroclaw status:

healthcheck:
  test: ["CMD", "zeroclaw", "status"]
  interval: 60s
  timeout: 10s
  retries: 3
  start_period: 10s

For 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: 5s

The GET /health endpoint is always public and returns 200 OK without authentication.

Sources: docker-compose.yml:52-59

Security Hardening

Production deployments should implement these security measures:

  1. Non-root execution - Both Docker stages run as user 65534:65534
  2. Distroless base - Release stage uses minimal distroless image with no shell
  3. Secret management - Use environment variables for API keys, never commit secrets
  4. Volume permissions - Ensure volume mounts have appropriate ownership
  5. Network isolation - Use Docker networks to isolate the gateway from other services
  6. Tunnel-only access - Configure a tunnel (Tailscale, Cloudflare) instead of exposing ports directly

Sources: Dockerfile:87-112, README.md:385-391

Observability

For production monitoring, consider:

  • Prometheus metrics - ZeroClaw includes a prometheus dependency for metrics export
  • OpenTelemetry traces - OTLP trace and metrics export available via opentelemetry-otlp integration
  • Log aggregation - Structured logging via tracing-subscriber with 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

Feature Flags and Optional Components

Hardware Features

# 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-pdf

The 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

Sandbox Features

# Linux: Enable Landlock sandboxing
cargo build --release --features sandbox-landlock

# Enable Bubblewrap sandboxing (requires bubblewrap binary)
cargo build --release --features sandbox-bubblewrap

Sandbox features are optional and platform-specific. The runtime.kind = "docker" configuration provides containerized sandboxing without requiring build-time features.

Sources: Cargo.toml:151-156

Next Steps

For detailed deployment instructions and production best practices, see:

For configuration details, see Configuration and its child pages.


Clone this wiki locally