Skip to content
Français
Conteneurs & Orchestration medium

Managing Incus with Terraform (the lxc/incus provider)

20 min de lecture

Read this page in French

incus logo

Typing CLI commands to create an instance is fine for learning; describing it as Infrastructure as Code is what makes an infrastructure reproducible. The lxc/incus provider, official to the LXC project, manages Incus instances, profiles, networks and pools with Terraform or OpenTofu. This guide writes a first main.tf, applies it to an Incus server, then targets a remote server by token, and lists the available resources. Tested on Incus 7.0 with OpenTofu 1.12 and provider 1.1.1.

What you will learn

  • Declare the lxc/incus provider, local or remote.
  • Create an instance and a profile with incus_instance and incus_profile.
  • Target a remote server through a trust token.
  • The available resources and the common traps.

Prerequisites

  • Terraform or OpenTofu installed (the commands below use tofu, terraform behaves identically).
  • A reachable Incus server: either local through the Unix socket, or remote with the API exposed (core.https_address).
  • The Incus basics: see First steps with Incus.

Declaring the provider

The provider is named lxc/incus on the Terraform and OpenTofu registries. On a local server it connects to the Incus Unix socket; no token is needed.

terraform {
required_providers {
incus = {
source = "lxc/incus"
version = "~> 1.1"
}
}
}
provider "incus" {
remote {
name = "local"
address = "unix://"
}
default_remote = "local"
}

The remote block declares an Incus server. unix:// targets the local daemon; the remote variant with a token comes further down.

Creating a first instance

You describe a profile (reusable configuration) and an instance that applies it. The image field follows the remote:alias format: here images:debian/13 pulls from the community image server.

resource "incus_profile" "web" {
name = "tf-web-profile"
config = {
"limits.memory" = "512MiB"
}
}
resource "incus_instance" "web" {
name = "tf-web"
image = "images:debian/13"
type = "container"
profiles = ["default", incus_profile.web.name]
config = {
"boot.autostart" = "true"
"limits.cpu" = "1"
}
}
  1. Initialise the directory: tofu init downloads the provider.

    - Installing lxc/incus v1.1.1...
    - Installed lxc/incus v1.1.1 (signed, key ID C638974D64792D67)
    OpenTofu has been successfully initialized!
  2. Apply: tofu apply -auto-approve creates the profile then the instance.

    incus_profile.web: Creation complete after 0s [name=tf-web-profile]
    incus_instance.web: Creation complete after 5s [name=tf-web]
    Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
  3. Check on the Incus side: the instance runs, with both profiles applied.

    +--------+---------+-----------+----------+
    | NAME | STATE | TYPE | LOCATION |
    +--------+---------+-----------+----------+
    | tf-web | RUNNING | CONTAINER | node1 |
    +--------+---------+-----------+----------+

On a cluster, the instance is placed automatically (here node1) unless you set target = "node2" in the resource. The profiles column of incus config show tf-web confirms default then tf-web-profile.

Targeting a remote server

To drive a remote Incus server, you declare its address and a single-use trust token, generated on the server with incus config trust add.

provider "incus" {
generate_client_certificates = true
accept_remote_certificate = true
default_remote = "cluster"
remote {
name = "cluster"
address = "https://192.168.10.184:8443"
token = "eyJjbGllbnRfbmFtZSI6..."
}
}

The available resources

The provider covers the essentials of the Incus object model. Every resource accepts a remote field to target one of the declared servers.

ResourceRole
incus_instanceContainer or VM (type = "virtual-machine")
incus_profileReusable configuration profile
incus_networkNetwork (bridge, OVN)
incus_storage_poolStorage pool (dir, ZFS, LVM)
incus_storage_volumeCustom volume
incus_projectProject (compartmentalisation)
incus_imageImage managed or copied into the pool

The traps worth knowing

Three defaults regularly surprise people, and are worth keeping in mind to avoid unexpected apply runs.

  • type defaults to container: for a virtual machine you have to spell out type = "virtual-machine".
  • An explicit profiles list replaces the default: if you pass a list, the default profile is not added automatically; include it when you need it, as above.
  • The image must be reachable from the remote: image = "images:debian/13" assumes the images remote exists on the server side, which it does by default.

Destroying the infrastructure

Like any Terraform setup, the configuration is reversible: tofu destroy removes the instance and the profile it created. That is the point of IaC against manual clicking: the state is described, versioned and disposable.

Fenêtre de terminal
tofu destroy -auto-approve

Key points

  • The lxc/incus provider manages Incus as code with Terraform or OpenTofu.
  • Locally it uses the Unix socket (address = "unix://") with no token.
  • Remotely: a single-use trust token, then a client certificate that is remembered.
  • Trap: type defaults to container; an explicit profiles list does not add default.
  • Resources: incus_instance, incus_profile, incus_network, incus_storage_pool, incus_project.

FAQ: common questions about Terraform 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