TemplatesModules
Back to Templates
Devcontainers (Docker) Icon

Devcontainers (Docker)

By:
Provision envbuilder containers as Coder workspaces
Source
README
Resources (4)
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    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