Skip to content

Commit 6e9e5c4

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

File tree

8 files changed

+289
-0
lines changed

8 files changed

+289
-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: mutaiwork@gmail.com
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: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
mounts:
10+
- [
11+
"LABEL=${home_volume_label}",
12+
"/home/${username}",
13+
auto,
14+
"defaults,uid=1000,gid=1000",
15+
]
16+
write_files:
17+
- path: /opt/coder/init
18+
permissions: "0755"
19+
encoding: b64
20+
content: ${init_script}
21+
- path: /etc/systemd/system/coder-agent.service
22+
permissions: "0644"
23+
content: |
24+
[Unit]
25+
Description=Coder Agent
26+
After=network-online.target
27+
Wants=network-online.target
28+
29+
[Service]
30+
User=${username}
31+
ExecStart=/opt/coder/init
32+
Environment=CODER_AGENT_TOKEN=${coder_agent_token}
33+
Restart=always
34+
RestartSec=10
35+
TimeoutStopSec=90
36+
KillMode=process
37+
38+
OOMScoreAdjust=-900
39+
SyslogIdentifier=coder-agent
40+
41+
[Install]
42+
WantedBy=multi-user.target
43+
runcmd:
44+
- chown ${username}:${username} /home/${username}
45+
- systemctl enable coder-agent
46+
- 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: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
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+
init_script = base64encode(coder_agent.main.init_script)
83+
coder_agent_token = coder_agent.main.token
84+
})
85+
}
86+
87+
resource "hcloud_volume" "home_volume" {
88+
count = data.coder_workspace.me.start_count
89+
name = "coder-${data.coder_workspace.me.id}-home"
90+
size = data.coder_parameter.home_volume_size.value
91+
location = data.coder_parameter.hcloud_location.value
92+
format = "ext4"
93+
}
94+
95+
resource "hcloud_volume_attachment" "home_volume_attachment" {
96+
count = data.coder_workspace.me.start_count
97+
volume_id = hcloud_volume.home_volume[count.index].id
98+
server_id = hcloud_server.dev[count.index].id
99+
}
100+
101+
locals {
102+
username = lower(data.coder_workspace_owner.me.name)
103+
104+
# Data source: local JSON file under the module directory
105+
# Check API for latest server types & availability: https://docs.hetzner.cloud/reference/cloud#server-types
106+
hcloud_server_types_data = jsondecode(file("${path.module}/hetzner_server_types.json"))
107+
hcloud_server_type_meta = local.hcloud_server_types_data.type_meta
108+
hcloud_server_types_by_location = local.hcloud_server_types_data.availability
109+
110+
hcloud_server_type_options_for_selected_location = [
111+
for type_name in lookup(local.hcloud_server_types_by_location, data.coder_parameter.hcloud_location.value, []) : {
112+
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)
113+
value = type_name
114+
}
115+
]
116+
}
117+
118+
data "coder_provisioner" "me" {}
119+
120+
provider "coder" {}
121+
122+
data "coder_workspace" "me" {}
123+
124+
data "coder_workspace_owner" "me" {}
125+
126+
data "coder_parameter" "home_volume_size" {
127+
name = "home_volume_size"
128+
display_name = "Home volume size"
129+
description = "How large would you like your home volume to be (in GB)?"
130+
type = "number"
131+
default = "20"
132+
mutable = false
133+
validation {
134+
min = 1
135+
max = 100 # Adjust the max size as needed
136+
}
137+
}
138+
139+
resource "coder_agent" "main" {
140+
os = "linux"
141+
arch = "amd64"
142+
143+
metadata {
144+
key = "cpu"
145+
display_name = "CPU Usage"
146+
interval = 5
147+
timeout = 5
148+
script = "coder stat cpu"
149+
}
150+
metadata {
151+
key = "memory"
152+
display_name = "Memory Usage"
153+
interval = 5
154+
timeout = 5
155+
script = "coder stat mem"
156+
}
157+
metadata {
158+
key = "home"
159+
display_name = "Home Usage"
160+
interval = 600 # every 10 minutes
161+
timeout = 30 # df can take a while on large filesystems
162+
script = "coder stat disk --path /home/${local.username}"
163+
}
164+
}
165+
166+
module "code-server" {
167+
count = data.coder_workspace.me.start_count
168+
source = "registry.coder.com/coder/code-server/coder"
169+
170+
# 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.
171+
version = "~> 1.0"
172+
173+
agent_id = coder_agent.main.id
174+
order = 1
175+
}

0 commit comments

Comments
 (0)