Back to Templates
Docker Containers

Docker Containers

By coder

Provision Docker containers as Coder workspaces

View on GitHub (link opens in new tab)

Provision Docker containers as Coder workspaces with this example template.

Prerequisites

Infrastructure

The VM you run Coder on must have a running Docker socket and the coder user must be added to the Docker group:

# Add coder user to Docker group
sudo adduser coder docker

# Restart Coder server
sudo systemctl restart coder

# Test Docker
sudo -u coder docker ps

Architecture

This template provisions the following resources:

  • Docker image (built by Docker socket and kept locally)
  • Docker container pod (ephemeral)
  • Docker volume (persistent on /home/coder)

This means, when the workspace restarts, any tools or files outside of the home directory are not persisted. To pre-bake tools into the workspace (e.g. python3), modify the container image. Alternatively, individual developers can personalize their workspaces with dotfiles.

Note This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.

Editing the image

Edit the Dockerfile and run coder templates push to update workspaces.

1terraform {
2  required_providers {
3    coder = {
4      source = "coder/coder"
5    }
6    docker = {
7      source = "kreuzwerker/docker"
8    }
9  }
10}
11
12locals {
13  username = data.coder_workspace_owner.me.name
14}
15
16variable "docker_socket" {
17  default     = ""
18  description = "(Optional) Docker socket URI"
19  type        = string
20}
21
22provider "docker" {
23  # Defaulting to null if the variable is an empty string lets us have an optional variable without having to set our own default
24  host = var.docker_socket != "" ? var.docker_socket : null
25}
26
27data "coder_provisioner" "me" {}
28data "coder_workspace" "me" {}
29data "coder_workspace_owner" "me" {}
30
31resource "coder_agent" "main" {
32  arch           = data.coder_provisioner.me.arch
33  os             = "linux"
34  startup_script = <<-EOT
35    set -e
36
37    # Prepare user home with default files on first start.
38    if [ ! -f ~/.init_done ]; then
39      cp -rT /etc/skel ~
40      touch ~/.init_done
41    fi
42
43    # Add any commands that should be executed at workspace startup (e.g install requirements, start a program, etc) here
44  EOT
45
46  # These environment variables allow you to make Git commits right away after creating a
47  # workspace. Note that they take precedence over configuration defined in ~/.gitconfig!
48  # You can remove this block if you'd prefer to configure Git manually or using
49  # dotfiles. (see docs/dotfiles.md)
50  env = {
51    GIT_AUTHOR_NAME     = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
52    GIT_AUTHOR_EMAIL    = "${data.coder_workspace_owner.me.email}"
53    GIT_COMMITTER_NAME  = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
54    GIT_COMMITTER_EMAIL = "${data.coder_workspace_owner.me.email}"
55  }
56
57  # The following metadata blocks are optional. They are used to display
58  # information about your workspace in the dashboard. You can remove them
59  # if you don't want to display any information.
60  # For basic resources, you can use the `coder stat` command.
61  # If you need more control, you can write your own script.
62  metadata {
63    display_name = "CPU Usage"
64    key          = "0_cpu_usage"
65    script       = "coder stat cpu"
66    interval     = 10
67    timeout      = 1
68  }
69
70  metadata {
71    display_name = "RAM Usage"
72    key          = "1_ram_usage"
73    script       = "coder stat mem"
74    interval     = 10
75    timeout      = 1
76  }
77
78  metadata {
79    display_name = "Home Disk"
80    key          = "3_home_disk"
81    script       = "coder stat disk --path $${HOME}"
82    interval     = 60
83    timeout      = 1
84  }
85
86  metadata {
87    display_name = "CPU Usage (Host)"
88    key          = "4_cpu_usage_host"
89    script       = "coder stat cpu --host"
90    interval     = 10
91    timeout      = 1
92  }
93
94  metadata {
95    display_name = "Memory Usage (Host)"
96    key          = "5_mem_usage_host"
97    script       = "coder stat mem --host"
98    interval     = 10
99    timeout      = 1
100  }
101
102  metadata {
103    display_name = "Load Average (Host)"
104    key          = "6_load_host"
105    # get load avg scaled by number of cores
106    script   = <<EOT
107      echo "`cat /proc/loadavg | awk '{ print $1 }'` `nproc`" | awk '{ printf "%0.2f", $1/$2 }'
108    EOT
109    interval = 60
110    timeout  = 1
111  }
112
113  metadata {
114    display_name = "Swap Usage (Host)"
115    key          = "7_swap_host"
116    script       = <<EOT
117      free -b | awk '/^Swap/ { printf("%.1f/%.1f", $3/1024.0/1024.0/1024.0, $2/1024.0/1024.0/1024.0) }'
118    EOT
119    interval     = 10
120    timeout      = 1
121  }
122}
123
124# See https://registry.coder.com/modules/code-server
125module "code-server" {
126  count  = data.coder_workspace.me.start_count
127  source = "registry.coder.com/modules/code-server/coder"
128
129  # This ensures that the latest version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production.
130  version = ">= 1.0.0"
131
132  agent_id = coder_agent.main.id
133  order    = 1
134}
135
136# See https://registry.coder.com/modules/jetbrains-gateway
137module "jetbrains_gateway" {
138  count  = data.coder_workspace.me.start_count
139  source = "registry.coder.com/modules/jetbrains-gateway/coder"
140
141  # JetBrains IDEs to make available for the user to select
142  jetbrains_ides = ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD", "RR"]
143  default        = "IU"
144
145  # Default folder to open when starting a JetBrains IDE
146  folder = "/home/coder"
147
148  # This ensures that the latest version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production.
149  version = ">= 1.0.0"
150
151  agent_id   = coder_agent.main.id
152  agent_name = "main"
153  order      = 2
154}
155
156resource "docker_volume" "home_volume" {
157  name = "coder-${data.coder_workspace.me.id}-home"
158  # Protect the volume from being deleted due to changes in attributes.
159  lifecycle {
160    ignore_changes = all
161  }
162  # Add labels in Docker to keep track of orphan resources.
163  labels {
164    label = "coder.owner"
165    value = data.coder_workspace_owner.me.name
166  }
167  labels {
168    label = "coder.owner_id"
169    value = data.coder_workspace_owner.me.id
170  }
171  labels {
172    label = "coder.workspace_id"
173    value = data.coder_workspace.me.id
174  }
175  # This field becomes outdated if the workspace is renamed but can
176  # be useful for debugging or cleaning out dangling volumes.
177  labels {
178    label = "coder.workspace_name_at_creation"
179    value = data.coder_workspace.me.name
180  }
181}
182
183resource "docker_container" "workspace" {
184  count = data.coder_workspace.me.start_count
185  image = "codercom/enterprise-base:ubuntu"
186  # Uses lower() to avoid Docker restriction on container names.
187  name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
188  # Hostname makes the shell more user friendly: coder@my-workspace:~$
189  hostname = data.coder_workspace.me.name
190  # Use the docker gateway if the access URL is 127.0.0.1
191  entrypoint = ["sh", "-c", replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal")]
192  env        = ["CODER_AGENT_TOKEN=${coder_agent.main.token}"]
193  host {
194    host = "host.docker.internal"
195    ip   = "host-gateway"
196  }
197  volumes {
198    container_path = "/home/coder"
199    volume_name    = docker_volume.home_volume.name
200    read_only      = false
201  }
202
203  # Add labels in Docker to keep track of orphan resources.
204  labels {
205    label = "coder.owner"
206    value = data.coder_workspace_owner.me.name
207  }
208  labels {
209    label = "coder.owner_id"
210    value = data.coder_workspace_owner.me.id
211  }
212  labels {
213    label = "coder.workspace_id"
214    value = data.coder_workspace.me.id
215  }
216  labels {
217    label = "coder.workspace_name"
218    value = data.coder_workspace.me.name
219  }
220}
221