High-level architecture of incus-compose and how components fit together, a resource-first design:
- Unified Resource Interface - Images, instances, networks, profiles, and volumes are all first-class resources
- Two-Phase Pattern - Configuration (resource creation) then execution (ensure/start/stop/delete)
- Priority-Based Ordering - Dependencies managed via numeric priorities, no complex graph resolution
- Stack Execution - Batch operations with parallel image downloads
- Hook System - Before/after action interception for logging and validation
incus-compose/
├── cmd/incus-compose/ # CLI entry point
├── client/ # Incus client with resources, stack, pool
└── project/ # Compose-spec to Incus translation
cmd/incus-compose/
- CLI and flag parsing
- Wires together client and project
- Commands: up, down, ps, config
client/
- High-level Incus API wrapper
- Resources: Profile, Image, Network, StorageVolume, Instance
- Stack for task collection and ordering
- WorkerPool for parallel execution
- Hooks for action interception
project/
- Loads Docker Compose files via compose-go
- Translates compose services to Incus resources
- Configures client resources based on compose definitions
- Handles environment variables and dependencies
cmd/incus-compose
├── client (creates GlobalClient, runs Stack)
└── project (loads compose, configures client resources)
project
└── client (calls client.Resource() to create resources)
The CLI creates a GlobalClient and loads the compose project. Then project takes over: it reads the compose definitions and configures resources on the client. The client owns the resources, but project drives what gets created.
This means project is not a passive loader. It actively builds the resource graph by calling into client. The Stack returned by project contains all resources ready for execution.
GlobalClient
├── imageCache (default project, configurable via INCUS_COMPOSE_IMAGE_CACHE)
└── Client (project-scoped)
├── Profile
├── Image
├── Network
├── StorageVolume
└── Instance
├── Devices (pre-creation)
└── PostDevices (post-creation)
Images go through three stages:
- Remote - OCI registry (docker.io, ghcr.io)
- Cache - Incus
defaultproject (configurable viaINCUS_COMPOSE_IMAGE_CACHE) - Project - per-project copy used by the instance
Registry ──pull──> Cache ──copy──> Project ──use──> Instance
(slow)
Benefits:
- First pull is slow (network), subsequent runs are fast (local cache)
- No registry rate limits after initial download
- Cache persists across
down/upcycles - Project deletion does not affect the cache
-
Configuration phase - Resource created in memory
image, _ := client.Resource(KindImage, "docker.io/alpine", &ImageConfig{}) image.Config.Source = imageServer // configure
-
Execution phase - Resource created on Incus
image.Ensure(OptionCreate()) // blocks, creates on server
See Client Package for Stack, WorkerPool, resource ordering, and hook details.
My_Project! -> my-project
Valid DNS names, max 63 chars, long names hashed to 32 hex chars.
Linux interface limit (13 chars), uses hash for long names:
backend -> app-backend or ic-a1b2c3d4e5
See Errors for sentinel errors and context enrichment.
Direct URL (testing/CI):
export INCUS_COMPOSE_URL="https://192.168.1.100:8443"
export INCUS_COMPOSE_CERT="./certs/client.crt"
export INCUS_COMPOSE_KEY="./certs/client.key"Provided connection (for testing):
client.New(ctx, client.ClientProvideConnection(instanceServer, cacheServer))- OS environment variables NOT included by default
.envfiles can use OS variables for interpolation- Use
--os-envflag for Docker Compose compatibility
Pass raw Incus configuration options directly to instances and networks:
services:
web:
image: docker.io/nginx:alpine
x-incus:
limits.memory: 512MB
limits.cpu: "2"
security.nesting: "false"
networks:
custom:
x-incus:
nat: "false"
ipv4.nat: "true"All key-value pairs are passed verbatim to Incus. See the Incus instance options reference for available options, and Compose Compatibility for the per-resource (instance, network, volume) x-incus reference.
Compose-specific transformations and conveniences handled by incus-compose:
x-incus-compose:
healthd:
incus: https://:8443
network: :default
services:
app:
image: docker.io/myapp:latesthealthd.incus and healthd.network configure where the ic-healthd sidecar
attaches and which Incus endpoint it connects to. Both default to the project's
own network and the connection's port; see
Health Checking - Network Configuration for
the full set of combinations.
incus-compose up # Start services
incus-compose up --no-start # Create without starting
incus-compose up --recreate # Recreate existing containers
incus-compose down # Stop and remove
incus-compose down --volumes # Also remove volumes
incus-compose list # List running containers
incus-compose config --quiet # Validate compose file
incus-compose config # Show resolved configuration
incus-compose config --services # List service names
incus-compose config --networks # List network names
incus-compose config --volumes # List volume names
incus-compose config --environment # Show interpolation environment# Basic service
services:
web:
image: docker.io/nginx:alpine
ports:
- "8080:80"
# With dependencies
services:
db:
image: docker.io/postgres:16-alpine
app:
image: docker.io/myapp:latest
depends_on:
- db
# With named volume
services:
app:
image: docker.io/myapp:latest
volumes:
- data:/var/lib/app
- ./config:/etc/app:ro
volumes:
data:
# With environment file
services:
app:
image: docker.io/myapp:latest
environment:
DATABASE_URL: ${DATABASE_URL}
env_file:
- .envSee the docs index for all user and contributor docs. Closely related:
- Client Package - Resources, Stack, WorkerPool
- Testing - Testing patterns and fixtures
- Health Checking - ic-healthd sidecar
- Progress - Live operation progress and the terminal renderer
- Bugs/Features: Open an issue on GitHub
- Questions: Check the docs above or open a discussion