Skip to content

Commit ce1d0cb

Browse files
Add docker-build template to coder-labs namespace (#235)
This template builds Docker containers from a Dockerfile, rather than using a pre-built image, allowing for more customization of the development environment. Based on the docker template that was removed in coder/coder#15504. --------- Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
1 parent ab87e0e commit ce1d0cb

File tree

3 files changed

+298
-0
lines changed

3 files changed

+298
-0
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
display_name: Docker Build
3+
description: Build Docker containers from Dockerfile as Coder workspaces
4+
icon: ../../../../.icons/docker.svg
5+
verified: true
6+
tags: [docker, container, dockerfile]
7+
---
8+
9+
# Remote Development on Docker Containers (Build from Dockerfile)
10+
11+
Build and provision Docker containers from a Dockerfile as [Coder workspaces](https://coder.com/docs/workspaces) with this example template.
12+
13+
This template builds a custom Docker image from the included Dockerfile, allowing you to customize the development environment by modifying the Dockerfile rather than using a pre-built image.
14+
15+
<!-- TODO: Add screenshot -->
16+
17+
## Prerequisites
18+
19+
### Infrastructure
20+
21+
The VM you run Coder on must have a running Docker socket and the `coder` user must be added to the Docker group:
22+
23+
```sh
24+
# Add coder user to Docker group
25+
sudo adduser coder docker
26+
27+
# Restart Coder server
28+
sudo systemctl restart coder
29+
30+
# Test Docker
31+
sudo -u coder docker ps
32+
```
33+
34+
## Architecture
35+
36+
This template provisions the following resources:
37+
38+
- Docker image (built from Dockerfile and kept locally)
39+
- Docker container pod (ephemeral)
40+
- Docker volume (persistent on `/home/coder`)
41+
42+
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 `build/Dockerfile`. Alternatively, individual developers can [personalize](https://coder.com/docs/dotfiles) their workspaces with dotfiles.
43+
44+
> [!NOTE]
45+
> This template is designed to be a starting point! Edit the Terraform and Dockerfile to extend the template to support your use case.
46+
47+
### Editing the image
48+
49+
Edit the `build/Dockerfile` and run `coder templates push` to update workspaces. The image will be rebuilt automatically when the Dockerfile changes.
50+
51+
## Difference from the standard Docker template
52+
53+
The main difference between this template and the standard Docker template is:
54+
55+
- **Standard Docker template**: Uses a pre-built image (e.g., `codercom/enterprise-base:ubuntu`)
56+
- **Docker Build template**: Builds a custom image from the included `build/Dockerfile`
57+
58+
This allows for more customization of the development environment while maintaining the same workspace functionality.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
FROM ubuntu
2+
3+
RUN apt-get update \
4+
&& apt-get install -y \
5+
curl \
6+
git \
7+
golang \
8+
sudo \
9+
vim \
10+
wget \
11+
&& rm -rf /var/lib/apt/lists/*
12+
13+
ARG USER=coder
14+
RUN useradd --groups sudo --no-create-home --shell /bin/bash ${USER} \
15+
&& echo "${USER} ALL=(ALL) NOPASSWD:ALL" >/etc/sudoers.d/${USER} \
16+
&& chmod 0440 /etc/sudoers.d/${USER}
17+
USER ${USER}
18+
WORKDIR /home/${USER}
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
terraform {
2+
required_providers {
3+
coder = {
4+
source = "coder/coder"
5+
}
6+
docker = {
7+
source = "kreuzwerker/docker"
8+
}
9+
}
10+
}
11+
12+
locals {
13+
username = data.coder_workspace_owner.me.name
14+
}
15+
16+
variable "docker_socket" {
17+
default = ""
18+
description = "(Optional) Docker socket URI"
19+
type = string
20+
}
21+
22+
provider "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+
27+
data "coder_provisioner" "me" {}
28+
data "coder_workspace" "me" {}
29+
data "coder_workspace_owner" "me" {}
30+
31+
resource "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+
# Install the latest code-server.
44+
# Append "--version x.x.x" to install a specific version of code-server.
45+
curl -fsSL https://code-server.dev/install.sh | sh -s -- --method=standalone --prefix=/tmp/code-server
46+
47+
# Start code-server in the background.
48+
/tmp/code-server/bin/code-server --auth none --port 13337 >/tmp/code-server.log 2>&1 &
49+
EOT
50+
51+
# These environment variables allow you to make Git commits right away after creating a
52+
# workspace. Note that they take precedence over configuration defined in ~/.gitconfig!
53+
# You can remove this block if you'd prefer to configure Git manually or using
54+
# dotfiles. (see docs/dotfiles.md)
55+
env = {
56+
GIT_AUTHOR_NAME = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
57+
GIT_AUTHOR_EMAIL = "${data.coder_workspace_owner.me.email}"
58+
GIT_COMMITTER_NAME = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
59+
GIT_COMMITTER_EMAIL = "${data.coder_workspace_owner.me.email}"
60+
}
61+
62+
# The following metadata blocks are optional. They are used to display
63+
# information about your workspace in the dashboard. You can remove them
64+
# if you don't want to display any information.
65+
# For basic resources, you can use the `coder stat` command.
66+
# If you need more control, you can write your own script.
67+
metadata {
68+
display_name = "CPU Usage"
69+
key = "0_cpu_usage"
70+
script = "coder stat cpu"
71+
interval = 10
72+
timeout = 1
73+
}
74+
75+
metadata {
76+
display_name = "RAM Usage"
77+
key = "1_ram_usage"
78+
script = "coder stat mem"
79+
interval = 10
80+
timeout = 1
81+
}
82+
83+
metadata {
84+
display_name = "Home Disk"
85+
key = "3_home_disk"
86+
script = "coder stat disk --path $${HOME}"
87+
interval = 60
88+
timeout = 1
89+
}
90+
91+
metadata {
92+
display_name = "CPU Usage (Host)"
93+
key = "4_cpu_usage_host"
94+
script = "coder stat cpu --host"
95+
interval = 10
96+
timeout = 1
97+
}
98+
99+
metadata {
100+
display_name = "Memory Usage (Host)"
101+
key = "5_mem_usage_host"
102+
script = "coder stat mem --host"
103+
interval = 10
104+
timeout = 1
105+
}
106+
107+
metadata {
108+
display_name = "Load Average (Host)"
109+
key = "6_load_host"
110+
# get load avg scaled by number of cores
111+
script = <<EOT
112+
echo "`cat /proc/loadavg | awk '{ print $1 }'` `nproc`" | awk '{ printf "%0.2f", $1/$2 }'
113+
EOT
114+
interval = 60
115+
timeout = 1
116+
}
117+
118+
metadata {
119+
display_name = "Swap Usage (Host)"
120+
key = "7_swap_host"
121+
script = <<EOT
122+
free -b | awk '/^Swap/ { printf("%.1f/%.1f", $3/1024.0/1024.0/1024.0, $2/1024.0/1024.0/1024.0) }'
123+
EOT
124+
interval = 10
125+
timeout = 1
126+
}
127+
}
128+
129+
resource "coder_app" "code-server" {
130+
agent_id = coder_agent.main.id
131+
slug = "code-server"
132+
display_name = "code-server"
133+
url = "http://localhost:13337/?folder=/home/${local.username}"
134+
icon = "/icon/code.svg"
135+
subdomain = false
136+
share = "owner"
137+
138+
healthcheck {
139+
url = "http://localhost:13337/healthz"
140+
interval = 5
141+
threshold = 6
142+
}
143+
}
144+
145+
resource "docker_volume" "home_volume" {
146+
name = "coder-${data.coder_workspace.me.id}-home"
147+
# Protect the volume from being deleted due to changes in attributes.
148+
lifecycle {
149+
ignore_changes = all
150+
}
151+
# Add labels in Docker to keep track of orphan resources.
152+
labels {
153+
label = "coder.owner"
154+
value = data.coder_workspace_owner.me.name
155+
}
156+
labels {
157+
label = "coder.owner_id"
158+
value = data.coder_workspace_owner.me.id
159+
}
160+
labels {
161+
label = "coder.workspace_id"
162+
value = data.coder_workspace.me.id
163+
}
164+
# This field becomes outdated if the workspace is renamed but can
165+
# be useful for debugging or cleaning out dangling volumes.
166+
labels {
167+
label = "coder.workspace_name_at_creation"
168+
value = data.coder_workspace.me.name
169+
}
170+
}
171+
172+
resource "docker_image" "main" {
173+
name = "coder-${data.coder_workspace.me.id}"
174+
build {
175+
context = "./build"
176+
build_args = {
177+
USER = local.username
178+
}
179+
}
180+
triggers = {
181+
dir_sha1 = sha1(join("", [for f in fileset(path.module, "build/*") : filesha1(f)]))
182+
}
183+
}
184+
185+
resource "docker_container" "workspace" {
186+
count = data.coder_workspace.me.start_count
187+
image = docker_image.main.name
188+
# Uses lower() to avoid Docker restriction on container names.
189+
name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
190+
# Hostname makes the shell more user friendly: coder@my-workspace:~$
191+
hostname = data.coder_workspace.me.name
192+
# Use the docker gateway if the access URL is 127.0.0.1
193+
entrypoint = ["sh", "-c", replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal")]
194+
env = ["CODER_AGENT_TOKEN=${coder_agent.main.token}"]
195+
host {
196+
host = "host.docker.internal"
197+
ip = "host-gateway"
198+
}
199+
volumes {
200+
container_path = "/home/${local.username}"
201+
volume_name = docker_volume.home_volume.name
202+
read_only = false
203+
}
204+
205+
# Add labels in Docker to keep track of orphan resources.
206+
labels {
207+
label = "coder.owner"
208+
value = data.coder_workspace_owner.me.name
209+
}
210+
labels {
211+
label = "coder.owner_id"
212+
value = data.coder_workspace_owner.me.id
213+
}
214+
labels {
215+
label = "coder.workspace_id"
216+
value = data.coder_workspace.me.id
217+
}
218+
labels {
219+
label = "coder.workspace_name"
220+
value = data.coder_workspace.me.name
221+
}
222+
}

0 commit comments

Comments
 (0)