Skip to content

danylomikula/terraform-hcloud-server

Repository files navigation

Hetzner Cloud Server Terraform Module

Release Pre-Commit License

Terraform module for managing Hetzner Cloud servers with full support for all provider features including private networks, firewalls, placement groups, and advanced configuration options.

Features

  • Multi-server management: Manage multiple servers with a single module invocation.
  • Private networking: Attach servers to Hetzner Cloud private networks with static IPs.
  • Firewall integration: Apply firewalls at server creation time.
  • Placement groups: Control server placement for high availability or performance.
  • Flexible configuration: Support for all hcloud_server resource attributes.
  • Common settings: Apply SSH keys, labels, and firewalls to all servers.
  • Rich outputs: Detailed server information grouped by location, type, and more.

Usage

Basic Single Server

module "server" {
  source  = "danylomikula/server/hcloud"
  version = "~> 3.0"

  servers = {
    web-1 = {
      server_type = "cx23"
      location    = "nbg1"
      image       = "ubuntu-24.04"
      ssh_keys    = [12345]

      labels = {
        role = "webserver"
      }
    }
  }
}

Servers with Private Network

module "network" {
  source  = "danylomikula/network/hcloud"
  version = "~> 1.0"

  create_network = true
  name           = "app-network"
  ip_range       = "10.0.0.0/16"

  subnets = {
    main = {
      type         = "cloud"
      network_zone = "eu-central"
      ip_range     = "10.0.1.0/24"
    }
  }
}

module "servers" {
  source  = "danylomikula/server/hcloud"
  version = "~> 3.0"

  servers = {
    app-01 = {
      server_type = "cx23"
      location    = "nbg1"
      image       = "ubuntu-24.04"

      networks = [{
        subnet_id = module.network.subnets["main"].id
        ip        = "10.0.1.10"
      }]
    }

    app-02 = {
      server_type = "cx23"
      location    = "fsn1"
      image       = "ubuntu-24.04"

      networks = [{
        subnet_id = module.network.subnets["main"].id
        ip        = "10.0.1.11"
      }]
    }
  }

  common_ssh_keys = [12345]
  common_labels = {
    cluster    = "app"
    managed_by = "terraform"
  }
}

Complete Example with SSH Key Generation

# Generate SSH key.
module "ssh_key" {
  source  = "danylomikula/ssh-key/hcloud"
  version = "~> 1.0"

  create_key = true
  name       = "cluster-key"
  algorithm  = "ED25519"
}

# Create network.
module "network" {
  source  = "danylomikula/network/hcloud"
  version = "~> 1.0"

  create_network = true
  name           = "web-network"
  ip_range       = "10.100.0.0/16"

  subnets = {
    web = {
      type         = "cloud"
      network_zone = "eu-central"
      ip_range     = "10.100.1.0/24"
    }
  }
}

# Create servers.
module "server" {
  source  = "danylomikula/server/hcloud"
  version = "~> 3.0"

  servers = {
    web-1 = {
      server_type = "cx23"
      location    = "nbg1"
      image       = "ubuntu-24.04"

      networks = [{
        subnet_id = module.network.subnets["web"].id
        ip        = "10.100.1.10"
      }]

      labels = {
        role = "webserver"
      }
    }
  }

  common_ssh_keys = [module.ssh_key.ssh_key_id]

  common_labels = {
    managed_by = "terraform"
  }
}

See complete example for a full working configuration.

Finding Available Server Types

Install hcloud CLI:

brew install hcloud

Setup guide: https://github.com/hetznercloud/cli/blob/main/docs/tutorials/setup-hcloud-cli.md

List server types:

hcloud server-type list

Locations

List locations:

hcloud location list

Documentation: https://docs.hetzner.com/cloud/general/locations/

Images

List available images:

# List all system images.
hcloud image list --type system

# Filter for specific OS.
hcloud image list --type system | grep ubuntu
hcloud image list --type system | grep rocky

Dynamic Image Selection with Data Source

Use hcloud_image data source to automatically select the latest image:

# Find latest Rocky Linux 9 for x86 architecture.
data "hcloud_image" "rocky9" {
  with_selector     = "os_flavor=rocky"
  with_architecture = "x86"
  most_recent       = true
}

# Find latest Ubuntu for ARM64.
data "hcloud_image" "ubuntu_arm" {
  name              = "ubuntu-24.04"
  with_architecture = "arm"
}

# Use in server configuration.
module "servers" {
  source  = "danylomikula/server/hcloud"
  version = "~> 3.0"

  servers = {
    app-01 = {
      server_type = "cx23"
      image       = data.hcloud_image.rocky9.name  # Uses latest Rocky 9.
      location    = "nbg1"
    }

    app-arm = {
      server_type = "cax11"  # ARM64 server.
      image       = data.hcloud_image.ubuntu_arm.name
      location    = "fsn1"
    }
  }
}

Data source reference: https://registry.terraform.io/providers/hetznercloud/hcloud/latest/docs/data-sources/image

Available selectors:

  • os_flavor=ubuntu / rocky / debian / fedora
  • os_version=24.04 / 9 / 12

Architectures:

  • x86 - Standard Intel/AMD (cx, ccx series)
  • arm - ARM64 (cax series)

Best Practices

  1. Use private networks for inter-server communication.
  2. Enable backups for production servers.
  3. Apply firewalls to restrict access.
  4. Use placement groups for high availability.
  5. Set labels for organization and management.
  6. Use common_* variables to reduce duplication.
  7. Generate SSH keys via the ssh-key module.

