Copy and paste the following into main.tf
and run coder template push
:
1terraform {
2 required_providers {
3 coder = {
4 source = "coder/coder"
5 }
6 incus = {
7 source = "lxc/incus"
8 }
9 }
10}
11
12data "coder_provisioner" "me" {}
13
14provider "incus" {}
15
16data "coder_workspace" "me" {}
17data "coder_workspace_owner" "me" {}
18
19data "coder_parameter" "image" {
20 name = "image"
21 display_name = "Image"
22 description = "The container image to use. Be sure to use a variant with cloud-init installed!"
23 default = "ubuntu/jammy/cloud/amd64"
24 icon = "/icon/image.svg"
25 mutable = true
26}
27
28data "coder_parameter" "cpu" {
29 name = "cpu"
30 display_name = "CPU"
31 description = "The number of CPUs to allocate to the workspace (1-8)"
32 type = "number"
33 default = "1"
34 icon = "https://raw.githubusercontent.com/matifali/logos/main/cpu-3.svg"
35 mutable = true
36 validation {
37 min = 1
38 max = 8
39 }
40}
41
42data "coder_parameter" "memory" {
43 name = "memory"
44 display_name = "Memory"
45 description = "The amount of memory to allocate to the workspace in GB (up to 16GB)"
46 type = "number"
47 default = "2"
48 icon = "/icon/memory.svg"
49 mutable = true
50 validation {
51 min = 1
52 max = 16
53 }
54}
55
56data "coder_parameter" "git_repo" {
57 type = "string"
58 name = "Git repository"
59 default = "https://github.com/coder/coder"
60 description = "Clone a git repo into [base directory]"
61 mutable = true
62}
63
64data "coder_parameter" "repo_base_dir" {
65 type = "string"
66 name = "Repository Base Directory"
67 default = "~"
68 description = "The directory specified will be created (if missing) and the specified repo will be cloned into [base directory]/{repo}🪄."
69 mutable = true
70}
71
72resource "coder_agent" "main" {
73 count = data.coder_workspace.me.start_count
74 arch = data.coder_provisioner.me.arch
75 os = "linux"
76 dir = "/home/${local.workspace_user}"
77 env = {
78 CODER_WORKSPACE_ID = data.coder_workspace.me.id
79 }
80
81 metadata {
82 display_name = "CPU Usage"
83 key = "0_cpu_usage"
84 script = "coder stat cpu"
85 interval = 10
86 timeout = 1
87 }
88
89 metadata {
90 display_name = "RAM Usage"
91 key = "1_ram_usage"
92 script = "coder stat mem"
93 interval = 10
94 timeout = 1
95 }
96
97 metadata {
98 display_name = "Home Disk"
99 key = "3_home_disk"
100 script = "coder stat disk --path /home/${lower(data.coder_workspace_owner.me.name)}"
101 interval = 60
102 timeout = 1
103 }
104}
105
106module "git-clone" {
107 source = "registry.coder.com/modules/git-clone/coder"
108 version = "1.0.2"
109 agent_id = local.agent_id
110 url = data.coder_parameter.git_repo.value
111 base_dir = local.repo_base_dir
112}
113
114module "code-server" {
115 source = "registry.coder.com/modules/code-server/coder"
116 version = "1.0.2"
117 agent_id = local.agent_id
118 folder = local.repo_base_dir
119}
120
121module "filebrowser" {
122 source = "registry.coder.com/modules/filebrowser/coder"
123 version = "1.0.2"
124 agent_id = local.agent_id
125}
126
127module "coder-login" {
128 source = "registry.coder.com/modules/coder-login/coder"
129 version = "1.0.2"
130 agent_id = local.agent_id
131}
132
133resource "incus_volume" "home" {
134 name = "coder-${data.coder_workspace.me.id}-home"
135 pool = local.pool
136}
137
138resource "incus_volume" "docker" {
139 name = "coder-${data.coder_workspace.me.id}-docker"
140 pool = local.pool
141}
142
143resource "incus_cached_image" "image" {
144 source_remote = "images"
145 source_image = data.coder_parameter.image.value
146}
147
148resource "incus_instance_file" "agent_token" {
149 count = data.coder_workspace.me.start_count
150 instance = incus_instance.dev.name
151 content = <<EOF
152CODER_AGENT_TOKEN=${local.agent_token}
153EOF
154 create_directories = true
155 target_path = "/opt/coder/init.env"
156}
157
158resource "incus_instance" "dev" {
159 running = data.coder_workspace.me.start_count == 1
160 name = "coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}"
161 image = incus_cached_image.image.fingerprint
162
163 config = {
164 "security.nesting" = true
165 "security.syscalls.intercept.mknod" = true
166 "security.syscalls.intercept.setxattr" = true
167 "boot.autostart" = true
168 "cloud-init.user-data" = <<EOF
169#cloud-config
170hostname: ${lower(data.coder_workspace.me.name)}
171users:
172 - name: ${local.workspace_user}
173 uid: 1000
174 gid: 1000
175 groups: sudo
176 packages:
177 - curl
178 shell: /bin/bash
179 sudo: ['ALL=(ALL) NOPASSWD:ALL']
180write_files:
181 - path: /opt/coder/init
182 permissions: "0755"
183 encoding: b64
184 content: ${base64encode(local.agent_init_script)}
185 - path: /etc/systemd/system/coder-agent.service
186 permissions: "0644"
187 content: |
188 [Unit]
189 Description=Coder Agent
190 After=network-online.target
191 Wants=network-online.target
192
193 [Service]
194 User=${local.workspace_user}
195 EnvironmentFile=/opt/coder/init.env
196 ExecStart=/opt/coder/init
197 Restart=always
198 RestartSec=10
199 TimeoutStopSec=90
200 KillMode=process
201
202 OOMScoreAdjust=-900
203 SyslogIdentifier=coder-agent
204
205 [Install]
206 WantedBy=multi-user.target
207 - path: /etc/systemd/system/coder-agent-watcher.service
208 permissions: "0644"
209 content: |
210 [Unit]
211 Description=Coder Agent Watcher
212 After=network-online.target
213
214 [Service]
215 Type=oneshot
216 ExecStart=/usr/bin/systemctl restart coder-agent.service
217
218 [Install]
219 WantedBy=multi-user.target
220 - path: /etc/systemd/system/coder-agent-watcher.path
221 permissions: "0644"
222 content: |
223 [Path]
224 PathModified=/opt/coder/init.env
225 Unit=coder-agent-watcher.service
226
227 [Install]
228 WantedBy=multi-user.target
229runcmd:
230 - chown -R ${local.workspace_user}:${local.workspace_user} /home/${local.workspace_user}
231 - |
232 #!/bin/bash
233 apt-get update && apt-get install -y curl docker.io
234 usermod -aG docker ${local.workspace_user}
235 newgrp docker
236 - systemctl enable coder-agent.service coder-agent-watcher.service coder-agent-watcher.path
237 - systemctl start coder-agent.service coder-agent-watcher.service coder-agent-watcher.path
238EOF
239 }
240
241 limits = {
242 cpu = data.coder_parameter.cpu.value
243 memory = "${data.coder_parameter.cpu.value}GiB"
244 }
245
246 device {
247 name = "home"
248 type = "disk"
249 properties = {
250 path = "/home/${local.workspace_user}"
251 pool = local.pool
252 source = incus_volume.home.name
253 }
254 }
255
256 device {
257 name = "docker"
258 type = "disk"
259 properties = {
260 path = "/var/lib/docker"
261 pool = local.pool
262 source = incus_volume.docker.name
263 }
264 }
265
266 device {
267 name = "root"
268 type = "disk"
269 properties = {
270 path = "/"
271 pool = local.pool
272 }
273 }
274}
275
276locals {
277 workspace_user = lower(data.coder_workspace_owner.me.name)
278 pool = "coder"
279 repo_base_dir = data.coder_parameter.repo_base_dir.value == "~" ? "/home/${local.workspace_user}" : replace(data.coder_parameter.repo_base_dir.value, "/^~\\//", "/home/${local.workspace_user}/")
280 repo_dir = module.git-clone.repo_dir
281 agent_id = data.coder_workspace.me.start_count == 1 ? coder_agent.main[0].id : ""
282 agent_token = data.coder_workspace.me.start_count == 1 ? coder_agent.main[0].token : ""
283 agent_init_script = data.coder_workspace.me.start_count == 1 ? coder_agent.main[0].init_script : ""
284}
285
286resource "coder_metadata" "info" {
287 count = data.coder_workspace.me.start_count
288 resource_id = incus_instance.dev.name
289 item {
290 key = "memory"
291 value = incus_instance.dev.limits.memory
292 }
293 item {
294 key = "cpus"
295 value = incus_instance.dev.limits.cpu
296 }
297 item {
298 key = "instance"
299 value = incus_instance.dev.name
300 }
301 item {
302 key = "image"
303 value = "${incus_cached_image.image.source_remote}:${incus_cached_image.image.source_image}"
304 }
305 item {
306 key = "image_fingerprint"
307 value = substr(incus_cached_image.image.fingerprint, 0, 12)
308 }
309}
310
311