Image Management - mensfeld/code-on-incus GitHub Wiki

Advanced image operations for creating and managing custom COI images.

List Images

# List COI images
coi image list

# List all local images
coi image list --all

# Filter by prefix
coi image list --prefix claudeyard-

# JSON output
coi image list --format json

Publish Containers as Images

Convert a container into a reusable image:

# Publish container as image
coi image publish my-container my-custom-image

# With description
coi image publish my-container my-custom-image --description "Custom build with Python 3.11"

# Skip compression for faster publishing
coi image publish my-container my-custom-image --compression none

# Create image from container with installed tools
coi image publish coi-workspace-1 coi-rust --description "COI with Rust toolchain"

Delete Images

# Delete specific image
coi image delete my-custom-image

# Delete old image
coi image delete my-old-image

Check Image Existence

# Check if image exists
coi image exists coi-default

# Use in scripts
if coi image exists my-custom-image; then
  echo "Image exists"
fi

Clean Up Old Versions

# Keep only 3 most recent versions, delete older ones
coi image cleanup claudeyard-node-42- --keep 3

# Clean up all old images (keep latest only)
coi image cleanup my-project- --keep 1

Creating Custom Images

Workflow 1: Install Tools in Container

# Start a session and install tools
coi shell --persistent

# Inside container: install your tools
sudo apt update
sudo apt install -y rust-all python3.11 golang
cargo install ripgrep
exit

# Publish the container as an image
coi image publish coi-workspace-1 coi-dev-full

# Use the custom image
coi shell --image coi-dev-full

Workflow 2: Profile with Build Script

Create a profile directory with a [container.build] section:

coi profile create my-custom --image my-custom-image

Edit ~/.coi/profiles/my-custom/config.toml:

[container]
image = "my-custom-image"

[container.build]
base = "coi-default"
script = "build.sh"    # resolved relative to this config.toml

Create ~/.coi/profiles/my-custom/build.sh:

#!/bin/bash
apt update
apt install -y your-tools
pip install your-packages

Build and use:

coi build --profile my-custom
coi shell --profile my-custom

Use Cases

Team Images

Create standardized images for your team:

# Create team image with specific tools
coi shell --persistent
# Install team tools...
exit
coi image publish coi-workspace-1 team-nodejs-2024

# Team members use it
coi shell --image team-nodejs-2024

Project-Specific Images

# Create image per project with dependencies pre-installed
coi image publish project-container myapp-v1.0

Version Management

# Keep multiple versions
coi image publish coi-workspace-1 myproject-v1.0
coi image publish coi-workspace-1 myproject-v1.1

# Clean up old versions, keep last 3
coi image cleanup myproject- --keep 3

Image Compression

By default, Incus compresses images with gzip when publishing. Use --compression to control the algorithm:

# Skip compression entirely (fastest, larger images — good for iteration)
coi build --compression none
coi image publish my-container my-image --compression none

# Use xz for high compression ratio (slowest, smallest images — good for distribution)
coi build --compression xz

Available values: none, gzip (default), xz, or any algorithm supported by Incus. The value is passed directly to incus publish --compression.

Build Configuration in Project Config

You can define how to build custom images directly in .coi/config.toml using the [container.build] section:

# .coi/config.toml
[container]
image = "coi-myproject"

[container.build]
base = "coi-default"            # Base image (default: "coi-default")
script = "build.sh"             # Path to build script (relative to config file)
# commands = ["apt-get update", "apt-get install -y rustup"]  # Or inline commands

When container.image is set to a custom name and [container.build] is configured:

  • coi build builds the custom image automatically
  • coi shell and coi run auto-detect missing images and trigger the build before launching

Script vs commands: If both script and commands are set, script takes precedence. Script paths are resolved relative to the config file location.

See also Profiles — Build Scripts for per-profile build configuration.

Pre-installed Runtime Manager (mise)

The base coi-default image includes mise — a polyglot runtime manager for installing and managing language runtimes. The following are pre-installed:

Tool Notes
Python 3 python3, pip, venv
pnpm Fast Node.js package manager
TypeScript tsc compiler
tsx TypeScript execution without compilation

Install additional runtimes inside your container or build script:

mise use --global go@latest      # Install Go
mise use --global ruby@3         # Install Ruby
mise use node@22                 # Install Node.js 22 (per-project)

Create a .mise.toml in your project root to pin runtime versions per-project. mise is activated in all shell sessions (interactive and non-interactive), so tools are available to AI agents automatically.

Note: System Node.js (v20 LTS) is retained for Claude CLI and core tooling alongside mise-managed runtimes.

Notes

  • Images are stored locally in Incus
  • Publishing an image captures the complete container state
  • Custom images can be used with --image flag
  • Image cleanup helps manage disk space
  • Use descriptive names and versions for easy identification