Copy and paste the following into main.tf
and run coder template push
:
1terraform {
2 required_providers {
3 coder = {
4 source = "coder/coder"
5 version = "~> 1.0.0"
6 }
7 docker = {
8 source = "kreuzwerker/docker"
9 }
10 envbuilder = {
11 source = "coder/envbuilder"
12 }
13 }
14}
15
16variable "docker_socket" {
17 default = ""
18 description = "(Optional) Docker socket URI"
19 type = string
20}
21
22provider "coder" {}
23provider "docker" {
24 # Defaulting to null if the variable is an empty string lets us have an optional variable without having to set our own default
25 host = var.docker_socket != "" ? var.docker_socket : null
26}
27provider "envbuilder" {}
28
29data "coder_provisioner" "me" {}
30data "coder_workspace" "me" {}
31data "coder_workspace_owner" "me" {}
32
33data "coder_parameter" "repo" {
34 description = "Select a repository to automatically clone and start working with a devcontainer."
35 display_name = "Repository (auto)"
36 mutable = true
37 name = "repo"
38 option {
39 name = "vercel/next.js"
40 description = "The React Framework"
41 value = "https://github.com/vercel/next.js"
42 }
43 option {
44 name = "home-assistant/core"
45 description = "🏡 Open source home automation that puts local control and privacy first."
46 value = "https://github.com/home-assistant/core"
47 }
48 option {
49 name = "discourse/discourse"
50 description = "A platform for community discussion. Free, open, simple."
51 value = "https://github.com/discourse/discourse"
52 }
53 option {
54 name = "denoland/deno"
55 description = "A modern runtime for JavaScript and TypeScript."
56 value = "https://github.com/denoland/deno"
57 }
58 option {
59 name = "microsoft/vscode"
60 icon = "/icon/code.svg"
61 description = "Code editing. Redefined."
62 value = "https://github.com/microsoft/vscode"
63 }
64 option {
65 name = "Custom"
66 icon = "/emojis/1f5c3.png"
67 description = "Specify a custom repo URL below"
68 value = "custom"
69 }
70 order = 1
71}
72
73data "coder_parameter" "custom_repo_url" {
74 default = ""
75 description = "Optionally enter a custom repository URL, see [awesome-devcontainers](https://github.com/manekinekko/awesome-devcontainers)."
76 display_name = "Repository URL (custom)"
77 name = "custom_repo_url"
78 mutable = true
79 order = 2
80}
81
82data "coder_parameter" "fallback_image" {
83 default = "codercom/enterprise-base:ubuntu"
84 description = "This image runs if the devcontainer fails to build."
85 display_name = "Fallback Image"
86 mutable = true
87 name = "fallback_image"
88 order = 3
89}
90
91data "coder_parameter" "devcontainer_builder" {
92 description = <<-EOF
93Image that will build the devcontainer.
94We highly recommend using a specific release as the `:latest` tag will change.
95Find the latest version of Envbuilder here: https://github.com/coder/envbuilder/pkgs/container/envbuilder
96EOF
97 display_name = "Devcontainer Builder"
98 mutable = true
99 name = "devcontainer_builder"
100 default = "ghcr.io/coder/envbuilder:latest"
101 order = 4
102}
103
104variable "cache_repo" {
105 default = ""
106 description = "(Optional) Use a container registry as a cache to speed up builds."
107 type = string
108}
109
110variable "insecure_cache_repo" {
111 default = false
112 description = "Enable this option if your cache registry does not serve HTTPS."
113 type = bool
114}
115
116variable "cache_repo_docker_config_path" {
117 default = ""
118 description = "(Optional) Path to a docker config.json containing credentials to the provided cache repo, if required."
119 sensitive = true
120 type = string
121}
122
123locals {
124 container_name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
125 devcontainer_builder_image = data.coder_parameter.devcontainer_builder.value
126 git_author_name = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
127 git_author_email = data.coder_workspace_owner.me.email
128 repo_url = data.coder_parameter.repo.value == "custom" ? data.coder_parameter.custom_repo_url.value : data.coder_parameter.repo.value
129 # The envbuilder provider requires a key-value map of environment variables.
130 envbuilder_env = {
131 # ENVBUILDER_GIT_URL and ENVBUILDER_CACHE_REPO will be overridden by the provider
132 # if the cache repo is enabled.
133 "ENVBUILDER_GIT_URL" : local.repo_url,
134 "ENVBUILDER_CACHE_REPO" : var.cache_repo,
135 "CODER_AGENT_TOKEN" : coder_agent.main.token,
136 # Use the docker gateway if the access URL is 127.0.0.1
137 "CODER_AGENT_URL" : replace(data.coder_workspace.me.access_url, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal"),
138 # Use the docker gateway if the access URL is 127.0.0.1
139 "ENVBUILDER_INIT_SCRIPT" : replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal"),
140 "ENVBUILDER_FALLBACK_IMAGE" : data.coder_parameter.fallback_image.value,
141 "ENVBUILDER_DOCKER_CONFIG_BASE64" : try(data.local_sensitive_file.cache_repo_dockerconfigjson[0].content_base64, ""),
142 "ENVBUILDER_PUSH_IMAGE" : var.cache_repo == "" ? "" : "true",
143 "ENVBUILDER_INSECURE" : "${var.insecure_cache_repo}",
144 }
145 # Convert the above map to the format expected by the docker provider.
146 docker_env = [
147 for k, v in local.envbuilder_env : "${k}=${v}"
148 ]
149}
150
151data "local_sensitive_file" "cache_repo_dockerconfigjson" {
152 count = var.cache_repo_docker_config_path == "" ? 0 : 1
153 filename = var.cache_repo_docker_config_path
154}
155
156resource "docker_image" "devcontainer_builder_image" {
157 name = local.devcontainer_builder_image
158}
159
160resource "docker_volume" "workspaces" {
161 name = "coder-${data.coder_workspace.me.id}"
162 # Protect the volume from being deleted due to changes in attributes.
163 lifecycle {
164 ignore_changes = all
165 }
166 # Add labels in Docker to keep track of orphan resources.
167 labels {
168 label = "coder.owner"
169 value = data.coder_workspace_owner.me.name
170 }
171 labels {
172 label = "coder.owner_id"
173 value = data.coder_workspace_owner.me.id
174 }
175 labels {
176 label = "coder.workspace_id"
177 value = data.coder_workspace.me.id
178 }
179 # This field becomes outdated if the workspace is renamed but can
180 # be useful for debugging or cleaning out dangling volumes.
181 labels {
182 label = "coder.workspace_name_at_creation"
183 value = data.coder_workspace.me.name
184 }
185}
186
187# Check for the presence of a prebuilt image in the cache repo
188# that we can use instead.
189resource "envbuilder_cached_image" "cached" {
190 count = var.cache_repo == "" ? 0 : data.coder_workspace.me.start_count
191 builder_image = local.devcontainer_builder_image
192 git_url = local.repo_url
193 cache_repo = var.cache_repo
194 extra_env = local.envbuilder_env
195 insecure = var.insecure_cache_repo
196}
197
198resource "docker_container" "workspace" {
199 count = data.coder_workspace.me.start_count
200 image = var.cache_repo == "" ? local.devcontainer_builder_image : envbuilder_cached_image.cached.0.image
201 # Uses lower() to avoid Docker restriction on container names.
202 name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
203 # Hostname makes the shell more user friendly: coder@my-workspace:~$
204 hostname = data.coder_workspace.me.name
205 # Use the environment specified by the envbuilder provider, if available.
206 env = var.cache_repo == "" ? local.docker_env : envbuilder_cached_image.cached.0.env
207 # network_mode = "host" # Uncomment if testing with a registry running on `localhost`.
208 host {
209 host = "host.docker.internal"
210 ip = "host-gateway"
211 }
212 volumes {
213 container_path = "/workspaces"
214 volume_name = docker_volume.workspaces.name
215 read_only = false
216 }
217 # Add labels in Docker to keep track of orphan resources.
218 labels {
219 label = "coder.owner"
220 value = data.coder_workspace_owner.me.name
221 }
222 labels {
223 label = "coder.owner_id"
224 value = data.coder_workspace_owner.me.id
225 }
226 labels {
227 label = "coder.workspace_id"
228 value = data.coder_workspace.me.id
229 }
230 labels {
231 label = "coder.workspace_name"
232 value = data.coder_workspace.me.name
233 }
234}
235
236resource "coder_agent" "main" {
237 arch = data.coder_provisioner.me.arch
238 os = "linux"
239 startup_script = <<-EOT
240 set -e
241
242 # Install the latest code-server.
243 # Append "--version x.x.x" to install a specific version of code-server.
244 curl -fsSL https://code-server.dev/install.sh | sh -s -- --method=standalone --prefix=/tmp/code-server
245
246 # Start code-server in the background.
247 /tmp/code-server/bin/code-server --auth none --port 13337 >/tmp/code-server.log 2>&1 &
248 EOT
249 dir = "/workspaces"
250
251 # These environment variables allow you to make Git commits right away after creating a
252 # workspace. Note that they take precedence over configuration defined in ~/.gitconfig!
253 # You can remove this block if you'd prefer to configure Git manually or using
254 # dotfiles. (see docs/dotfiles.md)
255 env = {
256 GIT_AUTHOR_NAME = local.git_author_name
257 GIT_AUTHOR_EMAIL = local.git_author_email
258 GIT_COMMITTER_NAME = local.git_author_name
259 GIT_COMMITTER_EMAIL = local.git_author_email
260 }
261
262 # The following metadata blocks are optional. They are used to display
263 # information about your workspace in the dashboard. You can remove them
264 # if you don't want to display any information.
265 # For basic resources, you can use the `coder stat` command.
266 # If you need more control, you can write your own script.
267 metadata {
268 display_name = "CPU Usage"
269 key = "0_cpu_usage"
270 script = "coder stat cpu"
271 interval = 10
272 timeout = 1
273 }
274
275 metadata {
276 display_name = "RAM Usage"
277 key = "1_ram_usage"
278 script = "coder stat mem"
279 interval = 10
280 timeout = 1
281 }
282
283 metadata {
284 display_name = "Home Disk"
285 key = "3_home_disk"
286 script = "coder stat disk --path $HOME"
287 interval = 60
288 timeout = 1
289 }
290
291 metadata {
292 display_name = "CPU Usage (Host)"
293 key = "4_cpu_usage_host"
294 script = "coder stat cpu --host"
295 interval = 10
296 timeout = 1
297 }
298
299 metadata {
300 display_name = "Memory Usage (Host)"
301 key = "5_mem_usage_host"
302 script = "coder stat mem --host"
303 interval = 10
304 timeout = 1
305 }
306
307 metadata {
308 display_name = "Load Average (Host)"
309 key = "6_load_host"
310 # get load avg scaled by number of cores
311 script = <<EOT
312 echo "`cat /proc/loadavg | awk '{ print $1 }'` `nproc`" | awk '{ printf "%0.2f", $1/$2 }'
313 EOT
314 interval = 60
315 timeout = 1
316 }
317
318 metadata {
319 display_name = "Swap Usage (Host)"
320 key = "7_swap_host"
321 script = <<EOT
322 free -b | awk '/^Swap/ { printf("%.1f/%.1f", $3/1024.0/1024.0/1024.0, $2/1024.0/1024.0/1024.0) }'
323 EOT
324 interval = 10
325 timeout = 1
326 }
327}
328
329resource "coder_app" "code-server" {
330 agent_id = coder_agent.main.id
331 slug = "code-server"
332 display_name = "code-server"
333 url = "http://localhost:13337/?folder=/workspaces"
334 icon = "/icon/code.svg"
335 subdomain = false
336 share = "owner"
337
338 healthcheck {
339 url = "http://localhost:13337/healthz"
340 interval = 5
341 threshold = 6
342 }
343}
344
345resource "coder_metadata" "container_info" {
346 count = data.coder_workspace.me.start_count
347 resource_id = coder_agent.main.id
348 item {
349 key = "workspace image"
350 value = var.cache_repo == "" ? local.devcontainer_builder_image : envbuilder_cached_image.cached.0.image
351 }
352 item {
353 key = "git url"
354 value = local.repo_url
355 }
356 item {
357 key = "cache repo"
358 value = var.cache_repo == "" ? "not enabled" : var.cache_repo
359 }
360}
361