Importing Existing Servers

Servers created outside Terraform (e.g. via the Hetzner Cloud Console) can be imported into this module. One upstream provider limitation matters here: terraform import of hcloud_server does not populate the inline network and public_net blocks in state (hetznercloud/terraform-provider-hcloud#944). If an imported server declares these blocks, the first plan shows phantom network / public_net changes, and applying a public_net change power-cycles the server.

The clean pattern is to omit networks and public_net for imported servers and manage attachments outside the server resource. This is a per-server decision, so imported and newly created servers can coexist in one module call.

module "servers" {
  source  = "danylomikula/server/hcloud"
  version = "~> 3.0"

  servers = {
    # Imported server: networks and public_net are deliberately omitted,
    # so the module emits no inline attachment blocks and the provider
    # leaves existing attachments untouched.
    legacy-web = {
      server_type = "cx23"
      location    = "nbg1"
      image       = "ubuntu-24.04"
    }
  }
}

# Import the server by its ID.
import {
  to = module.servers.hcloud_server.this["legacy-web"]
  id = "4711"
}

# Manage the private network attachment via a separate resource.
resource "hcloud_server_network" "legacy_web" {
  server_id  = module.servers.server_ids["legacy-web"]
  network_id = 98765 # or module.network.network_id from terraform-hcloud-network
  ip         = "10.0.1.10"
}

# Import the existing attachment as "<server_id>-<network_id>".
import {
  to = hcloud_server_network.legacy_web
  id = "4711-98765"
}

Primary IPs need no extra configuration: with public_net omitted they stay attached and unmanaged. To bring them under Terraform as well, import hcloud_primary_ip resources by ID and bind them with assignee_type = "server" / assignee_id.

Import checklist:

  1. Use the real server name as the key in servers — the module sets name from the key, so a different key renames the server.
  2. Match server_type, location and image to the live server (check with hcloud server describe <name>); a mismatched image forces replacement. Verify the first plan shows no destroy/replace actions.
  3. Mirror other live settings that differ from module defaults (e.g. backups = true).

Trade-offs: without inline blocks the private_network_ips and private_network_mac_addresses outputs are empty for imported servers, and ipv4_enabled / ipv6_enabled cannot be toggled through the module. Switching an imported server to inline networks / public_net later re-manages attachments through the server resource — plan carefully, as public_net changes power-cycle the server.

Lifecycle Considerations

The module uses ignore_changes for:

  • ssh_keys: Prevents recreation when keys are added manually.
  • user_data: Prevents recreation when cloud-init data changes.

Requirements

Name Version
terraform >= 1.14.0
hcloud >= 1.66.0

Providers

Name Version
hcloud >= 1.66.0

Modules

No modules.

Resources

Name Type
hcloud_server.this resource

Inputs

Name Description Type Default Required
common_firewall_ids List of firewall IDs to apply to all servers in addition to per-server firewalls. list(number) [] no
common_labels Labels to apply to all servers in addition to per-server labels. map(string) {} no
common_ssh_keys List of SSH key IDs to add to all servers in addition to per-server keys. list(number) [] no
servers Map of server configurations keyed by friendly name.
map(object({
server_type = string
location = optional(string)
image = string
ssh_keys = optional(list(number), [])
keep_disk = optional(bool, false)
iso = optional(number)
rescue = optional(string)
backups = optional(bool, false)
firewall_ids = optional(list(number), [])
placement_group_id = optional(number)
user_data = optional(string)
labels = optional(map(string), {})
shutdown_before_deletion = optional(bool, false)
ignore_remote_firewall_ids = optional(bool, false)
rebuild_protection = optional(bool, false)
delete_protection = optional(bool, false)

networks = optional(list(object({
network_id = optional(number)
subnet_id = optional(string)
ip = optional(string)
alias_ips = optional(list(string), [])
})), [])

public_net = optional(object({
ipv4_enabled = optional(bool, true)
ipv6_enabled = optional(bool, true)
ipv4 = optional(number)
ipv6 = optional(number)
}))
}))
{} no

Outputs

Name Description
private_network_ips Map of server names to their private network IP addresses.
private_network_mac_addresses Map of server names to their private network interface MAC addresses.
server_ids Map of server names to their IDs.
server_ipv4_addresses Map of server names to their IPv4 addresses.
server_ipv6_addresses Map of server names to their IPv6 addresses.
servers Map of all server resources with complete attributes.
servers_by_location Map of locations to lists of server IDs in each location.
servers_by_type Map of server types to lists of server IDs of each type.

Related Modules

Module Description GitHub Terraform Registry
terraform-hcloud-network Manage Hetzner Cloud networks and subnets GitHub Registry
terraform-hcloud-firewall Manage Hetzner Cloud firewalls GitHub Registry
terraform-hcloud-ssh-key Manage Hetzner Cloud SSH keys GitHub Registry

Authors

Module managed by Danylo Mikula.

Contributing

Contributions are welcome! Please read the Contributing Guide for details on the process and commit conventions.

License

Apache 2.0 Licensed. See LICENSE for full details.

About

Terraform module for managing Hetzner Cloud servers.

Topics

Resources

License

Contributing

Stars

1 star

Watchers

0 watching

Forks

Sponsor this project

Contributors

Languages