Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ The startup script is inline in each template's `template.tf` (e.g. `freeform/te
### Volume Strategy
- **Home directory**: Host path `/coder-workspaces/<owner>-<workspace>` → Container `/home/coder`
- **Docker cache**: Named volume `coder-<owner>-<workspace>-dind-cache` → `/var/lib/docker`
- **Isolation**: Each workspace gets separate host directory and Docker volume
- **Homebrew**: Named volume `coder-<owner>-<workspace>-linuxbrew` → `/home/linuxbrew` — persists the Homebrew Cellar across workspace stop/start, so `brew upgrade`/`brew install` done inside a workspace survives restarts. Unlike the `/home/coder` bind mount, a fresh named volume is auto-populated by Docker from the image's existing `/home/linuxbrew` contents on first mount, so no manual copy step is needed — only an ownership fix (`sudo chown -R coder:coder /home/linuxbrew`) in the startup script. `HOMEBREW_CACHE` is set to `/home/linuxbrew/.cache/Homebrew` (via the Dockerfile) so the cache and Cellar always stay on the same filesystem, avoiding cross-device link (`EXDEV`) errors.
- **Isolation**: Each workspace gets separate host directory and Docker volumes

### Terraform Variables
Key template variables (e.g. in `freeform/template.tf`):
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.4
v0.5
16 changes: 16 additions & 0 deletions drupal-contrib/template.tf
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ resource "coder_agent" "main" {
fi

sudo chown coder:coder /home/coder
sudo chown -R coder:coder /home/linuxbrew

if [ ! -f "/home/coder/.bashrc" ]; then
echo "Initializing home directory..."
Expand Down Expand Up @@ -1073,6 +1074,15 @@ resource "docker_volume" "coder_dind_cache" {
name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}-dind-cache"
}

# Persists /home/linuxbrew (Homebrew Cellar) across workspace stop/start so
# `brew upgrade`/`brew install` survives restarts. Not gated by start_count,
# matching coder_dind_cache above. Docker auto-populates a newly-created
# named volume from the image's existing directory contents on first mount,
# so no manual copy-from-image seed step is needed here.
resource "docker_volume" "coder_linuxbrew" {
name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}-linuxbrew"
}

module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
Expand Down Expand Up @@ -1188,6 +1198,12 @@ resource "docker_container" "workspace" {
target = "/var/lib/docker"
}

mounts {
type = "volume"
source = docker_volume.coder_linuxbrew.name
target = "/home/linuxbrew"
}

env = [
"CODER_AGENT_TOKEN=${coder_agent.main.token}",
"CODER_WORKSPACE_NAME=${data.coder_workspace.me.name}",
Expand Down
8 changes: 8 additions & 0 deletions drupal-contrib/tests/validate.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,11 @@ run "memory_above_maximum" {
}
expect_failures = [var.memory]
}

run "linuxbrew_volume_created" {
command = plan
assert {
condition = docker_volume.coder_linuxbrew.name == "coder-testuser-test-workspace-linuxbrew"
error_message = "docker_volume.coder_linuxbrew must be named per the coder-<owner>-<workspace>-linuxbrew convention"
}
}
16 changes: 16 additions & 0 deletions drupal-core/template.tf
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ resource "coder_agent" "main" {
# Since the volume comes from the host, it might have host permissions.
# We fix this on every startup.
sudo chown coder:coder /home/coder
sudo chown -R coder:coder /home/linuxbrew

# Copy defaults if empty (first run)
if [ ! -f "/home/coder/.bashrc" ]; then
Expand Down Expand Up @@ -1362,6 +1363,15 @@ resource "docker_volume" "coder_dind_cache" {
name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}-dind-cache"
}

# Persists /home/linuxbrew (Homebrew Cellar) across workspace stop/start so
# `brew upgrade`/`brew install` survives restarts. Not gated by start_count,
# matching coder_dind_cache above. Docker auto-populates a newly-created
# named volume from the image's existing directory contents on first mount,
# so no manual copy-from-image seed step is needed here.
resource "docker_volume" "coder_linuxbrew" {
name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}-linuxbrew"
}

