Dev Container Documentation

May 30, 2026 ยท View on GitHub

The cuti dev container provides a fully configured development environment with cuti, Claude Code mode as the default, and an explicit OpenClaw mode for the OpenClaw runtime plus configured add-ons.

Quick Start

# Start an interactive container session (works from any directory)
cuti container

# Run a specific command in the container
cuti container "cuti web"
cuti container "claude 'Explain this project'"

Key Features

๐Ÿ” Persistent Claude Authentication

  • Separate Linux credentials in ~/.cuti/claude-linux/
  • No conflicts with macOS Keychain
  • One-time authentication for all containers
  • ๐Ÿ“š Complete Authentication Guide

๐Ÿค– Agent Providers

  • Claude Code mode is enabled by default for cuti container
  • OpenClaw mode is opt-in with cuti container --openclaw or cuti container --claw
  • OpenClaw mode starts with OpenClaw only; add-ons are read from cuti providers enable ...
  • Provider-specific auth, config, and skills directories are mounted automatically
  • Provider CLIs are installed through their current standalone/native install paths at container startup
  • Claude Code mode refreshes Claude Code into the persistent provider runtime on each cuti container startup
  • Hermes is available as an experimental provider track in cuti

๐ŸŽฏ Smart Container Selection

  • Universal Container (cuti-dev-universal): Used when running from any project directory

    • Installs cuti from PyPI via uv tool install
    • Perfect for using cuti with any project
    • Pre-configured with all tools
  • Development Container (cuti-dev-cuti): Used when running from cuti source directory

    • Installs cuti from local source code
    • Ideal for cuti development and testing
    • Includes development dependencies

๐ŸŽจ Custom Environment

  • Custom prompt: cuti:~/path $ for clear container identification
  • Pre-configured with oh-my-zsh for better terminal experience
  • All cuti commands available immediately
  • Python 3.11, Node.js 20, and common development tools pre-installed

๐Ÿณ Docker-in-Docker Support

  • Docker CLI installed in container (not the daemon)
  • Host Docker socket mounted at /var/run/docker.sock
  • Automatic sudo wrapper if needed for permissions
  • Run Docker commands that execute on host's Docker daemon
  • Build images, run containers, use docker-compose - all from within the container
  • Perfect for projects that need containerization during development

Prerequisites

macOS

# Install Colima (recommended Docker alternative for Mac)
brew install colima

# Start Colima
colima start

# Or use Docker Desktop
# Download from https://www.docker.com/products/docker-desktop

Linux

# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh

# Add user to docker group
sudo usermod -aG docker $USER
newgrp docker

Windows

Usage Examples

Interactive Development

# Start container from any project
cd ~/my-project
cuti container

# You'll see the custom prompt:
# cuti:/workspace $ 

# Inside container, all commands work:
cuti web          # Start read-only ops console (accessible at http://localhost:8000)
cuti cli          # Start interactive CLI
cuti agent list   # List available agents
claude --help     # Use Claude CLI (already authenticated)

Non-Interactive Commands

# Run cuti commands
cuti container "cuti add 'Review this code and suggest improvements'"
cuti container "cuti start"
cuti container "cuti status"

# Run Claude directly
cuti container "claude 'What does this project do?'"

# Add providers to the default Claude Code container mode
cuti providers enable codex
cuti providers enable opencode
cuti providers enable hermes
cuti container --rebuild
cuti container "codex --version && opencode --version && hermes version"

# Start OpenClaw mode explicitly
cuti container --openclaw

# OpenClaw add-ons come from explicit provider config
cuti providers enable claude
cuti providers enable codex
cuti container --openclaw "openclaw --version && claude --version && codex --version"

# Run Python scripts
cuti container "python script.py"

Ops Console

# Start the read-only ops console in container (accessible from host)
cuti container "cuti web"
# Then open http://localhost:8000 in your browser

# Run development servers
cuti container "npm run dev"
cuti container "python manage.py runserver"

Claude CLI Configuration

The container comes with Claude CLI pre-configured for optimal usage:

