TemplatesModules
Back to Templates
Devcontainers (Docker) Icon

Devcontainers (Docker)

By:
Provision envbuilder containers as Coder workspaces
Source
README
Resources (3)
Variables (2)

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