Skip to content
Français
Conteneurs & Orchestration medium

Managing Incus with Ansible: connection plugin and provisioning

20 min de lecture

Read this page in French

incus logo

Ansible and Incus meet on two uses worth keeping apart: configuring the inside of instances that already exist (the community.general.incus connection plugin, which goes through incus exec with no SSH), and creating the instances themselves (provisioning). This guide covers both, with one important point of honesty: there is no native Ansible module dedicated to Incus, so provisioning goes through lxd_container. Everything is tested on Incus 7.0.

What you will learn

  • The difference between the connection plugin and provisioning.
  • Manage an existing instance with community.general.incus, without SSH.
  • Build a dynamic inventory from the running instances.
  • Create an Incus instance with community.general.lxd_container.
  • The real state of Ansible support for Incus.

Prerequisites

  • Ansible installed with the community.general collection (shipped with the full ansible package, otherwise ansible-galaxy collection install community.general).
  • The incus client on the machine running Ansible: both approaches rely on it (incus exec, local socket).
  • Incus basics: see First steps with Incus.

Two uses not to confuse

Before writing anything, pick the right tool for the need. The table sums up the split, which structures the rest of this guide.

NeedToolHow
Configure the inside of an existing instancecommunity.general.incus connection pluginAnsible runs incus exec, no SSH
List the instances that existcommunity.general.incus inventory pluginAnsible calls the incus CLI
Create or delete an instancecommunity.general.lxd_container moduleThrough the API, with an explicit Incus socket
Build an imagePacker plus AnsibleSee the dedicated guide

Managing an existing instance (connection plugin)

The community.general.incus plugin treats an instance as a remote machine, but without SSH: every task runs through incus exec. It is ideal for applying configuration to a container that is already running.

The inventory declares the connection and the instance name on the Incus side.

inventory.yml
all:
hosts:
tf-web:
ansible_connection: community.general.incus
ansible_incus_remote: local
ansible_incus_project: default
ansible_incus_host: tf-web
ansible_user: root

The playbook first installs Python if needed (through raw, which does not depend on Python), then applies ordinary tasks.

play-connect.yml
- name: Configure an existing instance through incus
hosts: tf-web
gather_facts: false
tasks:
- name: Make sure python3 is present
ansible.builtin.raw: command -v python3 || (apt-get update -qq && apt-get install -y -qq python3)
changed_when: false
- name: Drop a marker file
ansible.builtin.copy:
content: "managed by ansible through the incus connection plugin\n"
dest: /root/ansible-marker.txt
mode: "0644"

Execution goes through incus exec, not the network: no SSH key, no port opened inside the instance.

PLAY [Configure an existing instance through incus] ****
TASK [Make sure python3 is present] ********************
TASK [Drop a marker file] ******************************
tf-web : ok=3 changed=2 unreachable=0 failed=0

Discovering instances with the inventory plugin

Writing the inventory by hand stops scaling as soon as instances come and go. The community.general.incus inventory plugin, added in community.general 12.0.0, builds the list by calling the incus CLI:

incus.yml
plugin: community.general.incus
filters:
- type=virtual-machine
- status=running
Fenêtre de terminal
ansible-inventory -i incus.yml --graph

The filters option takes the same expressions as incus list, so the inventory narrows to exactly the instances you mean. The plugin can cover several remotes through remotes, restrict itself to one project with the remote:project syntax, and by default names hosts INSTANCE.PROJECT.REMOTE, which keeps two instances of the same name in different projects apart. Set host_fqdn: false to get the short name back.

Combined with the connection plugin, this gives a setup where nothing is written down by hand: the inventory reflects what is actually running, and each task reaches the instance without SSH.

Provisioning an instance (lxd_container)

To create an instance, you use the community.general.lxd_container module. That module targets LXD by default: to aim at Incus, you point its socket explicitly with url: unix:/var/lib/incus/unix.socket.

play-provision.yml
- name: Provision an Incus instance
hosts: localhost
connection: local
gather_facts: false
tasks:
- name: Create and start ans-web on Incus
community.general.lxd_container:
name: ans-web
url: unix:/var/lib/incus/unix.socket
state: started
source:
type: image
mode: pull
server: https://images.linuxcontainers.org
protocol: simplestreams
alias: debian/13
profiles: ["default"]
wait_for_ipv4_addresses: true
timeout: 150

The module is idempotent: a second run recreates nothing. The instance appears on the Incus side, here placed on node2 of the cluster.

localhost : ok=2 changed=1 unreachable=0 failed=0
+---------+---------+-----------+----------+
| NAME | STATE | TYPE | LOCATION |
+---------+---------+-----------+----------+
| ans-web | RUNNING | CONTAINER | node2 |
+---------+---------+-----------+----------+

Key points

  • Two distinct uses: configuring the inside (connection plugin) against creating (provisioning).
  • community.general.incus runs tasks through incus exec, with no SSH.
  • The inventory plugin of the same name builds the host list from incus list, with filters and multiple remotes.
  • A minimal images: image needs Python installed with raw before the modules run.
  • You provision with lxd_container plus url: unix:/var/lib/incus/unix.socket.
  • No native Incus module: use lxd_container, or the CLI and API directly.

FAQ: common questions about Ansible and Incus

Next steps

Is this site useful to you?

Fewer than 1% of readers support this site.

I maintain more than 700 free guides, with no ads and no tracking. Any support, even a symbolic one, helps cover hosting and keeps these resources free. Thank you for the help.

The form does not show? Open Ko-fi in a new tab.

Subscribe and follow my DevSecOps work on LinkedIn