Automatic Permissions Bypass

  • Built-in Alias: claude automatically includes --dangerously-skip-permissions
  • No need to manually add the flag for every command
  • Works seamlessly in the containerized environment
# These commands are equivalent in the container:
claude "Explain this code"                              # Uses alias (recommended)
claude --dangerously-skip-permissions "Explain this code"  # Explicit flag (not needed)

Why This Matters

  • The --dangerously-skip-permissions flag is required in containers
  • Without it, Claude may fail with permission errors
  • The alias ensures Claude always works correctly
  • Simplifies usage and prevents common errors

Rebuild to Apply Updates

If you're using an older container image, rebuild to get the alias:

cuti container --rebuild

๐Ÿค– Agent Providers

Claude Code mode is the default for cuti container. OpenClaw mode is explicit and does not join the default container just because OpenClaw has been enabled in provider config:

cuti providers list
cuti providers doctor

# Default Claude Code mode. Claude Code is refreshed for subsequent containers.
cuti container

# Explicit OpenClaw mode.
cuti container --openclaw

# OpenClaw mode add-ons.
cuti providers enable claude
cuti providers enable codex
cuti providers enable opencode
cuti providers enable hermes
cuti container --openclaw

cuti providers auth claude --login
qt-openclaw onboard
qt-openclaw up
cuti providers update codex
cuti providers update openclaw
cuti providers update hermes

The standard cloud profile exports provider metadata into the container:

  • CUTI_CONTAINER_MODE is claude-code or openclaw
  • CUTI_AGENT_PROVIDERS contains the enabled provider IDs
  • CUTI_PRIMARY_AGENT_PROVIDER is claude in Claude Code mode and openclaw in OpenClaw mode
  • CODEX_HOME, OPENCLAW_STATE_DIR, OPENCLAW_CONFIG_PATH, OPENCLAW_PREFIX, HERMES_HOME, XDG_CONFIG_HOME, and XDG_DATA_HOME are set for provider state

Provider-specific mounts are added automatically when the provider is enabled:

ProviderHost PathContainer PathPurpose
Claude~/.cuti/claude-linux//home/cuti/.claude-linuxLinux Claude credentials
Provider runtimes~/.cuti/provider-runtimes//home/cuti/.cuti-providersPersistent provider CLI installs
Claude~/.claude//home/cuti/.claude-macosmacOS config reference (read-only)
Codex~/.codex//home/cuti/.codexAuth, config, skills
OpenCode~/.opencode//home/cuti/.opencodeCLI install and provider state
OpenCode~/.config/opencode//home/cuti/.config/opencodeConfig
OpenCode~/.local/share/opencode//home/cuti/.local/share/opencodeData
OpenClaw~/.openclaw//home/cuti/.openclawGateway config, credentials, agents, plugins, browser, voice-call, logs, media
Hermes Agent~/.hermes//home/cuti/.hermesConfig, .env, memory, skills, sessions, gateway state
Hermes/OpenClaw migration~/.openclaw//home/cuti/.openclawRead-only migration source when Hermes is enabled without OpenClaw
Hermes Claude auth reuse~/.claude//home/cuti/.claudeRead-only Claude Code credentials for Hermes native Anthropic auth
Shared agent files~/.agents//home/cuti/.agentsShared provider skills/prompts

Install behavior is provider-specific:

  • Claude uses the official native installer
  • Codex uses the official standalone installer
  • OpenCode uses the official install script
  • OpenClaw uses the official local-prefix installer when available, falls back to npm prefix install, stores the CLI under ~/.cuti/provider-runtimes/openclaw/, and runs openclaw doctor --non-interactive after install/update/bootstrap
  • Hermes Agent uses the official NousResearch installer with HERMES_HOME=/home/cuti/.hermes

