Skip to content

Commit f6d24e1

Browse files
committed
feat(examples/templates): add GCP VM devcontainer template
1 parent 3f6096b commit f6d24e1

File tree

2 files changed

+291
-0
lines changed

2 files changed

+291
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
display_name: Google Compute Engine Devcontainer (Linux)
3+
description: Provision a Devcontainer on Google Compute Engine instances as Coder workspaces
4+
icon: ../../../site/static/icon/gcp.png
5+
maintainer_github: coder
6+
verified: true
7+
tags: [vm, linux, gcp, devcontainer]
8+
---
9+
10+
# Remote Development in a Devcontainer on Google Compute Engine
11+
12+
## Prerequisites
13+
14+
### Authentication
15+
16+
This template assumes that coderd is run in an environment that is authenticated
17+
with Google Cloud. For example, run `gcloud auth application-default login` to
18+
import credentials on the system and user running coderd. For other ways to
19+
authenticate [consult the Terraform
20+
docs](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/getting_started#adding-credentials).
21+
22+
Coder requires a Google Cloud Service Account to provision workspaces. To create
23+
a service account:
24+
25+
1. Navigate to the [CGP
26+
console](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create),
27+
and select your Cloud project (if you have more than one project associated
28+
with your account)
29+
30+
1. Provide a service account name (this name is used to generate the service
31+
account ID)
32+
33+
1. Click **Create and continue**, and choose the following IAM roles to grant to
34+
the service account:
35+
36+
- Compute Admin
37+
- Service Account User
38+
39+
Click **Continue**.
40+
41+
1. Click on the created key, and navigate to the **Keys** tab.
42+
43+
1. Click **Add key** > **Create new key**.
44+
45+
1. Generate a **JSON private key**, which will be what you provide to Coder
46+
during the setup process.
47+
48+
## Architecture
49+
50+
This template provisions the following resources:
51+
52+
- GCP VM (persistent)
53+
- GCP Disk (persistent, mounted to root)
54+
55+
Coder persists the root volume. The full filesystem is preserved when the workspace restarts. See this [community example](https://github.com/bpmct/coder-templates/tree/main/aws-linux-ephemeral) of an ephemeral AWS instance.
56+
57+
> **Note**
58+
> This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.
59+
60+
## code-server
61+
62+
`code-server` is installed via the `startup_script` argument in the `coder_agent`
63+
resource block. The `coder_app` resource is defined to access `code-server` through
64+
the dashboard UI over `localhost:13337`.
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
terraform {
2+
required_providers {
3+
coder = {
4+
source = "coder/coder"
5+
}
6+
google = {
7+
source = "hashicorp/google"
8+
}
9+
}
10+
}
11+
12+
provider "coder" {
13+
}
14+
15+
variable "project_id" {
16+
description = "Which Google Compute Project should your workspace live in?"
17+
}
18+
19+
data "coder_parameter" "zone" {
20+
name = "zone"
21+
display_name = "Zone"
22+
description = "Which zone should your workspace live in?"
23+
type = "string"
24+
icon = "/emojis/1f30e.png"
25+
default = "us-central1-a"
26+
mutable = false
27+
option {
28+
name = "North America (Northeast)"
29+
value = "northamerica-northeast1-a"
30+
icon = "/emojis/1f1fa-1f1f8.png"
31+
}
32+
option {
33+
name = "North America (Central)"
34+
value = "us-central1-a"
35+
icon = "/emojis/1f1fa-1f1f8.png"
36+
}
37+
option {
38+
name = "North America (West)"
39+
value = "us-west2-c"
40+
icon = "/emojis/1f1fa-1f1f8.png"
41+
}
42+
option {
43+
name = "Europe (West)"
44+
value = "europe-west4-b"
45+
icon = "/emojis/1f1ea-1f1fa.png"
46+
}
47+
option {
48+
name = "South America (East)"
49+
value = "southamerica-east1-a"
50+
icon = "/emojis/1f1e7-1f1f7.png"
51+
}
52+
}
53+
54+
provider "google" {
55+
zone = data.coder_parameter.zone.value
56+
project = var.project_id
57+
}
58+
59+
data "google_compute_default_service_account" "default" {
60+
}
61+
62+
data "coder_workspace" "me" {
63+
}
64+
65+
resource "google_compute_disk" "root" {
66+
name = "coder-${data.coder_workspace.me.id}-root"
67+
type = "pd-ssd"
68+
zone = data.coder_parameter.zone.value
69+
image = "debian-cloud/debian-12"
70+
lifecycle {
71+
ignore_changes = [name, image]
72+
}
73+
}
74+
75+
data "coder_parameter" "repo_url" {
76+
name = "repo_url"
77+
display_name = "Repository URL"
78+
default = "https://github.com/coder/envbuilder-starter-devcontainer"
79+
description = "Repository URL"
80+
mutable = true
81+
}
82+
83+
resource "coder_agent" "dev" {
84+
count = data.coder_workspace.me.start_count
85+
arch = "amd64"
86+
auth = "token"
87+
os = "linux"
88+
dir = "/worskpaces"
89+
connection_timeout = 0
90+
startup_script_timeout = 180
91+
startup_script = <<-EOT
92+
set -e
93+
94+
# install and start code-server
95+
curl -fsSL https://code-server.dev/install.sh | sh -s -- --method=standalone --prefix=/tmp/code-server
96+
/tmp/code-server/bin/code-server --auth none --port 13337 >/tmp/code-server.log 2>&1 &
97+
EOT
98+
99+
metadata {
100+
key = "cpu"
101+
display_name = "CPU Usage"
102+
interval = 5
103+
timeout = 5
104+
script = "coder stat cpu"
105+
}
106+
metadata {
107+
key = "memory"
108+
display_name = "Memory Usage"
109+
interval = 5
110+
timeout = 5
111+
script = "coder stat mem"
112+
}
113+
metadata {
114+
key = "disk"
115+
display_name = "Disk Usage"
116+
interval = 5
117+
timeout = 5
118+
script = "coder stat disk"
119+
}
120+
}
121+
122+
resource "coder_app" "code-server" {
123+
count = data.coder_workspace.me.start_count
124+
agent_id = coder_agent.dev[0].id
125+
slug = "code-server"
126+
display_name = "code-server"
127+
icon = "/icon/code.svg"
128+
url = "http://localhost:13337?folder=/home/coder"
129+
subdomain = false
130+
share = "owner"
131+
132+
healthcheck {
133+
url = "http://localhost:13337/healthz"
134+
interval = 3
135+
threshold = 10
136+
}
137+
}
138+
139+
resource "google_compute_instance" "vm" {
140+
zone = data.coder_parameter.zone.value
141+
name = "coder-${lower(data.coder_workspace.me.owner)}-${lower(data.coder_workspace.me.name)}-root"
142+
machine_type = "e2-medium"
143+
# data.coder_workspace.me.owner == "default" is a workaround to suppress error in the terraform plan phase while creating a new workspace.
144+
desired_status = (data.coder_workspace.me.owner == "default" || data.coder_workspace.me.start_count == 1) ? "RUNNING" : "TERMINATED"
145+
146+
network_interface {
147+
network = "default"
148+
access_config {
149+
// Ephemeral public IP
150+
}
151+
}
152+
153+
boot_disk {
154+
auto_delete = false
155+
source = google_compute_disk.root.name
156+
}
157+
158+
service_account {
159+
email = data.google_compute_default_service_account.default.email
160+
scopes = ["cloud-platform"]
161+
}
162+
163+
metadata = {
164+
# The startup script runs as root with no $HOME environment set up, so instead of directly
165+
# running the agent init script, create a user (with a homedir, default shell and sudo
166+
# permissions) and execute the init script as that user.
167+
startup-script = <<-META
168+
#!/usr/bin/env sh
169+
set -eux
170+
171+
# If user does not exist, create it and set up passwordless sudo
172+
if ! id -u "${local.linux_user}" >/dev/null 2>&1; then
173+
useradd -m -s /bin/bash "${local.linux_user}"
174+
echo "${local.linux_user} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/coder-user
175+
fi
176+
177+
# Check for Docker, install if not present
178+
if ! command -v docker &> /dev/null
179+
then
180+
echo "Docker not found, installing..."
181+
curl -fsSL https://get.docker.com -o get-docker.sh && sudo sh get-docker.sh 2>&1 >/dev/null
182+
sudo usermod -aG docker ${local.linux_user}
183+
newgrp docker
184+
else
185+
echo "Docker is already installed."
186+
fi
187+
# Start envbuilder
188+
docker run --rm \
189+
-v /tmp/envbuilder:/workspaces \
190+
-e CODER_AGENT_TOKEN="${try(coder_agent.dev[0].token, "")}" \
191+
-e CODER_AGENT_URL="${data.coder_workspace.me.access_url}" \
192+
-e GIT_URL="${data.coder_parameter.repo_url.value}" \
193+
-e INIT_SCRIPT="echo ${base64encode(try(coder_agent.dev[0].init_script, ""))} | base64 -d | sh" \
194+
-e FALLBACK_IMAGE="codercom/enterprise-base:ubuntu" \
195+
ghcr.io/coder/envbuilder
196+
META
197+
}
198+
}
199+
200+
locals {
201+
# Ensure Coder username is a valid Linux username
202+
linux_user = lower(substr(data.coder_workspace.me.owner, 0, 32))
203+
}
204+
205+
resource "coder_metadata" "workspace_info" {
206+
count = data.coder_workspace.me.start_count
207+
resource_id = google_compute_instance.vm.id
208+
209+
item {
210+
key = "type"
211+
value = google_compute_instance.vm.machine_type
212+
}
213+
214+
item {
215+
key = "zone"
216+
value = data.coder_parameter.zone.value
217+
}
218+
}
219+
220+
resource "coder_metadata" "home_info" {
221+
resource_id = google_compute_disk.root.id
222+
223+
item {
224+
key = "size"
225+
value = "${google_compute_disk.root.size} GiB"
226+
}
227+
}

0 commit comments

Comments
 (0)