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