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