TemplatesModules
Back to Templates
Devcontainers (Kubernetes) Icon

Devcontainers (Kubernetes)

By:
Provision envbuilder pods as Coder workspaces
Source
README
Resources (2)
Variables (4)

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    kubernetes = {
8      source = "hashicorp/kubernetes"
9    }
10  }
11}
12
13provider "coder" {}
14provider "kubernetes" {
15  # Authenticate via ~/.kube/config or a Coder-specific ServiceAccount, depending on admin preferences
16  config_path = var.use_kubeconfig == true ? "~/.kube/config" : null
17}
18
19data "coder_provisioner" "me" {}
20data "coder_workspace" "me" {}
21data "coder_workspace_owner" "me" {}
22
23variable "use_kubeconfig" {
24  type        = bool
25  description = <<-EOF
26  Use host kubeconfig? (true/false)
27
28  Set this to false if the Coder host is itself running as a Pod on the same
29  Kubernetes cluster as you are deploying workspaces to.
30
31  Set this to true if the Coder host is running outside the Kubernetes cluster
32  for workspaces.  A valid "~/.kube/config" must be present on the Coder host.
33  EOF
34  default     = false
35}
36
37variable "namespace" {
38  type        = string
39  default     = "default"
40  description = "The Kubernetes namespace to create workspaces in (must exist prior to creating workspaces). If the Coder host is itself running as a Pod on the same Kubernetes cluster as you are deploying workspaces to, set this to the same namespace."
41}
42
43variable "cache_repo" {
44  default     = ""
45  description = "Use a container registry as a cache to speed up builds."
46  sensitive   = true
47  type        = string
48}
49
50data "coder_parameter" "cpu" {
51  type         = "number"
52  name         = "cpu"
53  display_name = "CPU"
54  description  = "CPU limit (cores)."
55  default      = "2"
56  icon         = "/emojis/1f5a5.png"
57  mutable      = true
58  validation {
59    min = 1
60    max = 99999
61  }
62  order = 1
63}
64
65data "coder_parameter" "memory" {
66  type         = "number"
67  name         = "memory"
68  display_name = "Memory"
69  description  = "Memory limit (GiB)."
70  default      = "2"
71  icon         = "/icon/memory.svg"
72  mutable      = true
73  validation {
74    min = 1
75    max = 99999
76  }
77  order = 2
78}
79
80data "coder_parameter" "workspaces_volume_size" {
81  name         = "workspaces_volume_size"
82  display_name = "Workspaces volume size"
83  description  = "Size of the `/workspaces` volume (GiB)."
84  default      = "10"
85  type         = "number"
86  icon         = "/emojis/1f4be.png"
87  mutable      = false
88  validation {
89    min = 1
90    max = 99999
91  }
92  order = 3
93}
94
95data "coder_parameter" "repo" {
96  description  = "Select a repository to automatically clone and start working with a devcontainer."
97  display_name = "Repository (auto)"
98  mutable      = true
99  name         = "repo"
100  order        = 4
101  type         = "string"
102}
103
104data "coder_parameter" "fallback_image" {
105  default      = "codercom/enterprise-base:ubuntu"
106  description  = "This image runs if the devcontainer fails to build."
107  display_name = "Fallback Image"
108  mutable      = true
109  name         = "fallback_image"
110  order        = 6
111}
112
113data "coder_parameter" "devcontainer_builder" {
114  description  = <<-EOF
115Image that will build the devcontainer.
116We highly recommend using a specific release as the `:latest` tag will change.
117Find the latest version of Envbuilder here: https://github.com/coder/envbuilder/pkgs/container/envbuilder
118EOF
119  display_name = "Devcontainer Builder"
120  mutable      = true
121  name         = "devcontainer_builder"
122  default      = "ghcr.io/coder/envbuilder:latest"
123  order        = 7
124}
125
126variable "cache_repo_secret_name" {
127  default     = ""
128  description = "Path to a docker config.json containing credentials to the provided cache repo, if required."
129  sensitive   = true
130  type        = string
131}
132
133data "kubernetes_secret" "cache_repo_dockerconfig_secret" {
134  count = var.cache_repo_secret_name == "" ? 0 : 1
135  metadata {
136    name      = var.cache_repo_secret_name
137    namespace = var.namespace
138  }
139}
140
141locals {
142  deployment_name            = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
143  devcontainer_builder_image = data.coder_parameter.devcontainer_builder.value
144  git_author_name            = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
145  git_author_email           = data.coder_workspace_owner.me.email
146  repo_url                   = data.coder_parameter.repo.value
147}
148
149resource "kubernetes_persistent_volume_claim" "home" {
150  metadata {
151    name      = "coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}-home"
152    namespace = var.namespace
153    labels = {
154      "app.kubernetes.io/name"     = "coder-pvc"
155      "app.kubernetes.io/instance" = "coder-pvc-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}"
156      "app.kubernetes.io/part-of"  = "coder"
157      //Coder-specific labels.
158      "com.coder.resource"       = "true"
159      "com.coder.workspace.id"   = data.coder_workspace.me.id
160      "com.coder.workspace.name" = data.coder_workspace.me.name
161      "com.coder.user.id"        = data.coder_workspace_owner.me.id
162      "com.coder.user.username"  = data.coder_workspace_owner.me.name
163    }
164    annotations = {
165      "com.coder.user.email" = data.coder_workspace_owner.me.email
166    }
167  }
168  wait_until_bound = false
169  spec {
170    access_modes = ["ReadWriteOnce"]
171    resources {
172      requests = {
173        storage = "${data.coder_parameter.workspaces_volume_size.value}Gi"
174      }
175    }
176  }
177}
178
179resource "kubernetes_deployment" "main" {
180  count = data.coder_workspace.me.start_count
181  depends_on = [
182    kubernetes_persistent_volume_claim.home
183  ]
184  wait_for_rollout = false
185  metadata {
186    name      = local.deployment_name
187    namespace = var.namespace
188    labels = {
189      "app.kubernetes.io/name"     = "coder-workspace"
190      "app.kubernetes.io/instance" = local.deployment_name
191      "app.kubernetes.io/part-of"  = "coder"
192      "com.coder.resource"         = "true"
193      "com.coder.workspace.id"     = data.coder_workspace.me.id
194      "com.coder.workspace.name"   = data.coder_workspace.me.name
195      "com.coder.user.id"          = data.coder_workspace_owner.me.id
196      "com.coder.user.username"    = data.coder_workspace_owner.me.name
197    }
198    annotations = {
199      "com.coder.user.email" = data.coder_workspace_owner.me.email
200    }
201  }
202
203  spec {
204    replicas = 1
205    selector {
206      match_labels = {
207        "app.kubernetes.io/name" = "coder-workspace"
208      }
209    }
210    strategy {
211      type = "Recreate"
212    }
213
214    template {
215      metadata {
216        labels = {
217          "app.kubernetes.io/name" = "coder-workspace"
218        }
219      }
220      spec {
221        security_context {}
222
223        container {
224          name              = "dev"
225          image             = local.devcontainer_builder_image
226          image_pull_policy = "Always"
227          security_context {}
228          env {
229            name  = "CODER_AGENT_TOKEN"
230            value = coder_agent.main.token
231          }
232          env {
233            name  = "CODER_AGENT_URL"
234            value = data.coder_workspace.me.access_url
235          }
236          env {
237            name  = "ENVBUILDER_GIT_URL"
238            value = local.repo_url
239          }
240          env {
241            name  = "ENVBUILDER_INIT_SCRIPT"
242            value = coder_agent.main.init_script
243          }
244          env {
245            name  = "ENVBUILDER_FALLBACK_IMAGE"
246            value = data.coder_parameter.fallback_image.value
247          }
248          env {
249            name  = "ENVBUILDER_CACHE_REPO"
250            value = var.cache_repo
251          }
252          env {
253            name  = "ENVBUILDER_DOCKER_CONFIG_BASE64"
254            value = try(data.kubernetes_secret.cache_repo_dockerconfig_secret[0].data[".dockerconfigjson"], "")
255          }
256          # You may need to adjust this if you get an error regarding deleting files when building the workspace.
257          # For example, when testing in KinD, it was necessary to set `/product_name` and `/product_uuid` in
258          # addition to `/var/run`.
259          #           env {
260          #             name = "ENVBUILDER_IGNORE_PATHS"
261          #             value = "/product_name,/product_uuid,/var/run"
262          #           }
263          resources {
264            requests = {
265              "cpu"    = "250m"
266              "memory" = "512Mi"
267            }
268            limits = {
269              "cpu"    = "${data.coder_parameter.cpu.value}"
270              "memory" = "${data.coder_parameter.memory.value}Gi"
271            }
272          }
273          volume_mount {
274            mount_path = "/home/coder"
275            name       = "home"
276            read_only  = false
277          }
278        }
279
280        volume {
281          name = "home"
282          persistent_volume_claim {
283            claim_name = kubernetes_persistent_volume_claim.home.metadata.0.name
284            read_only  = false
285          }
286        }
287
288        affinity {
289          // This affinity attempts to spread out all workspace pods evenly across
290          // nodes.
291          pod_anti_affinity {
292            preferred_during_scheduling_ignored_during_execution {
293              weight = 1
294              pod_affinity_term {
295                topology_key = "kubernetes.io/hostname"
296                label_selector {
297                  match_expressions {
298                    key      = "app.kubernetes.io/name"
299                    operator = "In"
300                    values   = ["coder-workspace"]
301                  }
302                }
303              }
304            }
305          }
306        }
307      }
308    }
309  }
310}
311
312resource "coder_agent" "main" {
313  arch           = data.coder_provisioner.me.arch
314  os             = "linux"
315  startup_script = <<-EOT
316    set -e
317
318    # install and start code-server
319    curl -fsSL https://code-server.dev/install.sh | sh -s -- --method=standalone --prefix=/tmp/code-server --version 4.11.0
320    /tmp/code-server/bin/code-server --auth none --port 13337 >/tmp/code-server.log 2>&1 &
321  EOT
322  dir            = "/workspaces"
323
324  # These environment variables allow you to make Git commits right away after creating a
325  # workspace. Note that they take precedence over configuration defined in ~/.gitconfig!
326  # You can remove this block if you'd prefer to configure Git manually or using
327  # dotfiles. (see docs/dotfiles.md)
328  env = {
329    GIT_AUTHOR_NAME     = local.git_author_name
330    GIT_AUTHOR_EMAIL    = local.git_author_email
331    GIT_COMMITTER_NAME  = local.git_author_name
332    GIT_COMMITTER_EMAIL = local.git_author_email
333  }
334
335  # The following metadata blocks are optional. They are used to display
336  # information about your workspace in the dashboard. You can remove them
337  # if you don't want to display any information.
338  # For basic resources, you can use the `coder stat` command.
339  # If you need more control, you can write your own script.
340  metadata {
341    display_name = "CPU Usage"
342    key          = "0_cpu_usage"
343    script       = "coder stat cpu"
344    interval     = 10
345    timeout      = 1
346  }
347
348  metadata {
349    display_name = "RAM Usage"
350    key          = "1_ram_usage"
351    script       = "coder stat mem"
352    interval     = 10
353    timeout      = 1
354  }
355
356  metadata {
357    display_name = "Home Disk"
358    key          = "3_home_disk"
359    script       = "coder stat disk --path $HOME"
360    interval     = 60
361    timeout      = 1
362  }
363
364  metadata {
365    display_name = "CPU Usage (Host)"
366    key          = "4_cpu_usage_host"
367    script       = "coder stat cpu --host"
368    interval     = 10
369    timeout      = 1
370  }
371
372  metadata {
373    display_name = "Memory Usage (Host)"
374    key          = "5_mem_usage_host"
375    script       = "coder stat mem --host"
376    interval     = 10
377    timeout      = 1
378  }
379
380  metadata {
381    display_name = "Load Average (Host)"
382    key          = "6_load_host"
383    # get load avg scaled by number of cores
384    script   = <<EOT
385      echo "`cat /proc/loadavg | awk '{ print $1 }'` `nproc`" | awk '{ printf "%0.2f", $1/$2 }'
386    EOT
387    interval = 60
388    timeout  = 1
389  }
390
391  metadata {
392    display_name = "Swap Usage (Host)"
393    key          = "7_swap_host"
394    script       = <<EOT
395      free -b | awk '/^Swap/ { printf("%.1f/%.1f", $3/1024.0/1024.0/1024.0, $2/1024.0/1024.0/1024.0) }'
396    EOT
397    interval     = 10
398    timeout      = 1
399  }
400}
401
402resource "coder_app" "code-server" {
403  agent_id     = coder_agent.main.id
404  slug         = "code-server"
405  display_name = "code-server"
406  url          = "http://localhost:13337/?folder=/workspaces"
407  icon         = "/icon/code.svg"
408  subdomain    = false
409  share        = "owner"
410
411  healthcheck {
412    url       = "http://localhost:13337/healthz"
413    interval  = 5
414    threshold = 6
415  }
416}
417