OpenClaw-specific notes:

  • qt-openclaw ... and qt-OpenClaw ... run the same dedicated OpenClaw command group from the host.
  • qt-openclaw onboard runs openclaw onboard --install-daemon in the container, then runs openclaw doctor --non-interactive.
  • qt-openclaw up runs onboarding if no OpenClaw state is detected, runs doctor, then starts the gateway in the foreground.
  • Channel, browser, plugin, voice-call, and dashboard surfaces are exposed as direct wrappers: qt-openclaw channels-login, qt-openclaw channels ..., qt-openclaw browser ..., qt-openclaw plugins ..., qt-openclaw voice-setup, qt-openclaw voicecall ..., and qt-openclaw dashboard ....
  • Source-backed OpenClaw command families are also exposed directly: setup/config/configure, backup/reset/uninstall, message/agent/agents, status/health/sessions, tasks/flows/cron, models/infer/capability, ACP/MCP, approvals/exec-policy, nodes/devices/node, sandbox, TUI/chat/terminal, DNS/docs/proxy, hooks/webhooks, QR/pairing/directory, security/secrets/skills, update/completion, memory, and wiki.
  • Future plugin command roots remain supported through qt-openclaw run <command> ..., which forwards raw arguments to the installed OpenClaw CLI after cuti has enabled the provider and mounted state.
  • Managed OpenClaw state persists under ~/.openclaw; the CLI/runtime install persists separately under ~/.cuti/provider-runtimes/openclaw.
  • cuti sets OpenClaw's container environment to the mounted state tree: OPENCLAW_HOME=/home/cuti, OPENCLAW_STATE_DIR=/home/cuti/.openclaw, OPENCLAW_CONFIG_PATH=/home/cuti/.openclaw/openclaw.json, OPENCLAW_OAUTH_DIR=/home/cuti/.openclaw/credentials, OPENCLAW_PREFIX=/home/cuti/.cuti-providers/openclaw, NPM_CONFIG_PREFIX=/home/cuti/.cuti-providers/openclaw, npm_config_prefix=/home/cuti/.cuti-providers/openclaw, and PLAYWRIGHT_BROWSERS_PATH=/home/cuti/.openclaw/browser/browsers.
  • Containerized browser automation works best with OpenClaw-managed or remote-CDP browser profiles. Attaching to an already signed-in host browser still depends on host-local browser access.
  • Voice-call setup installs/enables the official @openclaw/voice-call plugin, but telephony providers still require their own credentials and reachable webhook/tunnel setup.

Hermes-specific notes:

  • Hermes is marked experimental in cuti providers list / status
  • cuti providers auth hermes --login runs hermes setup, which follows the upstream first-run wizard and can detect OpenClaw state for migration
  • cuti providers update hermes follows Hermes' native hermes update flow inside the container, so upstream dependency refresh, config checks, and bundled skill sync happen the same way Hermes documents them
  • If ~/.openclaw exists, run hermes claw migrate --dry-run inside the container to preview migration of OpenClaw persona, memory, skills, messaging settings, API keys, and workspace instructions before applying it
  • Hermes project context uses .hermes.md, HERMES.md, AGENTS.md, and CLAUDE.md; durable personality belongs in ~/.hermes/SOUL.md
  • Hermes profiles persist under ~/.hermes/profiles, and cuti recreates the lightweight ~/.local/bin/<profile> wrappers on container startup so profile aliases continue to work after container restarts

Host-side provider commands:

  • cuti providers list shows selection plus setup summary
  • cuti providers status <provider> shows detailed host/container state
  • cuti providers doctor checks readiness across providers
  • cuti providers auth <provider> --login launches the provider's interactive setup flow inside the container
  • cuti providers update <provider> refreshes that provider in persistent provider runtime storage and updates active cuti cloud containers in place

Docker-in-Docker Usage

# Build Docker images inside the container
cuti container "docker build -t myapp ."

# Run containers from within the container
cuti container "docker run -d redis:latest"
cuti container "docker ps"

# Use docker-compose
cuti container "docker-compose up -d"

# Interactive container with Docker support
cuti container
# Inside container:
docker images
docker run hello-world
docker-compose --version

Architecture

Container Images

cuti-dev-universal (Default for most projects)

  • Base: python:3.11-bullseye
  • Cuti: Installed from PyPI via uv tool install cuti
  • Agent providers: Claude preinstalled, other enabled providers installed at runtime
  • Docker CLI: Installed for Docker-in-Docker support
  • Tools: git, zsh, ripgrep, fd-find, bat, jq, curl, wget
  • Python: uv package manager, pytest, httpx, fastapi, uvicorn
  • Node.js: v20 with latest npm
  • Shell: Zsh with oh-my-zsh and custom cuti prompt

