Isolated Linux environments that behave like bare-metal machines, not stripped-down application containers.
Unlike Docker/Podman containers, which package a single application with a minimal filesystem, incus-spawn creates full system containers powered by Incus. Each environment runs its own init system, has real networking with working ping, traceroute, and strace, can run nested containers (Podman/Docker inside), and supports GUI and audio passthrough via Wayland. For untrusted code, KVM virtual machines provide hardware-level isolation with a separate kernel.
Primary use cases:
- Running untrusted AI agents (Claude Code, etc.) in isolated environments with pre-configured auth
- Reproducing bug reports from external contributors without risking your host
- Creating reproducible development environments with pre-cloned repos and cached dependencies
Built with Quarkus and Tamboui.
- Linux -- Incus system containers require a Linux kernel. macOS and Windows are not yet supported but are on the roadmap (likely via a managed Linux VM).
- Incus --
isx initauto-installs via the detected package manager (dnf,apt,zypper, orpacman); on other distros, install manually before running init
If on a Linux X64 machine, you can install incus-spawn with the following command:
# Install
curl -fsSL https://raw.githubusercontent.com/Sanne/incus-spawn/main/get-isx.sh | shIf on any other machine, you can install incus-spawn with the following command:
jbang app install isx@Sanne/incus-spawn# One-time host setup (Incus, firewall, auth)
isx init
# Build a template (builds parent images automatically)
isx build tpl-java
# Launch the interactive TUI
isxFedora users can also install via dnf, and JBang users via jbang — see Installation for all options.
Like git branch, branching creates an instant copy-on-write clone of any template. Each branch has its own independent filesystem -- changes in one branch cannot affect the template or any other branch. The storage backend (btrfs/zfs/lvm) deduplicates unchanged data automatically, so branches are instant to create and only consume disk space for their own modifications. isx init automatically creates a btrfs storage pool if needed.
tpl-java (stopped template, ~2GB)
├── fix-nasty-bug (running, uses ~50MB extra)
├── review-pr-423 (running, uses ~30MB extra)
└── experiment (stopped, uses ~10MB extra)
You can install packages, break things, and destroy a branch when done. The template and other branches are completely unaffected.
Branches can optionally enable GUI/audio passthrough (Wayland), restricted networking, or an inbox mount to share files read-only from the host.
API keys and tokens never enter containers in any form. A host-side MITM TLS proxy (isx proxy) provides completely transparent authentication:
- The proxy configures bridge-level DNS overrides (via dnsmasq on
incusbr0) so containers resolveapi.anthropic.com,github.com, and related domains to the Incus bridge gateway IP - Template images include a custom CA certificate so containers trust the proxy's TLS certificates
- The proxy terminates TLS, injects authentication headers, and forwards to the real upstream over TLS
- Tools (
curl,git,gh,claude) work transparently inside containers — placeholder auth values satisfy local checks, but the proxy replaces them with real credentials before requests reach upstream - Vertex AI support: when the host uses Vertex AI, the proxy transparently translates standard API requests to Vertex AI
rawPredictformat — containers run Claude Code in standard mode with zero knowledge of Vertex, no GCP credentials - There is no mechanism for code inside a container to read, extract, or exfiltrate real credentials
- HTTPS only: the proxy intercepts HTTPS traffic, so Git operations must use HTTPS URLs (not SSH).
ghdefaults to HTTPS automatically; forgit clone, usehttps://github.com/...instead ofgit@github.com:...
The proxy must be running for non-airgapped containers. isx init can install it as a systemd user service that starts automatically and survives reboots. Alternatively, run isx proxy in a separate terminal. View proxy logs with isx proxy logs.
Each branch runs in one of three network modes:
| Mode | Flag | Description |
|---|---|---|
| Full internet | (default) | Unrestricted network access via NAT, auth via MITM proxy |
| Proxy only | --proxy-only |
Outbound traffic restricted to MITM proxy only (iptables) |
| Airgapped | --airgap |
Network device removed, complete isolation |
In all non-airgapped modes, credentials are injected transparently by the MITM proxy. The network modes only control what other traffic the container can access:
- Full internet: containers can reach any destination; traffic to intercepted domains (Anthropic, GitHub) is transparently routed through the MITM proxy for auth injection
- Proxy only: iptables OUTPUT rules restrict all outbound traffic to the MITM proxy port (443) and DNS — the container cannot reach any external endpoint directly
- Airgapped: no network device, no traffic at all
Containers created with isx branch are isolated environments, but you need a way to get your changes back. incus-spawn integrates with git's native remote helper protocol so you can use standard git fetch, git push, and git pull between host repos and container repos:
# Inside the container, you make some commits...
# Back on the host:
git fetch fix-auth
git cherry-pick fix-auth/mainThe remote uses the isx:// URL scheme:
isx://<instance-name>/<path-to-repo>
For example:
# Tilde expands to /home/agentuser
git remote add fix-auth isx://fix-auth/~/quarkus
# Absolute paths work too
git remote add fix-auth isx://fix-auth/home/agentuser/quarkus
# Then use standard git commands
git fetch fix-auth
git log fix-auth/main
git diff main..fix-auth/main
git pull fix-auth main
git push fix-auth mainThe instance must be running for git operations to work. If you specify a wrong path, the error message lists known repositories from the image definition.
If you configure host-paths in ~/.config/incus-spawn/config.yaml, remotes are managed automatically:
# Base directories where your repos live on the host
# If a repo exists in multiple host-paths, you must add an explicit repo-paths entry
host-paths:
- ~/projects
- ~/workspace
# Explicit overrides for repos in non-standard locations or to resolve ambiguity
repo-paths:
quarkus: ~/work/quarkus
hibernate: /opt/hibernateWith this configuration:
isx branchadds a git remote named after the instance in each matching host repo. A host repo matches when itsoriginURL corresponds to a repo declared in the template's image definition (protocol-lenient — SSH and HTTPS URLs for the same repo are treated as equal).isx destroyremoves the remote from host repos.
# Branch from a template that declares a quarkus repo
isx branch fix-auth tpl-quarkus
# The remote is automatically added in ~/work/quarkus:
# git remote add fix-auth isx://fix-auth/home/agentuser/quarkus
cd ~/work/quarkus
git fetch fix-auth
# When done, destroy the instance — the remote is cleaned up
isx destroy fix-authIf a remote with the instance name already exists, a warning is printed with instructions to add it under a different name.
Template images are reusable base environments defined in YAML. They can inherit from each other -- building an image automatically builds any missing parents:
# images/java.yaml
name: tpl-java
description: JDK + Maven + Claude Code
parent: tpl-dev
packages:
- java-25-openjdk-devel
- java-25-openjdk-javadoc
- java-25-openjdk-src
tools:
- maven-3Three images are built-in (tpl-minimal, tpl-dev, tpl-java). Add your own by placing YAML files in ~/.config/incus-spawn/images/ (user-level) or .incus-spawn/images/ (project-local).
You can also point to external directories via searchPaths in config.yaml (see Configuration); this is useful to version your templates in a separate git project.
Later sources override earlier ones: built-in → user → search paths → project-local.
Use isx templates to manage templates from the CLI:
# List all available templates
isx templates list
isx templates list -v # with source path and description
# Create a new template (opens in $EDITOR with a commented skeleton)
isx templates new my-app # creates ~/.config/incus-spawn/images/my-app.yaml
isx templates new my-app --project # creates .incus-spawn/images/my-app.yaml
# Edit an existing template
isx templates edit tpl-java # opens in $EDITOR, validates on saveEditing a built-in template automatically creates a user-level override in ~/.config/incus-spawn/images/. The override takes precedence over the built-in but will not auto-update with isx upgrades. Templates are validated after editing: YAML syntax, required fields, and parent references are checked.
Image schema fields (all optional except name):
image-- base OS image, only for root images (default:images:fedora/43)parent-- parent image name (omit for root images)packages-- dnf packages to installtools-- tool names to run (resolved from YAML or Java, see Custom Tools)repos-- git repositories to clone as agentuser (see below)skills-- Claude Code skills to bake into the image (see below); accepts a list shorthand or an object withrepoandlistsub-fieldshost-resources-- host files/directories to share with containers (see below)description-- human-readable description for the TUI
# Build a specific image (builds missing parents automatically)
isx build tpl-java
# Rebuild all discovered images from scratch
isx build --allImages can declare git repositories to clone into the container. Declaring a git repository rather than using shell commands to fetch it allows for better integration into other tools, such as Claude Code.
name: tpl-quarkus
description: Quarkus development
parent: tpl-java
tools:
- podman
- gradle
repos:
- url: https://github.com/quarkusio/quarkus.git
path: ~/quarkus
prime: mvn -B dependency:go-offlineRepo entry fields:
url(required) -- git clone URL (HTTPS, for proxy compatibility)path(required) -- target directory (~expands to agentuser's home)branch(optional) -- branch or tag to check out; defaults to the repo's default branchprime(optional) -- shell command to run inside the repo directory after cloning, typically to pre-fetch dependencies (e.g.mvn dependency:go-offline,gradle dependencies)
Template images can declare Claude Code skills to bake in at build time. Skills are installed once into the template and inherited by every instance branched from it.
name: tpl-agent
description: Agent with security skills
parent: tpl-dev
skills:
repo: myorg/claude-skills # default catalog for bare skill names
list:
- security-review # short name → myorg/claude-skills@security-review
- code-review # short name → myorg/claude-skills@code-review
- xixu-me/skills@xget # explicit owner/repo@skill-name
- myorg/catalog # all skills from a repoThere is no implicit default catalog -- repo is only needed to resolve bare skill names (like security-review above). When all entries use the fully qualified owner/repo@skill or owner/repo form, you can omit repo and use the list shorthand:
skills:
- xixu-me/skills@xget
- myorg/catalogFor local skills (e.g. skills you are developing), point to a directory containing a SKILL.md or subdirectories each with their own SKILL.md. Relative paths are resolved from the directory where isx build is run:
skills:
- ./my-skills/code-review # single skill: my-skills/code-review/SKILL.md
- ./my-skills # all skills: one per subdirectory with SKILL.mdSkill source formats:
owner/repo@skill-name-- specific skill from a GitHub repoowner/repo-- all skills from a GitHub repohttps://github.com/owner/repo-- full GitHub URL./local-path-- local directory (always read from disk, not cached)skill-name-- bare name, resolved asrepo@skill-nameusing theskills.repofield. There is no built-in default catalog, so bare names requirerepoto be set -- otherwise the build will stop with an error explaining how to fix it.
Skills are fetched on the host at build time and cached at ~/.cache/incus-spawn/skills/. They are not installed on the host — each SKILL.md is written directly into the container at ~/.claude/skills/<skill-name>/SKILL.md, the global skills directory that Claude Code reads automatically. Subsequent builds reuse the cached files without hitting the network.
Skills are deduplicated across the parent chain: if a parent already declares a skill, child images skip it.
To find available skills, browse skills.sh.
Template images can declare host files and directories to make available inside containers. This is useful for sharing configuration files, pre-populating caches, or providing large datasets without copying them into every template.
name: tpl-java
parent: tpl-dev
packages:
- java-25-openjdk-devel
tools:
- maven-3
host-resources:
- source: ~/.m2/repository
mode: overlay
- source: ~/.gitconfigThe ~/.m2/repository entry shares your host Maven cache with the container. With mode: overlay, the container sees a normal read-write directory pre-populated with your cached artifacts, but writes go to a container-local layer -- your host cache is never modified. Maven builds that would normally download hundreds of megabytes of dependencies can instead resolve them instantly from the shared cache.
The ~/.gitconfig entry mounts your git configuration read-only (the default mode), so git inside the container picks up your name, email, aliases, and other settings.
Three modes are available:
| Mode | Default? | Description |
|---|---|---|
readonly |
Yes | Read-only bind mount. Simple, safe. |
overlay |
No | Read-only lower layer from host + ephemeral writable upper in the container. Tools see a normal read-write directory. Host is fully protected. |
copy |
No | Copied into the container at build time. Becomes part of the template. Also supports URL sources. |
If path is omitted, it defaults to the same relative path under /home/agentuser/. For example, source: ~/.m2/repository maps to /home/agentuser/.m2/repository inside the container.
More examples:
host-resources:
# Share SSH config (read-only)
- source: ~/.ssh/config
# Copy a custom gitconfig from a URL
- source: https://example.com/team-gitconfig
path: /home/agentuser/.gitconfig
mode: copy
# Share Gradle cache with overlay (writable inside container, host protected)
- source: ~/.gradle/caches
mode: overlayIf a host path doesn't exist at build or branch time, the entry is skipped with a warning -- the build proceeds without it. This means templates with host-resources remain portable: they work on machines that have the declared paths and gracefully degrade on machines that don't.
Host resources compose across the parent chain: a child image inherits its parent's host-resources and can override individual entries (matched by container path) to change the mode.
Template inheritance forms a single chain -- a template has exactly one parent. Tools provide composition: reusable capabilities that any template can mix in independently. A gradle tool can be added to a Java template, a Kotlin template, or a project-local template without duplicating definitions or creating diamond inheritance.
Tools are defined as YAML files and referenced from image definitions via tools::
# .incus-spawn/tools/gradle.yaml
name: gradle
description: Gradle 9.4.1
downloads:
- url: https://services.gradle.org/distributions/gradle-9.4.1-bin.zip
sha256: 2ab2958f2a1e51120c326cad6f385153bb11ee93b3c216c5fccebfdfbb7ec6cb
extract: /opt
links:
/opt/gradle-9.4.1/bin/gradle: /usr/local/bin/gradle
verify: gradle --version
Downloads declared this way are cached on the host at ~/.cache/incus-spawn/downloads/, so rebuilding images doesn't re-download unchanged artifacts.
Extraction happens on the host -- the container doesn't need tar, unzip, or curl.
Tool schema fields (all optional except name):
packages-- dnf packages to installdownloads-- artifacts to download, cache on the host, and extract into the containerrequires-- list of other tool names that must be installed first (resolved transitively; circular dependencies are detected and rejected)run-- shell commands as rootrun_as_user-- shell commands as agentuserfiles-- files to write (with optionalowner)env-- lines appended to agentuser's.bashrcverify-- verification command (logged, non-fatal)actions-- runtime actions available from the TUI when the tool is installed (see Tool Actions)
Download entry fields:
url(required) -- download URLsha256(recommended) -- SHA-256 checksum; enables cache reuse and verifies integrityextract(required) -- directory in the container to extract intolinks(optional) -- map ofsource_path: symlink_pathto create after extraction
Supported archive formats: .tar.gz/.tgz, .tar.bz2, .tar.xz, .zip.
Execution order during install(): packages → downloads → run → run_as_user → files → env → verify.
Resolution order: built-in YAML → ~/.config/incus-spawn/tools/ (user) → search paths → .incus-spawn/tools/ (project-local) → Java plugins.
The built-in idea-backend tool installs the JetBrains IntelliJ IDEA remote development backend, allowing you to connect from JetBrains Gateway on the host. It declares requires: [sshd], so the SSH server is installed automatically:
name: tpl-java-ide
parent: tpl-java
tools:
- idea-backend # auto-installs sshd via requiresAfter branching, connect from JetBrains Gateway using the container's IP (visible in the TUI via F3) over SSH as agentuser. Add your SSH public key to the template's ~/.ssh/authorized_keys (e.g. via a host-resource or a custom tool) so authentication works automatically in every branch.
The idea-backend tool also declares a tool action that lets you open repos directly in Gateway from the TUI — press F9 on a running instance to see available actions, including an "Open repo in Gateway" entry for each declared repository.
Tools can declare runtime actions that appear in the TUI when the tool is installed on an instance. Press F9 on a selected instance to open the actions menu. Actions are only shown for tools that are part of the instance's template chain.
Three action types are supported:
| Type | Description |
|---|---|
url |
Opens a URL in the default browser (via xdg-open) |
command |
Runs a shell command on the host (exits the TUI, re-enters after) |
copy-to-clipboard |
Copies text to the system clipboard (via xclip) |
Actions support template variables that are interpolated at execution time:
| Variable | Value |
|---|---|
${ip} |
Instance IPv4 address |
${name} |
Instance name |
${parent} |
Parent template name |
${repo_name} |
Repository directory name (when expanded) |
${repo_path} |
Repository path inside the container (when expanded) |
${repo_url} |
Repository clone URL (when expanded) |
Use expand: repos to generate one action per declared repository in the template's inheritance chain. This is how the idea-backend tool creates a separate "Open in Gateway" entry for each repo:
# tools/idea-backend.yaml (excerpt)
actions:
- label: "Open repo '${repo_name}' in Gateway"
type: url
expand: repos
url: "jetbrains-gateway://connect#idePath=%2Fopt%2Fidea&host=${ip}&port=22&user=agentuser&type=ssh&deploy=false&projectPath=${repo_path}"A standalone action (without expand) looks like:
actions:
- label: "Open web UI"
type: url
url: "http://${ip}:8080"
- label: "Copy SSH command"
type: copy-to-clipboard
text: "ssh agentuser@${ip}"
- label: "Run migrations"
type: command
command: "incus exec ${name} -- sudo -u agentuser /home/agentuser/app/migrate.sh"Action entry fields:
label(required) -- text shown in the actions menu (supports template variables)type(required) -- one ofurl,command, orcopy-to-clipboardurl-- URL to open (forurltype)command-- shell command to run on the host (forcommandtype)text-- text to copy (forcopy-to-clipboardtype)expand-- set toreposto generate one action per declared repositoryrequires_running-- whether the instance must be running (default:true)auto_return-- forcommandtype, skip the "press any key" prompt after execution (default:false)
Actions can also be contributed programmatically by CDI beans implementing the ToolAction interface, for cases that need logic beyond what YAML declarations can express.
- Instant branching: copy-on-write clones that share storage with the parent image
- System containers: full init, real networking, bare-metal-like developer experience
- KVM VMs:
--vmflag for hardware-level isolation with separate kernel (optional) - Interactive TUI: Midnight Commander-style interface with F3 detail views, F9 tool actions, template staleness indicators, and modal dialogs for branching, renaming, and building
- GUI and audio passthrough: Wayland + PipeWire with GPU acceleration
- Host resources: share host files and directories with containers (read-only, overlay, or copy)
- Inbox mount: share a host directory read-only into the container
- MITM TLS proxy: transparent auth injection — credentials never enter containers in any form
- Proxy caching: OCI registry blobs and Maven/Gradle artifacts cached on the host, shared across all branches
- Proxy-only networking: (optional) iptables restricts egress to the MITM proxy only
- Network airgapping: fully isolate environments from the network
- Adaptive resource limits: CPU, memory, and disk auto-detected from host
- Claude Code integration: auth via MITM proxy — API key never enters containers
- Claude Code skills: bake skills into templates so they are available in every branched instance
- GitHub integration: auth via MITM proxy — token never enters containers
- Git remotes:
git fetch/git pushbetween host and container repos viaisx://URLs, with automatic remote management - Remote IDE: JetBrains Gateway support via built-in
idea-backendtool with transitivesshddependency, and one-click "Open in Gateway" action from the TUI - Tool actions: tools can declare runtime actions (open URL, run command, copy to clipboard) available via F9 in the TUI, with per-repo expansion and template variable interpolation
- Tool dependencies: tools can declare
requiresfor automatic transitive dependency resolution - Version drift detection: warns when templates were built with a different isx version or when definitions have changed since the last build
- Shell completions: bash, zsh, and fish via
isx completion {bash,zsh,fish}
| Command | Description |
|---|---|
isx |
Launch the interactive TUI |
isx init |
One-time host setup (Incus, firewall, auth) |
isx build <template> |
Build or rebuild a template image |
isx build --all |
Rebuild all discovered templates |
isx build --missing |
Build only templates that don't exist yet |
isx branch <name> |
Create a CoW clone from a template or instance |
isx shell <instance> |
Open a shell in an instance |
isx destroy <instance> |
Destroy an instance |
isx update-all |
Update all templates (packages, repos, tools) |
isx templates |
List available templates |
isx templates list -v |
List templates with source and description |
isx templates new <name> |
Create a new template definition |
isx templates edit <name> |
Edit a template in $EDITOR |
isx instances |
List connectable instance names (excludes templates) |
isx project create <name> |
Create a project template from incus-spawn.yaml |
isx project update <name> |
Update an existing project template |
isx proxy start |
Start the MITM auth proxy |
isx proxy stop |
Stop the proxy |
isx proxy status |
Show proxy status |
isx proxy install |
Install proxy as a systemd user service |
isx proxy uninstall |
Stop and remove the systemd proxy service |
isx proxy logs |
View proxy logs |
isx proxy dump |
Run a local pass-through proxy for API traffic capture |
isx completion <shell> |
Print shell completion script (bash, zsh, fish) |
Use isx <command> --help for detailed options on any command.
Details that save time and avoid frustration:
- Shared DNF cache: building a chain of templates (e.g.
tpl-javawhich derives fromtpl-devwhich derives fromtpl-minimal) mounts a host-side cache (~/.cache/incus-spawn/dnf) into each container during the build. DNF metadata and downloaded packages are shared across all builds, so child images don't re-download what the parent just fetched. The cache is unmounted before the image is finalized, keeping templates clean. - Registry blob caching: the MITM proxy caches OCI container image layers (
~/.cache/incus-spawn/registry/) by content-addressed SHA256 digest. Pulling the same container image in different branches downloads each layer only once. Each blob is verified against its SHA256 digest before being committed to the cache. - Maven/Gradle artifact caching: the MITM proxy caches artifacts from Maven Central, Maven repository, and Gradle plugin portal (
~/.cache/incus-spawn/maven/). Release artifacts are immutable and cached permanently; SNAPSHOT and metadata requests pass through uncached. When artifacts already exist in the host's~/.m2/repository, the proxy verifies their SHA1 against the upstream checksum before serving — stale or corrupted local artifacts are never served. - CoW pool auto-creation:
isx initcreates a btrfs storage pool if no copy-on-write pool exists, so branches are instant from the start. - Sudo ready: your agents and scripts can invoke sudo at will, no password will be required.
- Failed build inspection: if a template build fails, the container is promoted to an inspectable instance so you can shell in and debug.
- Proxy health check: builds, branches, and shell access verify the proxy is reachable before proceeding, so you get a clear error instead of mysterious connection failures.
- Template staleness indicators: the TUI marks templates with
!when they were built with a different isx version, and△when the image or tool definition has changed since the last build. This tells you at a glance which templates need rebuilding. - Version drift remediation: if the running proxy version doesn't match the CLI, the proxy is automatically restarted (when no containers are running) or a warning is shown.
- CA certificate mismatch warning: branching from a template built with a different CA certificate warns you before you hit TLS failures.
- Claude Code auto-trust: when templates declare repos, the build pre-trusts those directories in
.claude.jsonso Claude Code doesn't prompt for trust on first use. - Terminal title: shell sessions set the terminal title to
isx:<containername>, and the container prompt maintains it. Easy to identify which terminal belongs to which container.
sudo dnf copr enable sanne/incus-spawn
sudo rpm --import https://download.copr.fedorainfracloud.org/results/sanne/incus-spawn/pubkey.gpg
sudo dnf install incus-spawnUpdates automatically with sudo dnf upgrade.
curl -fsSL https://raw.githubusercontent.com/Sanne/incus-spawn/main/get-isx.sh | shInstalls a self-contained native binary to ~/.local/bin/isx. No JVM required. Set INSTALL_DIR to change the install location. To update, re-run the same command. To uninstall, run uninstall.sh (caches at ~/.cache/incus-spawn/ are preserved unless you pass --purge).
jbang app install isx@Sanne/incus-spawn# Build
mvn package
# Run tests
mvn test # unit tests (no Incus needed)
mvn verify -DskipITs=false # integration tests (requires Incus)
# Install locally
./install.sh # JVM
./install.sh --native # native (requires Docker, Podman, or GraalVM)Releases are automated via GitHub Actions. To create a new release, run:
./release.shThe script derives the version from the POM snapshot (e.g. 0.1.9-SNAPSHOT → v0.1.9), validates the working tree, creates the tag, and pushes it. You can also pass an explicit version: ./release.sh 0.2.0.
Pushing the tag triggers a workflow that will:
- Set the project version from the tag
- Build a self-contained uber-jar (for JBang users)
- Build a native binary via container-based GraalVM compilation
- Create a GitHub Release with auto-generated release notes and both artifacts attached
- Publish the native binary as an RPM to Fedora COPR
- Bump the POM version to the next snapshot
Users can then install or update via dnf upgrade (Fedora), curl -fsSL .../get-isx.sh | sh (native), or jbang app install isx@Sanne/incus-spawn (JVM).
~/.config/incus-spawn/config.yaml-- auth credentials and global settings~/.config/incus-spawn/images/*.yaml-- user-level template definitions~/.config/incus-spawn/tools/*.yaml-- user-level tool definitions.incus-spawn/images/*.yaml-- project-local template definitions.incus-spawn/tools/*.yaml-- project-local tool definitions
The config.yaml also supports git remote auto-management via host-paths and repo-paths (see Git Remotes), and a searchPaths list for loading templates and tools from external directories. Each directory should contain images/ and/or tools/ subdirectories following the same YAML schema as the built-in definitions. Tilde (~) expansion is supported for all path settings:
searchPaths:
- ~/my-templates
- /absolute/path/to/templatesmy-templates/
images/
quarkus.yaml
tools/
gradle.yaml
Resolution order (later sources override earlier ones with the same name):
- Built-in (bundled with isx)
- User (
~/.config/incus-spawn/) - Search paths (in listed order)
- Project-local (
.incus-spawn/)