Skip to content

Commit bf9449e

Browse files
committed
feat: add Hetzner Cloud server template and configuration files
1 parent 08ed594 commit bf9449e

File tree

8 files changed

+307
-0
lines changed

8 files changed

+307
-0
lines changed

.github/typos.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
muc = "muc" # For Munich location code
33
Hashi = "Hashi"
44
HashiCorp = "HashiCorp"
5+
hel = "hel" # For Helsinki location code
56

67
[files]
78
extend-exclude = ["registry/coder/templates/aws-devcontainer/architecture.svg"] #False positive

.icons/hetzner.svg

Lines changed: 5 additions & 0 deletions
Loading

registry/brymut/.images/avatar.png

30.6 KB
Loading

registry/brymut/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
display_name: brymut (Bryan Mutai)
3+
bio: Independent software developer based in Kenya, passionate about DevOps and Cloud Native solutions. Combining expert automation and scalability with a strong commitment to the community. Explore collaboration and drive success together.
4+
avatar: ./.images/avatar.png
5+
github: brymut
6+
support_email: [email protected]
7+
status: community
8+
---
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
display_name: Hetzner Cloud Server
3+
description: Provision Hetzner Cloud servers as Coder workspaces
4+
icon: ../../../../.icons/hetzner.svg
5+
tags: [vm, linux, hetzner]
6+
---
7+
8+
# Remote Development on Hetzner Cloud (Linux)
9+
10+
Provision Hetzner Cloud servers as [Coder workspaces](https://coder.com/docs/workspaces) with this example template.
11+
12+
## Prerequisites
13+
14+
To deploy workspaces as Hetzner Cloud servers, you'll need:
15+
16+
- Hetzner Cloud [API token](https://console.hetzner.cloud/projects) (create under Security > API Tokens)
17+
18+
### Authentication
19+
20+
This template assumes that the Coder Provisioner is run in an environment that is authenticated with Hetzner Cloud.
21+
22+
Obtain a Hetzner Cloud API token from your [Hetzner Cloud Console](https://console.hetzner.cloud/projects) and provide it as the `hcloud_token` variable when creating a workspace.
23+
For more authentication options, see the [Terraform provider documentation](https://registry.terraform.io/providers/hetznercloud/hcloud/latest/docs#authentication).
24+
25+
> [!NOTE]
26+
> This template is designed to be a starting point. Edit the Terraform to extend the template to support your use case.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#cloud-config
2+
users:
3+
- name: ${username}
4+
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
5+
groups: sudo
6+
shell: /bin/bash
7+
packages:
8+
- git
9+
%{ if home_volume_label != "" ~}
10+
fs_setup:
11+
- device: /dev/disk/by-id/scsi-0HC_Volume_${volume_id}
12+
filesystem: ext4
13+
label: ${home_volume_label}
14+
overwrite: false # This prevents reformatting the disk on every boot
15+
16+
mounts:
17+
- [
18+
"LABEL=${home_volume_label}",
19+
"/home/${username}",
20+
auto,
21+
"defaults,uid=1000,gid=1000",
22+
]
23+
%{ endif ~}
24+
write_files:
25+
- path: /opt/coder/init
26+
permissions: "0755"
27+
encoding: b64
28+
content: ${init_script}
29+
- path: /etc/systemd/system/coder-agent.service
30+
permissions: "0644"
31+
content: |
32+
[Unit]
33+
Description=Coder Agent
34+
After=network-online.target
35+
Wants=network-online.target
36+
37+
[Service]
38+
User=${username}
39+
ExecStart=/opt/coder/init
40+
Environment=CODER_AGENT_TOKEN=${coder_agent_token}
41+
Restart=always
42+
RestartSec=10
43+
TimeoutStopSec=90
44+
KillMode=process
45+
46+
OOMScoreAdjust=-900
47+
SyslogIdentifier=coder-agent
48+
49+
[Install]
50+
WantedBy=multi-user.target
51+
runcmd:
52+
- mount -a
53+
- chown ${username}:${username} /home/${username}
54+
- systemctl enable coder-agent
55+
- systemctl start coder-agent
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"type_meta": {
3+
"cx22": { "cores": 2, "memory_gb": 4, "disk_gb": 40 },
4+
"cx32": { "cores": 4, "memory_gb": 8, "disk_gb": 80 },
5+
"cx42": { "cores": 8, "memory_gb": 16, "disk_gb": 160 },
6+
"cx52": { "cores": 16, "memory_gb": 32, "disk_gb": 320 },
7+
"cpx11": { "cores": 2, "memory_gb": 2, "disk_gb": 40 },
8+
"cpx21": { "cores": 3, "memory_gb": 4, "disk_gb": 80 },
9+
"cpx31": { "cores": 4, "memory_gb": 8, "disk_gb": 160 },
10+
"cpx41": { "cores": 8, "memory_gb": 16, "disk_gb": 240 },
11+
"cpx51": { "cores": 16, "memory_gb": 32, "disk_gb": 360 },
12+
"ccx13": { "cores": 2, "memory_gb": 8, "disk_gb": 80 },
13+
"ccx23": { "cores": 4, "memory_gb": 16, "disk_gb": 160 },
14+
"ccx33": { "cores": 8, "memory_gb": 32, "disk_gb": 240 },
15+
"ccx43": { "cores": 16, "memory_gb": 64, "disk_gb": 360 },
16+
"ccx53": { "cores": 32, "memory_gb": 128, "disk_gb": 600 },
17+
"ccx63": { "cores": 48, "memory_gb": 192, "disk_gb": 960 }
18+
},
19+
"availability": {
20+
"fsn1": ["cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"],
21+
"ash": ["cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"],
22+
"hel1": ["cx22", "cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"],
23+
"hil": ["cpx11", "cpx21", "cpx31", "cpx41", "ccx13", "ccx23", "ccx33"],
24+
"nbg1": ["cx22", "cx32", "cx42", "cx52", "cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"],
25+
"sin": ["cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"]
26+
}
27+
}
28+
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
terraform {
2+
required_providers {
3+
hcloud = {
4+
source = "hetznercloud/hcloud"
5+
}
6+
coder = {
7+
source = "coder/coder"
8+
}
9+
}
10+
}
11+
12+
variable "hcloud_token" {
13+
sensitive = true
14+
}
15+
16+
provider "hcloud" {
17+
token = var.hcloud_token
18+
}
19+
20+
# Available locations: https://docs.hetzner.com/cloud/general/locations/
21+
data "coder_parameter" "hcloud_location" {
22+
name = "hcloud_location"
23+
display_name = "Hetzner Location"
24+
description = "Select the Hetzner Cloud location for your workspace."
25+
type = "string"
26+
default = "fsn1"
27+
option {
28+
name = "DE Falkenstein"
29+
value = "fsn1"
30+
}
31+
option {
32+
name = "US Ashburn, VA"
33+
value = "ash"
34+
}
35+
option {
36+
name = "US Hillsboro, OR"
37+
value = "hil"
38+
}
39+
option {
40+
name = "SG Singapore"
41+
value = "sin"
42+
}
43+
option {
44+
name = "DE Nuremberg"
45+
value = "nbg1"
46+
}
47+
option {
48+
name = "FI Helsinki"
49+
value = "hel1"
50+
}
51+
}
52+
53+
# Available server types: https://docs.hetzner.com/cloud/servers/overview/
54+
data "coder_parameter" "hcloud_server_type" {
55+
name = "hcloud_server_type"
56+
display_name = "Hetzner Server Type"
57+
description = "Select the Hetzner Cloud server type for your workspace."
58+
type = "string"
59+
60+
dynamic "option" {
61+
for_each = local.hcloud_server_type_options_for_selected_location
62+
content {
63+
name = option.value.name
64+
value = option.value.value
65+
}
66+
}
67+
}
68+
69+
resource "hcloud_server" "dev" {
70+
count = data.coder_workspace.me.start_count
71+
name = "coder-${data.coder_workspace.me.name}-dev"
72+
image = "ubuntu-24.04"
73+
server_type = data.coder_parameter.hcloud_server_type.value
74+
location = data.coder_parameter.hcloud_location.value
75+
public_net {
76+
ipv4_enabled = true
77+
ipv6_enabled = true
78+
}
79+
user_data = templatefile("cloud-config.yaml.tftpl", {
80+
username = lower(data.coder_workspace_owner.me.name)
81+
home_volume_label = "coder-${data.coder_workspace.me.id}-home"
82+
volume_id = hcloud_volume.home_volume[count.index].id
83+
init_script = base64encode(coder_agent.main.init_script)
84+
coder_agent_token = coder_agent.main.token
85+
})
86+
labels = {
87+
"coder_workspace_name" = data.coder_workspace.me.name,
88+
"coder_workspace_owner" = data.coder_workspace_owner.me.name,
89+
}
90+
}
91+
92+
resource "hcloud_volume" "home_volume" {
93+
count = data.coder_workspace.me.start_count
94+
name = "coder-${data.coder_workspace.me.id}-home"
95+
size = data.coder_parameter.home_volume_size.value
96+
location = data.coder_parameter.hcloud_location.value
97+
labels = {
98+
"coder_workspace_name" = data.coder_workspace.me.name,
99+
"coder_workspace_owner" = data.coder_workspace_owner.me.name,
100+
}
101+
}
102+
103+
resource "hcloud_volume_attachment" "home_volume_attachment" {
104+
count = data.coder_workspace.me.start_count
105+
volume_id = hcloud_volume.home_volume[count.index].id
106+
server_id = hcloud_server.dev[count.index].id
107+
automount = false
108+
}
109+
110+
locals {
111+
username = lower(data.coder_workspace_owner.me.name)
112+
113+
# Data source: local JSON file under the module directory
114+
# Check API for latest server types & availability: https://docs.hetzner.cloud/reference/cloud#server-types
115+
hcloud_server_types_data = jsondecode(file("${path.module}/hetzner_server_types.json"))
116+
hcloud_server_type_meta = local.hcloud_server_types_data.type_meta
117+
hcloud_server_types_by_location = local.hcloud_server_types_data.availability
118+
119+
hcloud_server_type_options_for_selected_location = [
120+
for type_name in lookup(local.hcloud_server_types_by_location, data.coder_parameter.hcloud_location.value, []) : {
121+
name = format("%s (%d vCPU, %dGB RAM, %dGB)", upper(type_name), local.hcloud_server_type_meta[type_name].cores, local.hcloud_server_type_meta[type_name].memory_gb, local.hcloud_server_type_meta[type_name].disk_gb)
122+
value = type_name
123+
}
124+
]
125+
}
126+
127+
data "coder_provisioner" "me" {}
128+
129+
provider "coder" {}
130+
131+
data "coder_workspace" "me" {}
132+
133+
data "coder_workspace_owner" "me" {}
134+
135+
data "coder_parameter" "home_volume_size" {
136+
name = "home_volume_size"
137+
display_name = "Home volume size"
138+
description = "How large would you like your home volume to be (in GB)?"
139+
type = "number"
140+
default = "20"
141+
mutable = false
142+
validation {
143+
min = 1
144+
max = 100 # Adjust the max size as needed
145+
}
146+
}
147+
148+
resource "coder_agent" "main" {
149+
os = "linux"
150+
arch = "amd64"
151+
152+
metadata {
153+
key = "cpu"
154+
display_name = "CPU Usage"
155+
interval = 5
156+
timeout = 5
157+
script = "coder stat cpu"
158+
}
159+
metadata {
160+
key = "memory"
161+
display_name = "Memory Usage"
162+
interval = 5
163+
timeout = 5
164+
script = "coder stat mem"
165+
}
166+
metadata {
167+
key = "home"
168+
display_name = "Home Usage"
169+
interval = 600 # every 10 minutes
170+
timeout = 30 # df can take a while on large filesystems
171+
script = "coder stat disk --path /home/${local.username}"
172+
}
173+
}
174+
175+
module "code-server" {
176+
count = data.coder_workspace.me.start_count
177+
source = "registry.coder.com/coder/code-server/coder"
178+
179+
# This ensures that the latest non-breaking version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production.
180+
version = "~> 1.0"
181+
182+
agent_id = coder_agent.main.id
183+
order = 1
184+
}

0 commit comments

Comments
 (0)