Getting Started
Prerequisites#
- Incus 7.0+ installed and running
- Access to an Incus server (local or remote)
podmanordockerfor image building (see Builds)
Incus must listen on the network (required)#
incus-compose requires the Incus server to listen on the network. Set
core.https_address:
incus config set core.https_address=:8443This is not optional, even for a local Incus reached over the Unix socket.
incus-compose caches images in a separate Incus project and copies each one into
your project on up. That cross-project copy uses Incus pull mode, which needs
the server to be reachable over the network: the same daemon pulls the image
from itself. Without core.https_address set, up fails with
The source server isn't listening on the network, and health checks are
silently skipped.
Only the server setting matters here; the client connection itself can stay on the Unix socket. See Local vs Remote Incus for the handful of behaviours that do depend on how you connect.
HTTPS Remote (for remote servers and health checks)#
Connect the client over HTTPS when Incus runs on another host, or when you use
health checks: the ic-healthd sidecar reaches Incus over HTTPS. By default
healthd uses the project's own network and reaches Incus over that bridge; use
--healthd-network / --healthd-incus if your setup differs, see
Network Configuration.
- Generate a cert and add it to the trust store as admin cert
# Generate and trust a certificate
incus remote generate-certificate
incus config trust add-certificate ~/.config/incus/client.crt- Add it as remote and set it as default remote
incus remote add local-https <a-ip-of-your-host>
# Switch to local-https as default remote
incus remote switch local-https- Test your new remote
incus list --all-projectsListen on a specific IP Address#
If you don't want to listen on all interfaces, set the
INCUS_COMPOSE_HEALTHD_INCUS environment variable or call up with
--healthd-incus; see Network Configuration.
Installation#
Install script (recommended)#
The install script downloads the matching release for your OS/architecture and verifies it against the published SHA-256 checksums.
# Into a user-writable directory on your PATH (no sudo and self-update working)
curl -sSfL https://raw.githubusercontent.com/lxc/incus-compose/main/install.sh | sh -s -- -b ~/.local/bin
# Or System-wide into /usr/local/bin
curl -sSfL https://raw.githubusercontent.com/lxc/incus-compose/main/install.sh | sudo sh -s -- -b /usr/local/binPass a release tag as the final argument to pin a version, e.g.
... | sudo sh -s -- -b /usr/local/bin 1.0.0-beta15. Without a tag the latest
release is installed.
Binary#
Download a prebuilt archive from the Releases Page.
On Windows and MacOS, incus-compose runs as a client that drives a remote Incus host over HTTPS - see Installing on Windows.
Source#
# Build from source
git clone https://github.com/lxc/incus-compose
cd incus-compose
just build
# Or install directly
go install github.com/lxc/incus-compose/cmd/incus-compose@latestQuick Start#
1. Create a compose.yaml#
services:
web:
image: docker.io/nginx:alpine
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html:ro
app:
image: docker.io/node:20-alpine
working_dir: /app
volumes:
- ./app:/app
command: node server.js
depends_on:
- web2. Start your services#
incus-compose upThis will:
- Create an Incus project named after your directory
- Pull images if needed
- Create networks and volumes
- Start containers in dependency order
If your compose file uses health checks, incus-compose manages the ic-healthd sidecar automatically. It is transparent during normal use, but it is also a core component: all healthcheck, restart: and depends_on: service_healthy behavior is enforced by this sidecar, not by Incus. A working healthd is also required to bring up a project that has service_healthy dependencies - up waits for healthd to report them healthy, so a broken healthd makes up hang and fail (unless you pass --no-healthd). If health, restart, or startup behavior ever looks wrong, debug healthd first - see Health Checking and Debugging ic-healthd.
3. Check status#
incus-compose list4. View logs#
# View logs from all services
incus-compose logs
# Follow logs in real-time
incus-compose logs -f
# View logs from specific services
incus-compose logs web app5. Stop and remove#
# Stop and remove containers
incus-compose down
# Also remove images used by the services
incus-compose down --images
# Remove the whole project, including volumes and images (--volumes is an alias)
incus-compose down --projectcompose.incus.yaml Override#
compose.incus.yaml is loaded automatically when it exists next to the selected compose.yaml. This lets you keep an upstream or Docker-focused Compose file unchanged while adding Incus-specific settings in a separate file.
Typical uses:
- Remove Docker-only port publishing with
ports: !reset [] - Add explicit health checks for
ic-healthd - Set static service IPs on Incus networks
- Pass raw Incus network or instance options via
x-incus
Example compose.incus.yaml:
services:
web:
ports: !reset []
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost"]
networks:
default:
ipv4_address: 10.131.32.17/24
networks:
default:
x-incus:
ipv4.nat: "true"
ipv4.address: 10.131.32.1/24The file follows normal Compose merge rules. For example, !reset [] clears a list from the base file. See Compose Compatibility for details.
Key Differences from Docker Compose#
Real IP Addresses#
Incus gives each container a real IP on your network:
$ incus-compose list
KIND NAME INCUSNAME IMAGE STATUS ADDRESSES
instance web-1 web-1 docker.io/library/nginx:alpine Running 10.149.206.30You can access containers directly: curl http://10.149.206.30
Port Publishing#
Published ports use Incus proxy devices (not iptables NAT):
ports:
- "8080:80" # Host 8080 → Container 80Volumes#
Named volumes are Incus custom storage volumes with automatic UID/GID shifting:
volumes:
data:/app/data # Named volume with proper permissions
./local:/app # Bind mount (incusd must be on this machine)A bind mount is passed through to incusd, which opens the path on its own
filesystem, so it works when the server is this machine, over the Unix socket
or over HTTPS. Against a server elsewhere, either use a named volume or set
x-incus-compose.seed: true
to copy the files across.
Networks#
Each network becomes an Incus bridge network with deterministic naming:
networks:
frontend:
backend:Long network names are hashed to fit Linux interface limits (13 chars for dhclient compatibility).
Project Isolation#
Each compose project gets its own Incus project:
$ incus-compose -p myapp up
# Creates Incus project "myapp"
$ incus-compose -p testing up
# Separate Incus project "testing"Projects are isolated: separate networks, volumes, and instances.
Image Caching#
Images are cached in either the incus-compose-cache project or the project you set via the INCUS_COMPOSE_IMAGE_CACHE env.
This means:
- First run pulls from registry (slow)
- Subsequent runs copy from local cache (fast)
- No Docker Hub rate limits after initial pull
incus-compose downonly removes project images, cache persists
The cache project is created automatically on first use.
Next Steps#
- CLI Reference
- Builds
- Compose Compatibility - What features are supported
- Health Checking - Healthchecks and restart policies
- Environment Variables - How env vars work
- Why Incus? - Benefits over Docker