Extras

The Compose spec covers what is portable across engines. Everything Incus can do beyond it is reachable from the same compose file, through three escape hatches:

For what the Compose spec itself supports, see Compose Compatibility.

The Incus override file

If a compose.incus.yaml file exists next to the selected compose.yaml, incus-compose loads it automatically as an additional Compose file. Use it for Incus-specific overrides while keeping the upstream Docker Compose file unchanged.

compose.yaml
compose.incus.yaml

Example compose.incus.yaml:

services:
  web:
    ports: !reset []
    x-incus:
      limits.memory: 512MiB

networks:
  default:
    x-incus:
      ipv4.address: 10.100.0.2/24
      ipv4.gateway: 10.100.0.1

Running with the base file also applies the Incus override when present:

incus-compose -f compose.yaml up

The override file follows normal Compose merge rules. For example, !reset [] clears a list from the base file.

x-incus

x-incus keys are passed verbatim to Incus and never interpreted by incus-compose, so any option Incus accepts works the day Incus adds it.

Instances

Any Incus instance config key can be set via the x-incus extension block on a service definition. Keys are passed verbatim to the Incus instance config on creation.

services:
  web:
    image: docker.io/nginx:alpine
    x-incus:
      limits.memory: 512MiB
      limits.cpu: "2"
      security.privileged: "true"

Any Incus instance option is accepted.

Networks

Any Incus network config key can be set via the x-incus extension block on a network definition. Keys are passed verbatim to the Incus network config on creation.

networks:
  backend:
    x-incus:
      ipv4.address: 10.100.0.1/24
      ipv6.address: fd42:abc::1/64
      ipv4.dhcp.ranges: 10.100.0.100-10.100.0.200

Any Incus bridge network option is accepted.

Volumes

Any Incus storage volume config key can be set via the x-incus extension block on a volume definition. Keys are passed verbatim to the Incus volume config on creation.

volumes:
  data:
    x-incus:
      size: 10GiB
      block.filesystem: ext4

Any Incus storage volume option is accepted.

The x-incus block also works inline on a volume entry, which is the only way to set options on a bind mount (a bind's source is a path, not a named volume):

services:
  web:
    volumes:
      - type: bind
        source: ./html
        target: /usr/share/nginx/html
        x-incus:
          security.shifted: "false"

An inline x-incus block takes precedence over the matching named volume definition. See Volume Permissions for security.shifted.

x-incus-compose

These are implemented by incus-compose itself rather than passed through.

Devices

Attach raw Incus devices to a service's instances with the x-incus-compose.devices block. Each named entry is passed to Incus verbatim; the type key selects the device type and is required.

services:
  web:
    image: docker.io/nginx:alpine
    x-incus-compose:
      devices:
        gpu0:
          type: gpu
          gputype: physical
          pci: "0000:01:00.0"
        extra-disk:
          type: disk
          source: /dev/sdb
          path: /mnt/data

This is an escape hatch for device types incus-compose does not model natively (gpu, unix-char, usb, ...). Compose-managed devices (ports, volumes, networks) should use their native keys. Any Incus device is accepted; keys collide by device name, so a raw device sharing a name with a compose-managed one overrides it.

A gpu device's id: is the DRM ID (incus info --resources, under GPU -> DRM -> ID, typically a small integer), not the /dev/dri/cardN or renderDNNN filename - the device name fails with "Failed to detect requested GPU device" otherwise.

A unix-hotplug device matches on idVendor/idProduct present on a device's own udev attributes. It cannot reach a character device created several sysfs levels below the matched USB node by a kernel driver that fans out into a different subsystem - the DVB character devices a dvb-usb driver creates are one such case. That case is accepted with no error, and the device simply never appears in the instance.

Since 1.0.0-beta.22

Volume pool

Set x-incus-compose.pool on a named volume to place it in a specific Incus storage pool. Without this the client's default storage pool is used.

volumes:
  data:
    x-incus-compose:
      pool: fast-ssd

services:
  app:
    image: docker.io/myapp:latest
    volumes:
      - data:/var/lib/app

To move an existing volume to a different pool, stop the project, then use incus storage volume move via the incus-compose incus passthrough:

incus-compose stop
incus-compose incus storage volume move default/vol-library ext/vol-library
incus-compose start

Then update x-incus-compose.pool in your compose file and run incus-compose up --recreate to reattach.

Volumes are stored with a vol- prefix. Long names are hashed, so my-very-long-volume-name may become vol-a1b2c3d4.... Use incus storage volume list to find the actual name before moving:

incus-compose incus storage volume list default

Volume seeding

A bind mount is normally passed through: Incus attaches a disk device and incusd resolves the source path on its own filesystem, so the files have to be on the server. Set x-incus-compose.seed: true inline on the volume entry to copy the source instead, which is how a bind mount works against a server that is not your machine:

services:
  web:
    image: docker.io/library/busybox:glibc
    volumes:
      - type: bind
        source: ./html
        target: /www
        read_only: true
        x-incus-compose:
          seed: true

The source is read by incus-compose, on the machine you run it from, and must exist there. What happens next depends on what it is:

Seeding is a copy, in one direction. Nothing written inside the container comes back out, so it does not replace a named volume for data you mean to keep.

Seeding is off by default: bind mounts are plain pass-through unless you ask.

Since: v1.0.0

Backup

Configure incus-compose backup with the top-level x-incus-compose.backup extension:

x-incus-compose:
  backup:
    pool: hdd

services:
  app:
    image: docker.io/nginx:alpine
    volumes:
      - data:/var/lib/app

volumes:
  data:
Key Description
pool Storage pool the backup copies live in. Defaults to the client's default pool; a separate disk is what makes them useful.
meta_volume Volume holding the manifests and the locks. Defaults to ic-backup-manifest.

The key only has to be present, so an empty backup: is enough to opt in. See CLI Reference - backup for the commands.

Healthd

Configure the ic-healthd sidecar with the top-level x-incus-compose.healthd extension:

x-incus-compose:
  healthd:
    scope: global
    incus: https://:8443
    network: :default
    workers: 128
    restart-workers: 32
    x-incus:
      limits.cpu: 2
      limits.memory: 256MiB

services:
  web:
    image: docker.io/nginx:alpine
Key Description
scope global (one shared daemon in the Incus incus-compose project, the default) or project (a sidecar of this project's own). Loses to a scope the Incus project already carries.
incus The Incus API URL healthd connects to. Defaults to the bridge gateway and the connection's port.
network <project>:<network> for a managed network, or a plain bridge name. Defaults to the bridge of the project the daemon runs in.
workers Health checks the daemon runs at once, over every project it watches. Default 128.
restart-workers Restarts it runs at once, over every project it watches. Default 32.
x-incus Raw Incus instance config for the sidecar, e.g. limits.*.
external Use a healthd you run yourself; incus-compose neither creates nor looks one up.

scope, incus and network are also --healthd-scope, --healthd-incus and --healthd-network on the CLI, which override the compose file. See Health Checking - Scope and Network Configuration.

With scope: global the daemon is shared, so the first project to bring it up supplies incus, workers, restart-workers and x-incus; a later project asking for something different is warned and ignored.

When this option is set, incus-compose does not create compose-managed Incus network resources for service network attachments. Instances use the network devices provided by the copied profile instead. Service-level static IP assignments (ipv4_address / ipv6_address) are not supported in this mode because incus-compose does not create explicit NIC devices.