A workable Incus setup for an agent lab is one unprivileged instance per run, created from a prepared base, with a profile that caps CPU, memory and process count, a bridge network that has no route to the outside, and a snapshot taken before the agent starts. Each run then begins from a known state, cannot reach anything it was not given, and is destroyed when it ends.
This post gives that setup as a sequence of commands, with the reason for each step and the documentation it comes from. It is the practical part of the guide to sandboxing AI agents on Linux, which also compares containers, microVMs and gVisor and explains why identical images do not give identical runs. What follows is general, public practice with standard Incus features, nothing more.
Why Incus for an agent lab?
Incus describes itself as a system container and virtual machine manager that offers a unified experience for running full Linux systems inside containers or virtual machines, with images for a large number of distributions, a REST API, and a scale that runs from one instance on a laptop to a cluster (Incus documentation, 2026). For a lab, the useful consequence is that the same profiles, images, snapshot commands and file transfer commands apply whether a run uses a container or a VM. You can start every task family in containers and move the riskier ones to VMs without changing the workflow around them.
Three features carry most of the weight here. Profiles let you define limits and devices once and apply them to every run. Managed bridge networks let you create a network with no NAT and no route out. Snapshots let you fix the state of an instance at a point in time and return to it.
Before creating anything, look at what the host already has. Networks and profiles are shared across every instance on the machine, and a new bridge on a host that already routes traffic for other work is the kind of change worth reading about first.
incus profile list
incus network list
incus storage listNothing below modifies an existing profile or network; every command creates something new with its own name.
Containers or virtual machines for each run?
Incus offers both. System containers run full Linux distributions on the host’s kernel, implemented through LXC, and their main advantage is low overhead and dense packing. Virtual machines run with their own kernel, implemented through QEMU, and are the right choice for anything that needs a different kernel, its own kernel modules, or full PCI device pass-through, which the documentation says works properly only with VMs (Incus documentation, 2026).
For most agent work, coding tasks, scripting, operations against a test service, an unprivileged container is enough and far cheaper. For tasks where an agent will run untrusted code with real capability, or where it needs a GPU, use a VM. The commands below use containers; adding --vm at launch is the only change needed to switch, though VM instances take some of the options differently and the instance options reference marks which keys are container-only (Incus instance options, 2026).
| Choice | Use for | Notes |
|---|---|---|
| Unprivileged container | Most coding and scripting tasks | Shares the host kernel; fast to create |
| Virtual machine | Untrusted code with real capability; GPU or device access | Own kernel; --vm at launch; pass-through is VM-only |
| Privileged container | Nothing in an agent lab | The security documentation warns these “aren’t root safe” |
Whichever you pick, the unit is one instance per run. Reusing an instance across runs is how one task’s leftovers become the next task’s input.
Step 1: a profile for agent runs
A profile stores configuration and devices that can be applied to any instance. The default profile, which Incus applies when you name none, defines a network interface and a root disk (Incus profiles documentation, 2026). Rather than editing it, create a separate profile for agent runs and apply only that one, so that an agent instance never inherits the default network.
incus profile create agent-runThen the limits. The instance options reference defines limits.cpu as which CPUs to expose, limits.cpu.allowance as how much of the CPU can be used (a percentage for a soft limit, or a time window such as 25ms/100ms for a hard one), limits.memory as the usage limit for the host’s memory, limits.memory.enforce as whether that limit is hard or soft (default hard), and limits.processes as the maximum number of processes that can run in the instance, with no limit by default (Incus instance options, 2026).
incus profile set agent-run limits.cpu=2 limits.cpu.allowance=100% limits.memory=4GiB limits.memory.enforce=hard limits.processes=1024The process cap is the one people forget. An agent that runs a test suite in parallel, or a build that forks without bound, degrades its own instance under this setting instead of the host. Choose a number that leaves room for a real build; 1024 is a starting point, not a recommendation for every workload.
Now the security keys. security.privileged defaults to false and should stay that way. security.nesting controls whether the instance may run its own containers; it defaults to false for containers, and an agent rarely needs it. security.idmap.isolated gives the instance a unique user-ID map rather than the shared one, which the security documentation describes as a protection against one container interfering with another (Incus security documentation, 2026; Incus instance options, 2026).
incus profile set agent-run security.privileged=false security.nesting=false security.idmap.isolated=trueFinally the root disk, which the profile must provide since the default profile is not applied. The disk device takes a storage pool and a mount path; a size setting on the root device sets a quota where the storage driver supports one.
incus profile device add agent-run root disk pool=default path=/ size=20GiBReplace default with the pool from incus storage list if yours is named differently.
Step 2: a network with no route out
A managed bridge in Incus is created with incus network create, and the bridge type is used when no type is given (Incus bridge network reference, 2026). The bridge reference lists the keys that decide whether instances on it can reach anything else. ipv4.address sets the bridge’s subnet and accepts none to turn the protocol off or auto to pick an unused one; ipv4.nat controls whether traffic is NATed, and defaults to true on creation when the address is auto; ipv4.routing controls whether traffic is routed in and out of the bridge and defaults to true. The IPv6 keys mirror these (Incus bridge network reference, 2026).
For an agent network, give the bridge a fixed address so the host can reach the instances, turn NAT off, and turn IPv6 off entirely to keep the configuration small.
incus network create agentlab0 ipv4.address=10.144.0.1/24 ipv4.nat=false ipv4.routing=false ipv6.address=noneWith NAT off, instances have no masqueraded path to the host’s uplink. With routing off, the host does not forward packets between the bridge and its other interfaces. The host itself remains directly connected to the bridge, which is what you want for pushing files in and pulling results out.
Check the result from inside an instance before relying on it. A quick test is to launch a throwaway container on the new network and confirm that a connection to an address outside the bridge fails, and that name resolution for public hosts does not succeed. Isolation that has not been tested from the inside is an assumption.
Then attach the network to the profile as the instance’s only interface.
incus profile device add agent-run eth0 nic network=agentlab0The bridge reference also documents network ACLs, with security.acls and default ingress and egress actions that reject unmatched traffic (Incus bridge network reference, 2026). When a task family genuinely needs one outside service, a package mirror you run or a test API, an ACL that allows that destination and rejects everything else is the way to add it, rather than switching NAT back on.
Step 3: a prepared base and one instance per run
Launch a base instance from an image with the profile, install the toolchain the agent needs, and stop it. The create-instances page gives the launch form as an image server, an image name and an instance name, with images:debian/12 as its example (Incus create instances documentation, 2026).
incus launch images:debian/12 agent-base --profile agent-runBecause the base is on the isolated network, package installation during preparation has to come from somewhere it can reach: a local mirror on the bridge, files pushed in with incus file push, or a temporary second interface on a routed network that you remove again before the base is finalised. The last option is the simplest and the one most likely to be forgotten; check the device list with incus config show agent-base before stopping.
Once prepared, stop the base and protect it from deletion. The instance options reference lists security.protection.delete, which prevents the instance from being deleted (Incus instance options, 2026).
incus stop agent-base
incus config set agent-base security.protection.delete=trueEach run then gets its own copy of the base, named for the run.
incus copy agent-base run-0001
incus start run-0001The copy carries the base’s profile, so the new instance has the same limits, the same isolated network and the same root disk size. Give the agent a non-root user inside the instance and a working directory, and keep the task’s inputs in that directory rather than anywhere the agent might confuse with the system.
Rebuild the base on a schedule rather than patching it in place. A base that is recreated from a short script is documented; a base that has been hand-adjusted for a year is accumulated.
Step 4: a snapshot before the run, and one after
The snapshot command is incus snapshot create <instance> [<snapshot name>]; incus info <instance> lists an instance’s snapshots, incus snapshot restore <instance> <snapshot> returns the instance to one, and incus snapshot delete removes one. The backup documentation notes that --stateful snapshots, which capture running state, are fully supported only for VMs (Incus snapshots documentation, 2026). For agent runs, the plain filesystem snapshot is what you want.
incus snapshot create run-0001 before-runThis is the step that makes “one instance per run” reliable. If the agent damages its environment partway through and you want to re-run the task from the same start, incus snapshot restore run-0001 before-run puts it back. If you want to compare the state the agent started with against the state it left, you have both.
Run the task. The exact command depends on your harness; the general form is to execute it inside the instance as the agent’s user, with the task files already in place.
incus exec run-0001 -- su - agent -c "cd /home/agent/task && ./run.sh"When the agent reports that it has finished, take the second snapshot.
incus snapshot create run-0001 after-runThe instance options reference also lists snapshots.schedule, which takes a cron expression or aliases such as @daily, and snapshots.expiry, which sets when new snapshots are deleted using an expression such as 1d or 2w (Incus instance options, 2026). Scheduled snapshots are useful for a long-running agent session; expiry keeps a busy lab’s storage from filling with snapshots nobody will revisit.
Step 5: results out, instance gone
Pull the agent’s output directory to the host. The file-access documentation gives incus file pull <instance>/<path> <local path>, with -r for a directory and its contents (Incus file access documentation, 2026).
incus file pull -r run-0001/home/agent/task/out ./results/run-0001/If the whole instance is worth keeping, export it. The backup page gives incus export <instance> [<file path>], with --instance-only to leave out snapshots and --optimized-storage for btrfs and ZFS pools (Incus snapshots documentation, 2026).
incus export run-0001 ./results/run-0001.tar.gzThen stop and delete. The instance-management documentation warns that deletion permanently removes the instance and all its snapshots, and suggests the alias incus alias add delete "delete -i" to make the command ask first (Incus manage instances documentation, 2026). The base is safe because of its deletion protection; the run instance is meant to go.
incus stop run-0001
incus delete run-0001Incus also has --ephemeral at launch, which deletes the instance automatically when it stops (Incus create instances documentation, 2026). It is convenient for throwaway experiments, but it does not fit a workflow that wants the after-run snapshot and a deliberate export before the instance disappears.
The whole cycle, from copy to delete, is short enough to script, and it should be. The value of the setup is that every run goes through the same steps; a run that skipped the snapshot because someone was in a hurry is a run you cannot return to.
What to watch for
A few things undo the setup if left alone.
The Incus socket. The security documentation states that local access to Incus through its Unix socket always grants full access, and that this access is limited to root and members of the incus-admin group (Incus security documentation, 2026). Never mount that socket into an instance, and never run the agent as a user in that group. An agent with the socket can create a privileged instance with the host’s root filesystem mounted, which is a sandbox escape by configuration rather than by exploit.
Disk devices from the host. Adding a host directory to an instance is one command, and it is the easiest way to leak something in or let something out. The misconfiguration and privilege-allocation categories that SandboxEscapeBench tests are exactly these kinds of mistakes, and its authors report that when such vulnerabilities are present, models find and use them (SandboxEscapeBench, 2026). If a task needs input files, push them with incus file push so the instance holds a copy, not a mount.
The default profile. Launching without --profile agent-run gives the default profile and, with it, the default network, which on most hosts routes out. Make the run script fail if the profile is not named.
Privileged and nesting. Both are per-instance keys that live-update, so a well-meaning fix to get some tool working can switch them on. Keep them false in the profile and check incus config show --expanded on any instance that behaves unexpectedly.
Credentials. Nothing in Incus stops an agent from reading a token that was pushed into its instance. Give each run only the credentials its task needs, scoped to the test service on the bridge, and treat anything placed in the instance as something the agent will find.
Remote API. Remote access to the daemon is off by default and requires TLS; the documentation recommends restricting it with firewall rules (Incus security documentation, 2026). Leave it off on a lab host unless something needs it.
What this means for teams running agents
A lab does not need special software to run agents safely; it needs a routine that it never skips. One unprivileged instance per run, from a base that is rebuilt rather than patched. A profile that limits CPU, memory and processes so the agent can only hurt its own box. A bridge with NAT and routing off, tested from the inside, with ACLs for the rare task that needs one outside service. A snapshot before the agent starts, another when it says it is done, results pulled out, instance deleted. All of it is documented Incus behaviour, and all of it fits in a short script. We are building task environments on exactly this kind of foundation; the environments page describes what that work is for.
For the reasoning behind each choice, the isolation trade-offs between containers, gVisor and microVMs, and why no two runs of the same image behave alike, see the full guide to sandboxing AI agents on Linux. Evidence Every factual claim on this page links to one of these. Numbers in brackets refer to this list. Why two runs of an AI agent on one container image diverge: inference is nondeterministic even at temperature 0, precision matters and timing differs. How containers, gVisor and Firecracker-style microVMs isolate an AI agent, what each boundary costs, and which to pick when the agent runs untrusted code.Sources
More on this topic
Same image, same behaviour? Not for agent runs
Containers, microVMs or gVisor for AI agents?