mensfeld/code-on-incus
Give each AI agent its own isolated machine with root, Docker, and systemd. Active defense detects and stops threats automatically. - mensfeld/code-on-incus
591 stars · 48 forks · Python Topics: cli, security, containers, sandbox, opencode, lxc, developer-tools, claude, container-security, code-sandbox, coding-assistant, ai-tools, anthropic, llm-security, llm-tools, incus, agentic-ai, claude-code, opencode-ai, sandboxing-tool
README
code-on-incus (coi)
Isolated machines for AI coding agents - with active defense. COI gives each AI agent its own machine - a full system container with root access, systemd, Docker, and the ability to install anything. Agents work like they would on a real server: run services, manage packages, use cron - without touching your actual system. Files stay correctly owned, no permission hacks needed. Your credentials stay on the host. SSH keys, environment variables, and Git tokens are never exposed to AI tools unless you explicitly mount them. If something goes wrong, COI catches it - reverse shells, credential scanning, data exfiltration - and pauses or kills the container automatically. No manual intervention needed. Built by developers, for developers who run AI agents and want to know what those agents are doing. Not a product, not a startup - a tool that does the job. Who this is for
You run AI coding agents and want them to have full machine access - root, Docker, package managers, services - without risking your host You want to know when an agent does something suspicious, not find out after the fact You run multiple agents in parallel and need them isolated from each other You want persistent dev environments that survive restarts and reboots, not throwaway containers that lose your setup every time You care about your credentials not ending up inside an agent-controlled environment
Watch the BetterStack video about Code on Incus
Table of Contents
Who this is for Supported AI Coding Tools Supported Tools (detailed) Features Quick Start Why Incus Instead of Docker or Docker Sandboxes? Installation macOS Support Usage Run Scripts and Commands in the Sandbox Session Resume Persistent Mode Configuration Profiles Resource and Time Limits Container Lifecycle & Session Persistence Network Isolation Security Monitoring Security Best Practices Snapshot Management System Health Check Troubleshooting FAQ
Supported AI Coding Tools Currently supported:
Claude Code (default) - Anthropic's official CLI tool opencode - Open-source AI coding agent (https://opencode.ai) pi - AI coding assistant (https://pi.dev)
Coming soon:
Aider - AI pair programming in your terminal Cursor - AI-first code editor And more...
Tool selection is config/profile-driven:
~/.coi/config.toml or ./.coi/config.toml
[tool] name = "opencode" # or "claude" (default), "pi" coi shell # Uses the configured tool (Claude Code by default) coi shell --profile opencode # Or switch via a profile with [tool] name = "opencode" Permission mode - Control whether AI tools run autonomously or ask before each action:~/.coi/config.toml or .coi/config.toml
[tool] name = "claude" # Default AI tool permission_mode = "bypass" # "bypass" (default) or "interactive" See the Supported Tools wiki page for detailed configuration, API key setup, and adding new tools. Features Core CapabilitiesMulti-slot support - Run parallel AI coding sessions for the same workspace with full isolation Session resume - Resume conversations with full history and credentials restored (workspace-scoped) Persistent containers - Keep containers alive between sessions (installed tools preserved) Workspace isolation - Each session mounts your project directory Slot isolation - Each parallel slot has its own home directory (files don't leak between slots) Workspace files persist even in ephemeral mode - Only the container is deleted, your work is always saved Container snapshots - Create checkpoints, rollback changes, and branch experiments with full state preservation
Host Integration
SSH agent forwarding - Use git-over-SSH inside containers without copying private keys ([ssh] forward_agent = true) Host port publishing - Publish container TCP ports on the host ([ports] pool for identity-mapped agent-usable ports, [[ports.map]] for fixed services): agent-started dev servers become reachable at localhost:<port>, with per-slot deterministic allocation, a pre-launch conflict check, and coi trust gating for untrusted project configs Host socket forwarding - Forward arbitrary host Unix sockets into the container ([[sockets]]) so the host endpoint never enters the container — the building block for credential brokers (mint short-lived tokens on the host, fetch them on demand inside). Untrusted project-config sockets are gated behind coi trust Credential catalog - Copy third-party provider credentials into the container via [[credentials]] entries (config or profile): reference a named catalog bundle (bundle = "ollama") or declare an ad-hoc host/container file pair for anything not yet cataloged. claude/opencode/pi's own credential files come from the same built-in catalog. Ad-hoc entries from an untrusted project .coi/config.toml are gated behind coi trust; catalog references carry the same trust level the built-in tool credentials already have Environment variable forwarding - Selectively forward host env vars by name (forward_env in config) Command-sourced env vars - Mint a fresh secret per session by running a host command at start and injecting its output as an env var ([defaults.env_commands]) — for short-lived API keys/tokens. Trusted-scope config only Host timezone inheritance - Containers automatically inherit the host's timezone (configurable via [timezone] config) Sandbox context file - Auto-injected ~/SANDBOX_CONTEXT.md tells AI tools about their environment (network mode, workspace path, persistence, etc.). Automatically loaded into each tool's native context system: Claude Code via ~/.claude/CLAUDE.md, OpenCode via the instructions field in opencode.json, pi via ~/.pi/agent/APPEND_SYSTEM.md symlink (opt out with auto_context = false)
Security & Isolation
Credential protection - SSH keys, .env files, Git credentials, and environment variables are never exposed unless explicitly mounted Privileged container guard - Refuses to start when security.privileged=true is detected, which defeats all container isolation Security posture verification - coi health checks seccomp, AppArmor, and privilege settings to confirm full isolation Kernel version enforcement - Warns on host kernels below 5.15 that may lack security features for safe isolation Real-time threat detection - Kernel-level nftables monitoring detects reverse shells, C2 connections, data exfiltration, DNS tunneling, and credential scanning Automated response - Auto-pause on HIGH threats, auto-kill on CRITICAL — no manual intervention needed Network isolation - nftables-based restricted/allowlist/open modes block private network access and prevent exfiltration Protected paths - .git/hooks, .git/config, .husky, .vscode mounted read-only to prevent supply-chain attacks Host-side immutable protection - Protected paths are locked with chattr +i during sessions, preventing unshare -m + umount bypass of read-only mounts (opt out: [security] host_immutable = false) Git identity guard - Containers enforce user.useConfigOnly=true, preventing AI tools from committing as the default "code" user Guest API disabled - Incus guest API (/dev/incus) disabled by default, preventing host path and topology leaks System containers - Full OS isolation with unprivileged containers, better than Docker privileged mode Automatic UID mapping - No permission hell, files owned correctly Audit logging - All security events logged to JSONL for forensics and compliance
Safe Dangerous Operations
AI coding tools often need broad filesystem access or bypass permission checks These operations are safe inside containers because the "root" is the container root, not your host system Containers are ephemeral - any changes are contained and don't affect your host This gives AI tools full capabilities while keeping your system protected
Quick Start
Install
curl -fsSL https://raw.githubusercontent.com/mensfeld/code-on-incus/master/install.sh | bashBuild image (first time only, ~5-10 minutes)
coi buildStart coding with your preferred AI tool (defaults to Claude Code)
cd your-project coi shellOr use opencode instead (config-driven: [tool] name = "opencode",
or a profile: coi shell --profile opencode)
That's it! Your AI coding assistant is now running in an isolated container with:
- Your project mounted at /workspace
- Correct file permissions (no more chown!)
- Full Docker access inside the container
- GitHub CLI available for PR/issue management
- All workspace changes persisted automatically
- No access to your host SSH keys, env vars, or credentials
Why Incus Instead of Docker or Docker Sandboxes? Incus is a modern Linux container and virtual machine manager, forked from LXD. Unlike Docker (which uses application containers), Incus provides system containers that behave like lightweight VMs with full init systems. Security ComparisonCapability code-on-incus Docker Sandbox Bare Metal
Credential isolation Default (never exposed) Partial None
Real-time threat detection Kernel-level (nftables) No No
Reverse shell detection Auto-kill No No
Data exfiltration alerts Auto-pause No No
Network isolation nftables (3 modes) Basic No
Protected paths Read-only mounts No No
Auto response (pause/kill) Yes No No
Audit logging JSONL forensics No No
Supply-chain attack prevention Git hooks/IDE configs protected No No
Why Incus Instead of Docker Sandboxes?
Linux-first, not Linux-last. Docker Sandboxes' microVM isolation is only available on macOS and Windows. Linux gets a legacy container-based fallback. COI is built for Linux from the ground up because Incus is Linux-native.
No Docker Desktop required. Docker Sandboxes is a Docker Desktop feature. Docker Desktop is not open source and has commercial licensing requirements for larger organizations. COI depends only on Incus - fully open source, no vendor lock-in, no additional runtime.
System containers, not containers-in-VMs. Incus system containers run a full OS with systemd and native Docker support inside - one clean isolation layer. Docker Sandboxes nests application containers inside microVMs, adding architectural complexity.
No permission hell. Incus automatic UID/GID shifting means files created by agents have correct ownership on the host. No mapping hacks needed. (Note: files created via sudo in the workspace will be root-owned — the sandbox context file instructs AI tools to fix ownership after sudo operations.)
Credential isolation by default. Host environment variables, SSH keys, and Git credentials are never exposed to AI tools unless explicitly mounted.
Simple and transparent. No separate daemon, no opaque VM nesting. COI talks directly to Incus - easy to inspect, debug, and extend.
Installation Automated Installation (Recommended)
One-shot install
curl -fsSL https://raw.githubusercontent.com/mensfeld/code-on-incus/master/install.sh | bashThis will:
- Download and install coi to /usr/local/bin
- Check for Incus installation
- Verify you're in incus-admin group
- Show next steps
Manual installation: Download the binary from GitHub Releases, make it executable, and move to /usr/local/bin/. Requires Linux with Incus installed and user in the incus-admin group. You must log out and back in (or run newgrp incus-admin) after adding your user to the group — COI runs incus directly and requires the group to be active in your session. See the Incus installation guide for setting up Incus. Build ImagesBuild the default coi-default image (5-10 minutes)
coi buildBuild without compression (faster iteration):
set [container.build] compression = "none" in config or the profile
coi buildBuild a custom image via a profile
coi profile create my-imageEdit .coi/profiles/my-image/config.toml: set [container] image and a [container.build] section
coi build --profile my-imageBuild images for all profiles that have a [container.build] section
coi build --allRebuild all profile images from scratch
coi build --all --force What's included in the coi-default image:Ubuntu 24.04 base with Docker (full Docker-in-container support) mise (polyglot runtime manager) — Python 3, pnpm, TypeScript, tsx pre-installed; add more with mise use go@latest, mise use ruby@3, etc. Node.js 22 LTS (system, for Claude CLI) + npm Claude Code CLI (default AI tool) + GitHub CLI (gh) tmux, git, curl, build-essential, and common build tools Modern CLI utilities: fd-find, bat, tree Debugging tools: strace, lsof Database clients: sqlite3, postgresql-client, redis-tools imagemagick for image processing
Custom images: Build your own specialized images using profile-based build scripts that run on top of the base coi-default image. See the Image Management wiki page for complete profile-based build workflows. macOS Support COI works on macOS using Colima or Lima VMs. See the macOS Setup Guide for complete instructions. Usage Basic Commands
Interactive session (defaults to Claude Code)
coi shellUse a different AI tool (config/profile-driven: [tool] name = "opencode")
coi shell --profile opencodeUse specific slot for parallel sessions
coi shell --slot 2Resume previous session
coi shell --resumeRun a command in the sandbox (streams output, propagates exit code)
coi run -- npm testRun the workspace run script (./coi-run) in the sandbox
coi runAttach to existing session
coi attachReal-time security monitoring dashboard
coi monitorView session logs (setup messages, network notices, errors)
coi logs # Auto-detect container from current workspace coi logs coi-abc123-1 -f # Tail logs liveStream the structured (JSON Lines) threat-event audit log for a session
coi audit coi-abc123-1 -fApprove out-of-workspace mounts / forwarded sockets from a project .coi/config.toml
coi trust # approve (coi trust --list to view, coi untrust to revoke)List active containers and saved sessions
coi list --all coi list --running # Only running containers (also: --stopped, --status frozen)Gracefully shutdown / force kill containers
coi shutdown coi-abc12345-1 coi kill --allCleanup stopped containers and orphaned resources
coi clean coi clean --pools # Detect containers in unused storage poolsUpdate coi to the latest release
coi updateUpgrading to 0.10? 0.10 removes all config-shaped CLI flags (--image, --persistent, --tmux, --tool, coi build --compression, coi shutdown --timeout) and the legacy CLAUDE_ON_INCUS_* / COI_LIMIT_* env-var overrides — everything config-shaped now lives in config files and profiles, and a removed flag fails with a hint naming its replacement key. See the Upgrading from 0.9 to 0.10 guide (the 0.8→0.9 notes are there too).
Container Aliases Assign human-friendly names to containers for easy management from any directory:
.coi/config.toml (in your project)
[container] alias = "myproject" coi shell myproject # Launch session using alias (from any directory) coi attach myproject # Attach to running aliased container See the Container Lifecycle and Sessions guide for full alias documentation. Global Flags --workspace PATH # Workspace directory to mount (default: current directory) --slot NUMBER # Slot number for parallel sessions (0 = auto-allocate) --resume [SESSION_ID] # Resume from session (omit ID to auto-detect latest for workspace) --continue [SESSION_ID] # Alias for --resume --profile NAME # Use named profile Everything else — image selection, persistence, network mode, mounts, socket forwarding, environment variables, SSH agent, monitoring, timezone, resource limits — is configured via config files or profiles, not flags (the former --image and --persistent flags were removed in 0.10; set [container] image / persistent instead). See the Configuration wiki page for the full reference. Advanced Usage See the wiki for detailed documentation:Container Operations - Container management and low-level operations File Transfer - Push/pull files between host and containers Tmux Automation - Automate AI sessions with tmux commands Image Management - Create and manage custom images Snapshot Management - Create checkpoints and rollback changes
Run Scripts and Commands in the Sandbox COI's isolation isn't only for AI agents — coi run executes regular commands and scripts with the same protection: workspace mount, read-only protected paths, secret masking, network isolation, resource/time limits, and security monitoring. Output streams live, stdin is connected, and the command's exit code becomes coi run's exit code.
Arbitrary commands
coi run -- npm test coi run -- make build cat data.csv | coi run -- ./process.shWorkspace run script: with no command, coi runs ./coi-run
cat > coi-run <<'EOF' #!/usr/bin/env bash set -euo pipefail npm ci && npm test EOF chmod +x coi-run coi run The run script is executed directly from the workspace mount — it comes from the host; nothing is copied into the container. It is extensionless and must be executable: the shebang decides the interpreter, so a bash, ruby, or python coi-run all work the same way. The container is cleaned up when the script finishes — or kept, with [container] persistent = true, so installed packages and caches survive between runs. Security note: a cloned repository can ship its own coi-run, so coi run in a repo you don't trust executes that repo's code — inside the sandbox, which is exactly what the sandbox is for. For untrusted projects, use a credential-limiting profile (e.g. coi run --profile hardened, or your own profile with [ssh] forward_agent = false and a restricted network mode) so the script gets no SSH agent, forwarded env, or open egress. Session Resume Resume a previous AI coding session with full history and credentials restored: coi shell --resume # Auto-detect latest session for this workspace coi shell --resume=<session-id> # Resume specific session coi list --all # List available sessions What's restored: Full conversation history, tool credentials, user settings, and project context. The profile used when the session was created is also automatically restored — no need to pass --profile again (explicitly passing --profile overrides the saved one). Sessions are workspace-scoped — --resume only finds sessions from the current workspace directory. See the Container Lifecycle and Sessions guide for details on how session persistence works. Persistent Mode By default, containers are ephemeral (deleted on exit). Your workspace files always persist regardless of mode. Enable persistent mode to also keep the container and its installed packages:~/.coi/config.toml, ./.coi/config.toml, or a profile
[container] persistent = true What persists:Ephemeral mode: Workspace files + session data (container deleted) Persistent mode: Workspace files + session data + container state + installed packages, system setup
See the Container Lifecycle and Sessions guide for details. Configuration Config file: ~/.coi/config.toml [container] image = "coi-default" persistent = true
storage_pool = "" # Empty = Incus default pool
alias = "myproject" # Human-friendly name for this workspace's containers
[tool] name = "claude" permission_mode = "bypass"
auto_context = true # Auto-inject sandbox context into tool's native system
Configuration hierarchy (highest precedence last):Built-in defaults User config (~/.coi/config.toml, or the file $COI_CONFIG points at) Project config (./.coi/config.toml) Profile (--profile <name>)
Config-shaped settings have no CLI flags and no env-var overrides — config and profiles are the single source of truth. The remaining CLI flags are per-invocation choices only: --workspace, --slot, --resume, --profile. Place a .coi/config.toml in any repository root to auto-configure COI for that project — useful for teams to share container image, environment, and resource limits.

