
Docker Containers
By coder
Provision Docker containers as Coder workspaces
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