cuti-dev-cuti (For cuti development)

  • Same base as universal
  • Cuti: Installed from local source code
  • Includes all cuti development dependencies
  • Used automatically when running from cuti source directory

Volume Mounts

The container automatically mounts:

Host PathContainer PathPurpose
Current directory/workspaceYour project files
~/.cuti//home/cuti/.cuti-sharedShared cuti config and account state
/var/run/docker.sock/var/run/docker.sockDocker socket for Docker-in-Docker

Additional provider mounts are added only for enabled providers.

Environment Variables

Automatically set in container:

  • CUTI_IN_CONTAINER=true - Indicates running in container
  • CUTI_AGENT_PROVIDERS - Comma-separated enabled providers
  • CUTI_PRIMARY_AGENT_PROVIDER - Primary provider for prompts/help text
  • CLAUDE_CONFIG_DIR=/home/cuti/.claude-linux - Linux Claude config location
  • CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS=true - Skip permission checks
  • CODEX_HOME=/home/cuti/.codex - Codex state root
  • HERMES_HOME=/home/cuti/.hermes - Hermes Agent config/state root
  • HERMES_INSTALL_DIR=/home/cuti/.hermes/hermes-agent - persisted Hermes Agent installation
  • XDG_CONFIG_HOME=/home/cuti/.config - Shared XDG config root
  • XDG_DATA_HOME=/home/cuti/.local/share - Shared XDG data root
  • PYTHONUNBUFFERED=1 - Python unbuffered output
  • TERM=xterm-256color - Color terminal support
  • ANTHROPIC_API_KEY - Loaded from environment or file if available

Network Configuration

Container runs with --network host for easy service access:

  • Port 8000: cuti ops console
  • Port 8080: Alternative web services
  • Port 3000: Frontend dev servers
  • Port 5000: Flask/FastAPI apps
  • Port 5173: Vite dev server

Project Type Detection

When generating dev containers, cuti automatically detects:

Project TypeDetection FilesAdditional Tools
Pythonpyproject.toml, requirements.txtpytest, black, ruff, mypy
JavaScriptpackage.jsonyarn, pnpm, typescript, nodemon
Full-stackBoth Python + JS filesAll Python + JS tools
RubyGemfileRuby, Bundler
Gogo.modGo 1.21+
RustCargo.tomlRust, Cargo
GeneralNone of aboveBasic dev tools

Troubleshooting

Docker Socket Issues After Container Exit (Fixed)

Previous versions had an issue where exiting the container would break the Docker socket connection on macOS with Colima. This has been fixed by:

  • Using --init flag for proper signal handling
  • Installing only Docker CLI (not the full Docker engine)
  • Adding proper signal traps for clean exit

If you experience this issue with an older version, restart Colima:

colima restart

Container Won't Start

  1. Check Docker/Colima is running:
# For Colima
colima status

# For Docker
docker version
  1. Start if needed:
# Colima (macOS)
colima start

# Docker Desktop - start from application
  1. Verify image exists:
docker images | grep cuti-dev

Claude Authentication

โœ… Solved: Claude authentication persists across all containers using separate Linux credentials.

Quick Setup:

  1. Run cuti container
  2. Inside container, run claude login (first time only)
  3. Authentication persists for all future containers

How it works:

  • Linux credentials stored in ~/.cuti/claude-linux/
  • macOS credentials remain in Keychain (no conflicts)
  • Settings copied from macOS automatically

๐Ÿ“š For detailed setup and troubleshooting, see Claude Container Authentication Guide

Cuti Not Found in Container

  1. Check which container is being used:
docker images | grep cuti-dev
  1. Force rebuild:
# Remove old images
docker rmi cuti-dev-universal cuti-dev-cuti

# Rebuild
cuti container
  1. Verify installation:
