Templates

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
parent: tpl-dev
packages:
  - java-25-openjdk-devel
  - java-25-openjdk-javadoc
  - java-25-openjdk-src
tools:
  - maven-3

Three images are built-in (tpl-minimal, tpl-dev, tpl-java). tpl-dev and tpl-java are deprecated and will be removed from isx: they still build, with a warning. Define your own templates in their place; to start from something, their maintained copies live in incus-spawn-templates under the same names. Add that checkout to searchPaths (or copy its images/dev.yaml and images/java.yaml into ~/.config/incus-spawn/images/) and the copies replace the built-ins; the definitions are the same, so nothing already built goes out of sync. The root image (tpl-minimal) uses a custom Fedora base from Sanne/incus-spawn-images. Use isx update-base to check for new base image releases, pin a specific version, or track the latest:

isx update-base              # interactive — shows versions, prompts for action
isx update-base --list       # list available versions
isx update-base --latest     # track the newest release (remove any pin)
isx update-base fedora-44-v2 # pin to a specific release tag

Tracking latest is the default and requires no action: when the base image is unpinned, isx build tpl-minimal resolves the newest release at build time and installs it (falling back to the version baked into the binary if it can't reach the release list). Pinning writes a user-level override to ~/.config/incus-spawn/images/minimal.yaml; --latest removes that override to resume tracking. After changing the base image version, rebuild with isx build tpl-minimal.

Add your own templates 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).

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 skeleton listing every field, commented out)
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 save

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

You can also define a custom root image (no parent) by specifying image, image_url, image_tag, and image_sha256 to point at your own pre-baked OS tarball. See the built-in minimal.yaml and the incus-spawn-images repo for the reference example.

Image schema fields (all optional except name; isx templates new writes them all into the new file, commented out):

# Build a specific image (builds missing parents automatically)
isx build tpl-java

# Build as a VM instead of a container (overrides the definition's type)
isx build tpl-java --type vm

# Rebuild a template and all its parents from scratch
isx build tpl-java --with-parents

# Rebuild two templates and all their parents, the parents they share once
isx build tpl-isx tpl-quarkus --with-parents

# Rebuild out-of-sync templates (changed definitions, older isx version, or a parent rebuilt since)
isx build --out-of-sync

# Rebuild all discovered images from scratch
isx build --all

The TUI marks templates with ! when they were built with a different isx version, △ when the image or tool definition has changed since the last build, and ↑ when their parent was rebuilt after them (so they were copied from its earlier build; a VM over a container parent is built from the definitions, never copied, so it is never ↑) — isx build --out-of-sync rebuilds all three, and everything that inherits from what it rebuilds. If a build fails, the container is promoted to an inspectable instance so you can shell in and debug.

Declarative Repos

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

Repo entry fields:

Declared repos are automatically pre-trusted in .claude.json so Claude Code doesn't prompt for trust on first use.

For a repository accessible only on the host, declare its source directly:

name: tpl-private
parent: tpl-java
repos:
  - host-source: ~/work/private-repo
    path: ~/private-repo
    branch: my-feature

Build and branch this template as usual. The guest receives all local branches and tags with their reachable history, including unpushed commits; branch selects the initial branch or tag without removing other branches. Stashes, reflog-only and unreachable objects, uncommitted files, source configuration and hooks are excluded unless their contents are reachable from an exported branch or tag. The host checkout is read without fetching or modifying it, and the guest copy remains usable after the source is removed. The guest has no upstream remote; fetch or update the original repository on the host, then rebuild to export newer work. Branching registers an isx:// remote in the resolved host checkout, so git fetch <instance-name> retrieves the instance's work; destroying or renaming the instance cleans up that remote. No repo-paths entry or host-resources overlay is needed, on Linux or macOS. Host sources require complete history (depth and shallow source repositories are refused), and project-local .incus-spawn/ templates cannot declare them. An unexported detached host HEAD requires an explicit branch or tag. Existing url: file:///... entries still refer to paths inside the guest.

Shell Defaults

Templates can configure the default working directory, shell command, and default action when connecting to a container:

name: tpl-quarkus
parent: tpl-java
tools: [claude]
repos:
  - url: https://github.com/quarkusio/quarkus.git
    path: ~/quarkus
workdir: ~/quarkus
default-action: claude

Host Resources

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-my-java
parent: tpl-java
host-resources:
  - source: ~/.m2/repository
    mode: overlay
  - source: ~/.gitconfig

The ~/.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 (see Git Configuration).

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. Linux only — not yet supported on macOS.
copy No Copied into the container at build time. Becomes part of the template. Also supports URL sources.

readonly and overlay resources can't be mounted into system directories such as /, /etc, /usr, /var or /tmp. Use /home/agentuser, /opt, /srv or /mnt instead, or mode: copy.

If path is omitted, it defaults to the same relative path under /home/agentuser/. Missing host paths are skipped with a warning, so templates remain portable. Host resources compose across the parent chain, with child entries overriding parent entries matched by container path.

Project-local templates (.incus-spawn/images/) come from whatever repository you cloned, so they can't reach outside it: host resources and file:// images must point inside the project directory, and their repos are always cloned from the network, never from your host checkouts. To give a template you trust wider access, move it to ~/.config/incus-spawn/images/.

VMs mount host resources via virtiofs. Single-file resources fall back to copy mode, since VMs only mount directories.