Skip to main content
  1. Posts/

Using an LXD Storage Bucket as a Terraform S3 backend

·467 words·3 mins

Update 2023-12-16: Added skip_requesting_account_id = true to the backend configuration.

With the latest version 5.12 of LXD it is now possible to use the Storage Bucket feature of LXD as Terraform S3 backend.

Prerequisites #

  1. You are familiar with Terraform and LXD
  2. I would recommend looking at Stéphane Graber’s introduction LXD’s S3 API.
  3. For this to work, you must have a valid SSL certificate for LXD’s Storage Bucket API, otherwise Terraform will report an error:
x509: "root@servername certificate is not standards compliant"

I would recommend using a reverse proxy like Traefik that can provide an SSL certificate like Let’s Encrypt.

Setup the Storage Bucket #

  1. Create a new storage bucket: lxc storage bucket create default terraform
  2. Get the access and secret key: lxc storage bucket key show default terraform admin
description: Admin user
role: admin
access-key: A234234....
secret-key: N234Masdf....
name: admin
  1. Set environment variables that will be used later by Terraform:
$ export AWS_ACCESS_KEY_ID="A234234...."
$ export AWS_SECRET_ACCESS_KEY="N234Masdf...."

Setup Terraform to use the Storage Bucket as S3 backend #

  1. Create the provider.tf file and take a look at the provider documentation to understand how the LXD connection works:
terraform {
  required_providers {
    lxd = {
      source  = "terraform-lxd/lxd"
      version = "1.9.0"
    }
  }
}

provider "lxd" {
  accept_remote_certificate    = true
  generate_client_certificates = false
}
  1. Create the backend.tf file and set the correct endpoint:
terraform {
  backend "s3" {
    bucket   = "terraform"
    key      = "state.json"
    endpoint = "https://YOUR_LXD_S3_API_HOSTNAME"

    region                      = "main"
    skip_credentials_validation = true
    skip_metadata_api_check     = true
    skip_region_validation      = true
    skip_requesting_account_id  = true
    force_path_style            = true
  }
}
  1. Create the instance.tf file to deploy an instance:
resource "lxd_container" "ubuntu" {
  name      = "ubuntu"
  image     = "ubuntu:22.04"
  ephemeral = false

  limits = {
    "cpu"    = 1
    "memory" = "512MiB"
  }
}
  1. Run terraform init

  2. Run terraform plan and you should see the following output:

Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # lxd_container.ubuntu will be created
  + resource "lxd_container" "ubuntu" {
      + ephemeral        = false
      + id               = (known after apply)
      + image            = "ubuntu:22.04"
      + ip_address       = (known after apply)
      + ipv4_address     = (known after apply)
      + ipv6_address     = (known after apply)
      + limits           = {
          + "cpu"    = "1"
          + "memory" = "512MiB"
        }
      + mac_address      = (known after apply)
      + name             = "ubuntu"
      + privileged       = false
      + profiles         = (known after apply)
      + start_container  = true
      + status           = (known after apply)
      + target           = (known after apply)
      + type             = (known after apply)
      + wait_for_network = true
    }

Plan: 1 to add, 0 to change, 0 to destroy.

───────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee to take exactly these actions if you run "terraform apply" now.