Skip to main content

Linux Cloud-init Blocks for Debian 12

750 words·
Linux Cloud-init Debian Bash Terraform Hetzner Cloud
Table of Contents

The following cloud-init configurations were tested on Debian 12 virtual machines hosted on Hetzner Cloud.


Terraform Project
#

File and Folder Structure
#

The file and folder structure of the Terraform project looks like this:

hetzner-cloud-init
├── compute.tf
├── terraform.tf
└── variables.tf

Create Project Folder
#

# Create a folder for the Terraform project
mkdir -p hetzner-cloud-init && cd hetzner-cloud-init

Terraform Provider
#

  • terraform.tf
# Terraform Provider
terraform {
  required_providers {
    hcloud = {
      source = "hetznercloud/hcloud"
      version = "~> 1.50"
    }
  }
}

provider "hcloud" {
  token = var.hcloud_token
}

Variables
#

  • variables.tf
# Hetzner Cloud Project Token
variable "hcloud_token" {
  sensitive = true
  default = "6LrVzPVm-some-hetzner-project-token"
}

Compute
#

  • compute.tf
# Example VM: Cloud-Init Configuration
locals {
  example_vm_cloud_init = <<-EOF
    #cloud-config
    <... replace config ...>

  EOF
}

# Example VM
resource "hcloud_server" "example_vm" {
  name        = "example-vm"
  server_type = "cx22"
  image       = "debian-12"
  location    = "nbg1"
  ssh_keys    = ["ubuntu@hetznervm"]
  labels = {
    purpose = "example-vm"
  }

  public_net {
    ipv4_enabled = true   # Public IP for example_vm
    ipv6_enabled = false
  }

  # Cloud-Init Configuration
  user_data = local.example_vm_cloud_init
}


# Outputs
output "example_vm_public_ip" {
  value       = hcloud_server.example_vm.ipv4_address
}

Cloud-Init Blocks
#

Set Hostname & FQDN
#

# Example VM: Cloud-Init Configuration
locals {
  example_vm_cloud_init = <<-EOF
    #cloud-config
    hostname: example-host
    fqdn: example-host.mydomain.com
    manage_etc_hosts: true
  EOF
}
  • manage_etc_hosts: true Allows cloud-init to manage the /etc/hosts file.

Verify the hostname after the VM was created:

# List hostname:
hostname

# Shell output:
example-host
# List FQDN:
hostname --fqdn

# Shell output:
example-host.mydomain.com
# Cat hosts file
cat /etc/hosts

# Shell output:
127.0.1.1 example-host.mydomain.com example-host
127.0.0.1 localhost

Create Users
#

The following cloud-init block creates to users, user-one can be accessed via an SSH key, user-one can be accessed via password, the password must be changed after the first login.

# Example VM: Cloud-Init Configuration
locals {
  example_vm_cloud_init = <<-EOF
    #cloud-config
    users:
      - name: user-one
        gecos: some user
        groups: sudo
        sudo: "ALL=(ALL) NOPASSWD:ALL"
        shell: /bin/bash
        system: false
        create_home: true
        ssh_authorized_keys:
          - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCn9/khzcOUDw2odkYGbxBu9kaZ+fO/hwOo3+I6QPkccV/FPlZdGkRqY4fWYvUToFhCwjRw0YdfGgEvc+cKKCz7hba0vx58MIoT0sCkRwC7I1nBhBGioSKcjYOVIctk1maPMLXP7cDC3ejoaUEbIpEoD92nUf1tt87adsj5PsT0Fdq+isrLgoI8V1fHyCxo2vRoDjrJIyOlAn1rNOT7S3tWApfqmoOkuFyf9g8F23QeFHiuA7mqFKfhg5OM6RM9bXpKJ1AZaYPxPeF3NDhbEbtDgqWFqAfUNw2cs5Qt4NrAHfGYHEGcLN2XhINa5zYjA6yQa7oVv1lwv3JziKP6b8WWxQrC1bwBVXN7wNn8TI5Y7Ssa7E72zcigciOhu5+jXgL+tDhXN4NhmlikilVfzuAA7BRTF6D57BAXYCdry/+jqmASJYVVhmvSPukcwiWdnZXE1Nlhqk1uf9kPGitQGlAeAwqurPi2xn/f3CM3y9UrzyCKo/cOI2M7djfAMhEdcX6uuuNPA+L1q1bJFTGePHxB7m1Ar7gYKSn2fP0xfhrIvn28PwJukOkq6jkEKjLSv2UDhCNxhjNsYLgtYJeedy0BRdlKfLBESRyyp8KRa58pIyEuI12JM+z3pLLl2JOeSQOe5GH8Uwokbk5+63IVIVbFtQ7VlMmK4DtzmAyEgeEVcQ== ubuntu@hetznervm

      - name: user-two
        gecos: another user
        groups: sudo
        sudo: "ALL=(ALL) NOPASSWD:ALL"
        shell: /bin/bash
        system: false
        create_home: true

    chpasswd:
      list: |
                user-two:mysecurepw
  EOF
}