# VS Code for Web
module "vscode-web" {
count = data.coder_workspace.me.start_count
Expand Down Expand Up @@ -1523,6 +1533,12 @@ resource "docker_container" "workspace" {
target = "/var/lib/docker"
}

mounts {
type = "volume"
source = docker_volume.coder_linuxbrew.name
target = "/home/linuxbrew"
}

# Environment variables
env = [
"CODER_AGENT_TOKEN=${coder_agent.main.token}",
Expand Down
11 changes: 11 additions & 0 deletions drupal-core/tests/validate.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,14 @@ run "memory_above_maximum" {
}
expect_failures = [var.memory]
}

run "linuxbrew_volume_created" {
command = plan
variables {
cache_path = "/tmp/mock-cache"
}
assert {
condition = docker_volume.coder_linuxbrew.name == "coder-testuser-test-workspace-linuxbrew"
error_message = "docker_volume.coder_linuxbrew must be named per the coder-<owner>-<workspace>-linuxbrew convention"
}
}
16 changes: 16 additions & 0 deletions freeform/template.tf
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ resource "coder_agent" "main" {
fi

sudo chown coder:coder /home/coder
sudo chown -R coder:coder /home/linuxbrew

if [ ! -f "/home/coder/.bashrc" ]; then
echo "Initializing home directory..."
Expand Down Expand Up @@ -443,6 +444,15 @@ resource "docker_volume" "coder_dind_cache" {
name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}-dind-cache"
}

# Persists /home/linuxbrew (Homebrew Cellar) across workspace stop/start so
# `brew upgrade`/`brew install` survives restarts. Not gated by start_count,
# matching coder_dind_cache above. Docker auto-populates a newly-created
# named volume from the image's existing directory contents on first mount,
# so no manual copy-from-image seed step is needed here.
resource "docker_volume" "coder_linuxbrew" {
name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}-linuxbrew"
}

module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
Expand Down Expand Up @@ -577,6 +587,12 @@ resource "docker_container" "workspace" {
target = "/var/lib/docker"
}

mounts {
type = "volume"
source = docker_volume.coder_linuxbrew.name
target = "/home/linuxbrew"
}

env = [
"CODER_AGENT_TOKEN=${coder_agent.main.token}",
"CODER_WORKSPACE_NAME=${data.coder_workspace.me.name}",
Expand Down
8 changes: 8 additions & 0 deletions freeform/tests/validate.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,11 @@ run "memory_above_maximum" {
}
expect_failures = [var.memory]
}

run "linuxbrew_volume_created" {
command = plan
assert {
condition = docker_volume.coder_linuxbrew.name == "coder-testuser-test-workspace-linuxbrew"
error_message = "docker_volume.coder_linuxbrew must be named per the coder-<owner>-<workspace>-linuxbrew convention"
}
}
10 changes: 9 additions & 1 deletion image/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,17 @@ RUN printf '#!/usr/bin/env bash\nddev php "$@"\n' > /usr/local/bin/php && \

RUN mkdir -p /home/linuxbrew/.linuxbrew && chown -R coder:coder /home/linuxbrew/

# Homebrew's cache must live on the same filesystem as the Cellar
# (/home/linuxbrew/.linuxbrew) to avoid cross-device link (EXDEV) errors when
# brew hardlinks a downloaded bottle from cache into the Cellar. At runtime,
# /home/linuxbrew is mounted as a persistent per-workspace volume (see each
# template's template.tf), so this path stays on the same filesystem as the
# Cellar across workspace restarts too.
ENV HOMEBREW_CACHE=/home/linuxbrew/.cache/Homebrew

USER coder
RUN /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
RUN /home/linuxbrew/.linuxbrew/bin/brew install \
RUN mkdir -p "$HOMEBREW_CACHE" && /home/linuxbrew/.linuxbrew/bin/brew install \
bats-core \
bats-core/bats-core/bats-assert \
bats-core/bats-core/bats-file \
Expand Down
Loading