mensfeld/code-on-incus: Give each AI agent its own isolated machine with root, Docker, and systemd. Active defense detects a

github.com · 2026-07-14 · by Long Nguyen ·github
Give each AI agent its own isolated machine with root, Docker, and systemd. Active defense detects and stops threats automatically. - mensfeld/code-on-incus

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 Capabilities

Multi-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 | bash

Build image (first time only, ~5-10 minutes)

coi build

Start coding with your preferred AI tool (defaults to Claude Code)

cd your-project coi shell

Or 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 Comparison

Capability 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 | bash

This 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 Images

Build the default coi-default image (5-10 minutes)

coi build

Build without compression (faster iteration):

set [container.build] compression = "none" in config or the profile

coi build

Build a custom image via a profile

coi profile create my-image

Edit .coi/profiles/my-image/config.toml: set [container] image and a [container.build] section

coi build --profile my-image

Build images for all profiles that have a [container.build] section

coi build --all

Rebuild 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 shell

Use a different AI tool (config/profile-driven: [tool] name = "opencode")

coi shell --profile opencode

Use specific slot for parallel sessions

coi shell --slot 2

Resume previous session

coi shell --resume

Run a command in the sandbox (streams output, propagates exit code)

coi run -- npm test

Run the workspace run script (./coi-run) in the sandbox

coi run

Attach to existing session

coi attach

Real-time security monitoring dashboard

coi monitor

View session logs (setup messages, network notices, errors)

coi logs # Auto-detect container from current workspace coi logs coi-abc123-1 -f # Tail logs live

Stream the structured (JSON Lines) threat-event audit log for a session

coi audit coi-abc123-1 -f

Approve 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 --all

Cleanup stopped containers and orphaned resources

coi clean coi clean --pools # Detect containers in unused storage pools

Update coi to the latest release

coi update

Upgrading 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.sh

Workspace 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.

source

github.com · 7 images · 165 links

Comments (0)

No comments yet