Disable Password and Root SSH Login
#

# Example VM: Cloud-Init Configuration
locals {
  example_vm_cloud_init = <<-EOF
    #cloud-config
    ssh_pwauth: false
    disable_root: true

    users:
      - name: user-one
        gecos: some user
        groups: sudo
        sudo: "ALL=(ALL) NOPASSWD:ALL"
        shell: /bin/bash
        system: false
        create_home: true
        ssh_authorized_keys:
          - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCn9/khzcOUDw2odkYGbxBu9kaZ+fO/hwOo3+I6QPkccV/FPlZdGkRqY4fWYvUToFhCwjRw0YdfGgEvc+cKKCz7hba0vx58MIoT0sCkRwC7I1nBhBGioSKcjYOVIctk1maPMLXP7cDC3ejoaUEbIpEoD92nUf1tt87adsj5PsT0Fdq+isrLgoI8V1fHyCxo2vRoDjrJIyOlAn1rNOT7S3tWApfqmoOkuFyf9g8F23QeFHiuA7mqFKfhg5OM6RM9bXpKJ1AZaYPxPeF3NDhbEbtDgqWFqAfUNw2cs5Qt4NrAHfGYHEGcLN2XhINa5zYjA6yQa7oVv1lwv3JziKP6b8WWxQrC1bwBVXN7wNn8TI5Y7Ssa7E72zcigciOhu5+jXgL+tDhXN4NhmlikilVfzuAA7BRTF6D57BAXYCdry/+jqmASJYVVhmvSPukcwiWdnZXE1Nlhqk1uf9kPGitQGlAeAwqurPi2xn/f3CM3y9UrzyCKo/cOI2M7djfAMhEdcX6uuuNPA+L1q1bJFTGePHxB7m1Ar7gYKSn2fP0xfhrIvn28PwJukOkq6jkEKjLSv2UDhCNxhjNsYLgtYJeedy0BRdlKfLBESRyyp8KRa58pIyEuI12JM+z3pLLl2JOeSQOe5GH8Uwokbk5+63IVIVbFtQ7VlMmK4DtzmAyEgeEVcQ== ubuntu@hetznervm

  EOF
}

Install Package & Run Commands
#

The following cloud-init block installs apache2 and run some commands that also create an index.html. It can take a minute till apache is ready.

# Example VM: Cloud-Init Configuration
locals {
  example_vm_cloud_init = <<-EOF
    #cloud-config
    package_update: true
    package_upgrade: true

    packages:
      - apache2

    runcmd:
      - systemctl start apache2
      - sudo systemctl enable apache2
      - echo "somewebsite" > /var/www/html/index.html

  EOF
}
# Verify apache2 is running (wait a few minutes)
systemctl status apache2

# Verify the index.html file
curl localhost:80

Create and Run Script
#

The following cloud-init block creates a bash script that installs docker and runs it. In can take a while after till docker is available on the created VM.

# Example VM: Cloud-Init Configuration
locals {
  example_vm_cloud_init = <<-EOF
    #cloud-config
    package_update: true
    package_upgrade: true

    write_files:
      - path: /root/install-docker.sh
        permissions: '0755'
        content: |
          #!/bin/bash
          apt-get update
          apt-get install -y ca-certificates curl
          install -m 0755 -d /etc/apt/keyrings
          curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
          chmod a+r /etc/apt/keyrings/docker.asc
          echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list
          apt-get update
          apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin          

    runcmd:
      - /root/install-docker.sh

  EOF
}
# Verify Docker is installed (wait a few minutes)
docker ps

Run Only Shell Scripts
#

The following cloud-init configuration runs only shell scripts, the script starts with #!/bin/sh instead of #cloud-config.

# Example VM: Cloud-Init Configuration
locals {
  example_vm_cloud_init = <<-EOF
    #!/bin/sh
    apt-get update
    apt-get install -y ca-certificates curl
    install -m 0755 -d /etc/apt/keyrings
    curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
    chmod a+r /etc/apt/keyrings/docker.asc
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list
    apt-get update
    apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin

  EOF
}
# Verify Docker is installed (wait a few minutes)
docker ps

Access VM
#

Apply Resources
#

# Initialize Terraform project
terraform init

# Validate Terraform configuration
terraform validate

# Test the deployment
terraform plan
# Apply terraform configuration
terraform apply -auto-approve

# Shell output:
example_vm_public_ip = "116.203.136.198"

Access VM
#

# Remove old host-keys
ssh-keygen -R 116.203.136.198

# Access Hetzner Cloud example VM
ssh root@116.203.136.198



Links #

# Ubuntu Cloud-init blocks
https://github.com/canonical/cloud-init/blob/main/doc/examples/cloud-config-apt.txt