docker run --rm cuti-dev-universal which cuti
docker run --rm cuti-dev-universal cuti --help

"input device is not a TTY" Error

This occurs in non-interactive environments (like Claude Code). The container automatically detects and handles this - no action needed.

Permission Issues

The container runs as root for simplicity. To fix file ownership after container use:

# On host after exiting container
sudo chown -R $(whoami) .

Port Already in Use

If port 8000 is already in use:

# Find what's using the port
lsof -i :8000

# Kill the process or use different port
cuti container "cuti web --port 8001"

Docker-in-Docker Access

How Docker access works in the container:

  • The container automatically detects if Docker socket requires sudo
  • If needed, creates a wrapper script that adds sudo automatically
  • You can use docker commands normally - sudo is handled transparently

Docker commands work normally:

# Inside container - these all work
docker ps
docker build -t myapp .
docker-compose up -d

If Docker still shows "permission denied":

  1. The container should auto-configure this, but if not:
    sudo docker ps  # Test with sudo
    
  2. Exit and restart the container - the wrapper will be created

Docker daemon not accessible:

  • Ensure Docker/Colima is running on your host machine
  • On macOS with Colima: colima status and colima start if needed
  • On Linux: sudo systemctl status docker

Colima Specific Issues (macOS)

Colima won't start:

# Stop and clean
colima stop -f
colima delete

# Start with specific settings
colima start --arch aarch64 --vm-type vz --cpu 2 --memory 4

Docker commands fail after Colima start:

# Check Docker context
docker context ls

# Set to use Colima
docker context use colima

Advanced Usage

Custom Dockerfile

After generating dev container files:

# Generate container configuration
cuti devcontainer generate

# Edit .devcontainer/Dockerfile to add custom tools
# Then rebuild
docker build -t my-custom-cuti -f .devcontainer/Dockerfile .

VS Code Integration

  1. Install "Dev Containers" extension
  2. Run cuti devcontainer generate in your project
  3. Command Palette: "Dev Containers: Reopen in Container"
  4. VS Code uses the generated configuration

Running Services

# Database in background
cuti container "docker run -d postgres:15"

# Redis
cuti container "docker run -d redis:7"

# Your app
cuti container "cuti web"

Persistent Data

Create named volumes for persistence:

# Create volume
docker volume create myproject-data

# Use in container
docker run -v myproject-data:/data ...

Command Reference

Main Commands

# Interactive container
cuti container

# Run specific command
cuti container "COMMAND"

# Generate dev container files
cuti devcontainer generate [--type TYPE]

# Clean up
cuti devcontainer clean

Docker Management

# List cuti images
docker images | grep cuti

# Remove cuti images
docker rmi cuti-dev-universal cuti-dev-cuti

# Clean all Docker resources
docker system prune -a

# Check container logs
docker logs [container-name]

# Execute command in running container
docker exec -it [container-name] bash

Implementation Details

Container Build Process

  1. Detection: Determines if running from cuti source or other project
  2. Image Selection: Chooses cuti-dev-universal or cuti-dev-cuti
  3. Build Check: Checks if image exists, builds if missing
  4. Mount Setup: Configures volume mounts for project and configs
  5. Environment: Sets environment variables
  6. Execution: Runs container with appropriate flags

File Locations

  • Dockerfile templates: src/cuti/services/devcontainer.py
  • Universal Dockerfile: .devcontainer/Dockerfile.universal
  • Development Dockerfile: .devcontainer/Dockerfile
  • Generated files: .devcontainer/ in your project

Security Considerations

  • Containers do not run with --privileged
  • Claude automatically uses --dangerously-skip-permissions via alias (see Claude CLI Configuration)
  • Config directories mounted with appropriate permissions
  • No telemetry or external connections
  • Local-first approach

Contributing

To improve dev container support:

  1. Edit src/cuti/services/devcontainer.py
  2. Update Dockerfile templates
  3. Test with both container types:
    # Test universal container
    cd ~/some-project
    cuti container
    
    # Test development container  
    cd ~/cuti-source
    cuti container
    
  4. Submit PR with test results

License

Part of the cuti project - MIT License