7.4 KiB
7.4 KiB
Incus Research Notes
Research conducted 2026-03-17. Incus development moves fast — verify against latest docs before implementing.
Incus Version & Status
- Latest: Incus 6.22 (February 28, 2026)
- LTS: Incus 6.0 (5 years support)
- Release cadence: Monthly feature releases
- Debian 13 (Trixie): Native packages available (
apt install incus)
OCI Container Support
- Added in: Incus 6.3 (July 2024) — 18+ months mature
- How it works: Pulls Docker/OCI images from registries, converts them, runs natively
- Setup:
incus remote add docker https://docker.io --protocol=oci - Usage:
incus launch docker:postgres:16-alpine my-postgres - Dependencies:
skopeoandumoci(included in Debian packages) - Maturity: Stable for production use, actively improved each release
System Containers vs OCI Containers
| Aspect | System Containers | OCI Containers |
|---|---|---|
| Purpose | Full OS simulation | Single application |
| Init | systemd (runs until stopped) | Entrypoint process only |
| Image size | 150-500MB per container | Smaller, layered |
| RAM overhead | ~50-100MB more than OCI | Comparable to Docker |
| Use case | Legacy apps, multi-process, full sysadmin | Microservices, pre-built images |
Networking
Managed Bridges
- Incus creates bridges with dnsmasq for DHCP + DNS
- Containers get DNS entries for their instance name automatically
- Container-to-container by name works out of the box
- Default bridge:
incusbr0(created duringincus admin init)
Port Forwarding — Proxy Devices
# Single port
incus config device add container http proxy \
listen=tcp:0.0.0.0:80 connect=tcp:127.0.0.1:80
# Port range
incus config device add container ports proxy \
listen=tcp:0.0.0.0:8080-9000 connect=tcp:127.0.0.1:8080-9000
# UDP (for HTTP/3)
incus config device add container h3 proxy \
listen=udp:0.0.0.0:443 connect=udp:127.0.0.1:443
Network Forwards (alternative to proxy devices)
incus network forward create <network_name> <listen_address>
Storage
Pool Backends
| Backend | Pros | Cons |
|---|---|---|
dir |
Simple, no setup | Slower, no snapshots |
zfs |
Fast snapshots, dedup, compression | Complex setup |
btrfs |
CoW, snapshots | Less tooling than ZFS |
lvm |
Reliable, mature | Block-level only |
Volume Operations
# Create volume
incus storage volume create <pool> <name>
# Attach to container
incus config device add <container> <device-name> disk \
pool=<pool> source=<volume-name> path=<mount-path>
# Mount host directory into container
incus config device add <container> <device-name> disk \
source=/host/path path=/container/path
Ansible Integration
Connection Plugin (community.general.incus)
- Exec commands inside containers without SSH
- Install:
ansible-galaxy collection install community.general - Usage:
ansible_connection: community.general.incus - Variables:
ansible_incus_remote,ansible_incus_project,ansible_host
Inventory Plugin (community.general.incus_inventory)
- Auto-discovers running Incus instances
- Dynamic inventory source
Third-Party: kmpm.incus Collection
- Dedicated modules:
incus_instance,incus_network,incus_profile - Status: Beta/WIP, actively maintained
- Install:
ansible-galaxy collection install git+https://github.com/kmpm/ansible-collection-incus.git
Practical Approach for This Project
Use ansible.builtin.command with incus CLI directly. Simpler, more transparent, easier to debug than collections. Connection plugin useful for exec-inside-container tasks.
Incus Initialization (Preseed)
Non-interactive setup via incus admin init --preseed:
config: {}
networks:
- config:
ipv4.address: auto
ipv4.nat: "true"
ipv6.address: none
name: incusbr0
type: bridge
storage_pools:
- config:
source: /var/lib/incus/storage-pools/default
name: default
driver: dir
profiles:
- config: {}
devices:
eth0:
name: eth0
network: incusbr0
type: nic
root:
path: /
pool: default
type: disk
name: default
Environment Variables in OCI Containers
Pass via --config flags at launch time:
incus launch docker:postgres:16-alpine gitea-db \
--config environment.POSTGRES_DB=gitea \
--config environment.POSTGRES_USER=gitea \
--config environment.POSTGRES_PASSWORD=secretpass
Update existing container:
incus config set gitea-db environment.POSTGRES_PASSWORD=newpass
incus restart gitea-db
Backup & Restore
Snapshots (fast, local only)
incus snapshot create <instance> <snapshot-name>
incus snapshot restore <instance> <snapshot-name>
incus snapshot list <instance>
Exports (portable, for off-site backup)
incus export <instance> backup.tar.gz --optimized-storage
incus import backup.tar.gz <new-instance-name>
Volume Exports
incus storage volume snapshot <pool> <volume> <snapshot-name>
incus storage volume export <pool> <volume> volume-backup.tar.gz
incus storage volume import <pool> volume-backup.tar.gz <new-volume-name>
Database Dumps (same pattern as Docker)
incus exec gitea-db -- pg_dump -U gitea gitea > gitea-db.sql
incus exec outline-db -- pg_dump -U outline outline > outline-db.sql
6.22 Enhancements
- Direct backup streaming (no temporary disk files)
- Disk-only snapshot restore (revert storage without config)
Auto-Start / Restart Policy
# Start container on host boot
incus config set <container> boot.autostart true
# Set start order (lower = earlier)
incus config set <container> boot.autostart.priority 10
# Start delay (seconds, for dependency ordering)
incus config set <container> boot.autostart.delay 5
No automatic restart on crash (unlike Docker's restart: unless-stopped). Options:
- Accept manual restart for crashes (rare)
- Add a systemd watchdog timer on the host
- Use
incus monitorto watch for state changes
Running Docker Inside Incus (for Gitea Runner)
If needed for the runner:
incus launch images:debian/13/cloud gitea-runner-host
incus config set gitea-runner-host security.nesting true
incus exec gitea-runner-host -- apt install docker.io
# Then run act_runner inside this system container
Cloud-init Support
System containers with /cloud images support cloud-init:
incus launch images:debian/13/cloud my-container \
--config cloud-init.user-data="#cloud-config
packages:
- curl
- git
"
Note: Cloud-init runs only on first boot. Not applicable to OCI containers.