Compare commits

...

10 Commits

Author SHA1 Message Date
Danielle Maywood 258591833f fix(devcontainers-cli): allow yarn to install when packageManager not yarn (#287)
On our dogfood workspaces, we fail to install `@devcontainers/cli` with
`yarn` because our agent directory `/home/coder/coder` contains a
`package.json` with `packageManager` being set to `pnpm`. This change
instead ensures to run `yarn global add` inside the
`$CODER_SCRIPT_DATA_DIR` so that we don't read a `package.json` and
cause things to break.
2025-08-04 13:00:13 +01:00
Michael Orlov 3efc22c589 fix/amazon-q mcp integration (#248)
Co-authored-by: Michael Orlov <michaelo@amdocs.com>
Co-authored-by: DevCats <christofer@coder.com>
2025-08-03 06:27:46 +00:00
Hugo Dutka 8ba4c323c2 fix(claude-code): workaround for a coder bug (#283)
Workaround to address https://github.com/coder/coder/issues/18776
2025-08-02 16:21:13 +02:00
Phorcys 3afa72095b chore: polish some modules readme (#272) 2025-08-01 21:12:05 -05:00
Michael Smith cf66809349 fix: update HCP vault links to use current URL patterns (#275)
Realized this was an issue while fixing how we're rendering GFM alerts
on the Registry website.

## Description

Basically, the URLs we're using right now are technically valid, but
they were using the old URL pattern from before we created the
`coder/registry` repo. The Registry website has logic to handle
redirects for these, but we should avoid triggering it as it adds
unnecessary entries to the user's browser history.

### Changes made
- Updated all Registry links in the HCP Vault alert to include `coder`
as the namespace

## Type of Change

- [ ] New module
- [ ] Bug fix
- [ ] Feature/enhancement
- [x] Documentation
- [ ] Other

## Testing & Validation

- [x] Tests pass (`bun test`)
- [x] Code formatted (`bun run fmt`)
- [x] Changes tested locally
2025-07-31 19:50:37 -05:00
Michael Smith 020a2cba79 fix: update icon path for k8s-username (#276)
## Description

This is an issue that doesn't exist within `coder/registry`, but was
breaking our build process for the Registry website. We were using an
invalid image path.

## Type of Change

- [ ] New module
- [x] Bug fix
- [ ] Feature/enhancement
- [ ] Documentation
- [ ] Other

## Testing & Validation

- [x] Tests pass (`bun test`)
- [x] Code formatted (`bun run fmt`)
- [x] Changes tested locally
2025-07-31 20:58:06 +01:00
Eric Paulsen 3fd7b47097 feat: dynamic username template (#261)
## Description

this PR adds a new template that creates & runs Coder workspaces on K8s
with the user's Coder `username` as the Linux UID. a commonly requested
use-case by customers.

## Type of Change

- [x] New module
- [ ] Bug fix
- [ ] Feature/enhancement
- [ ] Documentation
- [ ] Other

## Module Information

<!-- Delete this section if not applicable -->

**Path:** `registry/ericpaulsen/templates/k8s-pod-username.tf`  

## Testing & Validation

- [x] Changes tested locally
2025-07-31 14:25:29 +00:00
Atif Ali e1f077dac3 chore: deploy registry changes nightly (#264) 2025-07-30 22:33:25 -04:00
Marcin Tojek 29c52b7072 feat: jupyter-notebook: preinstall Python packages (#263) 2025-07-30 20:25:53 -05:00
Hugo Dutka 312cb71bf0 chore: bump agentapi version in the claude code module (#265)
Bump agentapi to v0.3.0 and claude code module version.
2025-07-30 13:37:30 +02:00
18 changed files with 489 additions and 83 deletions
+3
View File
@@ -1,6 +1,9 @@
name: deploy-registry
on:
schedule:
# Runs at 02:30 UTC Monday through Friday
- cron: "30 2 * * 1-5"
push:
tags:
# Matches release/<namespace>/<resource_name>/<semantic_version>
+7 -6
View File
@@ -1,21 +1,22 @@
---
display_name: AgentAPI
description: Building block for modules that need to run an agentapi server
description: Building block for modules that need to run an AgentAPI server
icon: ../../../../.icons/coder.svg
verified: true
tags: [internal]
tags: [internal, library]
---
# AgentAPI
The AgentAPI module is a building block for modules that need to run an agentapi server. It is intended primarily for internal use by Coder to create modules compatible with Tasks.
> [!CAUTION]
> We do not recommend using this module directly. Instead, please consider using one of our [Tasks-compatible AI agent modules](https://registry.coder.com/modules?search=tag%3Atasks).
We do not recommend using this module directly. Instead, please consider using one of our [Tasks-compatible AI agent modules](https://registry.coder.com/modules?search=tag%3Atasks).
The AgentAPI module is a building block for modules that need to run an AgentAPI server. It is intended primarily for internal use by Coder to create modules compatible with Tasks.
```tf
module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "1.0.1"
version = "1.0.2"
agent_id = var.agent_id
web_app_slug = local.app_slug
@@ -50,4 +51,4 @@ module "agentapi" {
## For module developers
For a complete example of how to use this module, see the [goose module](https://github.com/coder/registry/blob/main/registry/coder/modules/goose/main.tf).
For a complete example of how to use this module, see the [Goose module](https://github.com/coder/registry/blob/main/registry/coder/modules/goose/main.tf).
+8 -33
View File
@@ -13,7 +13,7 @@ Run [Aider](https://aider.chat) AI pair programming in your workspace. This modu
```tf
module "aider" {
source = "registry.coder.com/coder/aider/coder"
version = "1.1.1"
version = "1.1.2"
agent_id = coder_agent.example.id
}
```
@@ -30,29 +30,8 @@ module "aider" {
## Module Parameters
| Parameter | Description | Type | Default |
| ---------------------------------- | -------------------------------------------------------------------------- | -------- | ------------------- |
| `agent_id` | The ID of a Coder agent (required) | `string` | - |
| `folder` | The folder to run Aider in | `string` | `/home/coder` |
| `install_aider` | Whether to install Aider | `bool` | `true` |
| `aider_version` | The version of Aider to install | `string` | `"latest"` |
| `use_screen` | Whether to use screen for running Aider in the background | `bool` | `true` |
| `use_tmux` | Whether to use tmux instead of screen for running Aider in the background | `bool` | `false` |
| `session_name` | Name for the persistent session (screen or tmux) | `string` | `"aider"` |
| `order` | Position of the app in the UI presentation | `number` | `null` |
| `icon` | The icon to use for the app | `string` | `"/icon/aider.svg"` |
| `experiment_report_tasks` | Whether to enable task reporting | `bool` | `true` |
| `system_prompt` | System prompt for instructing Aider on task reporting and behavior | `string` | See default in code |
| `task_prompt` | Task prompt to use with Aider | `string` | `""` |
| `ai_provider` | AI provider to use with Aider (openai, anthropic, azure, etc.) | `string` | `"anthropic"` |
| `ai_model` | AI model to use (can use Aider's built-in aliases like "sonnet", "4o") | `string` | `"sonnet"` |
| `ai_api_key` | API key for the selected AI provider | `string` | `""` |
| `custom_env_var_name` | Custom environment variable name when using custom provider | `string` | `""` |
| `experiment_pre_install_script` | Custom script to run before installing Aider | `string` | `null` |
| `experiment_post_install_script` | Custom script to run after installing Aider | `string` | `null` |
| `experiment_additional_extensions` | Additional extensions configuration in YAML format to append to the config | `string` | `null` |
> **Note**: `use_screen` and `use_tmux` cannot both be enabled at the same time. By default, `use_screen` is set to `true` and `use_tmux` is set to `false`.
> [!NOTE]
> The `use_screen` and `use_tmux` parameters cannot both be enabled at the same time. By default, `use_screen` is set to `true` and `use_tmux` is set to `false`.
## Usage Examples
@@ -68,7 +47,7 @@ variable "anthropic_api_key" {
module "aider" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/aider/coder"
version = "1.1.1"
version = "1.1.2"
agent_id = coder_agent.example.id
ai_api_key = var.anthropic_api_key
}
@@ -93,7 +72,7 @@ variable "openai_api_key" {
module "aider" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/aider/coder"
version = "1.1.1"
version = "1.1.2"
agent_id = coder_agent.example.id
use_tmux = true
ai_provider = "openai"
@@ -114,7 +93,7 @@ variable "custom_api_key" {
module "aider" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/aider/coder"
version = "1.1.1"
version = "1.1.2"
agent_id = coder_agent.example.id
ai_provider = "custom"
custom_env_var_name = "MY_CUSTOM_API_KEY"
@@ -131,7 +110,7 @@ You can extend Aider's capabilities by adding custom extensions:
module "aider" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/aider/coder"
version = "1.1.1"
version = "1.1.2"
agent_id = coder_agent.example.id
ai_api_key = var.anthropic_api_key
@@ -210,7 +189,7 @@ data "coder_parameter" "ai_prompt" {
module "aider" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/aider/coder"
version = "1.1.1"
version = "1.1.2"
agent_id = coder_agent.example.id
ai_api_key = var.anthropic_api_key
task_prompt = data.coder_parameter.ai_prompt.value
@@ -308,7 +287,3 @@ If you encounter issues:
3. **Browser mode issues**: If the browser interface doesn't open, check that you're accessing it from a machine that can reach your Coder workspace
For more information on using Aider, see the [Aider documentation](https://aider.chat/docs/).
```
```
+5 -4
View File
@@ -13,8 +13,9 @@ Run [Amazon Q](https://aws.amazon.com/q/) in your workspace to access Amazon's A
```tf
module "amazon-q" {
source = "registry.coder.com/coder/amazon-q/coder"
version = "1.1.1"
version = "1.1.2"
agent_id = coder_agent.example.id
# Required: see below for how to generate
experiment_auth_tarball = var.amazon_q_auth_tarball
}
@@ -81,7 +82,7 @@ module "amazon-q" {
```tf
module "amazon-q" {
source = "registry.coder.com/coder/amazon-q/coder"
version = "1.1.1"
version = "1.1.2"
agent_id = coder_agent.example.id
experiment_auth_tarball = var.amazon_q_auth_tarball
experiment_use_tmux = true
@@ -93,7 +94,7 @@ module "amazon-q" {
```tf
module "amazon-q" {
source = "registry.coder.com/coder/amazon-q/coder"
version = "1.1.1"
version = "1.1.2"
agent_id = coder_agent.example.id
experiment_auth_tarball = var.amazon_q_auth_tarball
experiment_report_tasks = true
@@ -105,7 +106,7 @@ module "amazon-q" {
```tf
module "amazon-q" {
source = "registry.coder.com/coder/amazon-q/coder"
version = "1.1.1"
version = "1.1.2"
agent_id = coder_agent.example.id
experiment_auth_tarball = var.amazon_q_auth_tarball
experiment_pre_install_script = "echo Pre-install!"
+7 -25
View File
@@ -125,24 +125,7 @@ variable "ai_prompt" {
locals {
encoded_pre_install_script = var.experiment_pre_install_script != null ? base64encode(var.experiment_pre_install_script) : ""
encoded_post_install_script = var.experiment_post_install_script != null ? base64encode(var.experiment_post_install_script) : ""
# We need to use allowed tools to limit the context Amazon Q receives.
# Amazon Q can't handle big contexts, and the `create_template_version` tool
# has a description that's too long.
mcp_json = <<EOT
{
"mcpServers": {
"coder": {
"command": "coder",
"args": ["exp", "mcp", "server", "--allowed-tools", "coder_report_task"],
"env": {
"CODER_MCP_APP_STATUS_SLUG": "amazon-q"
}
}
}
}
EOT
encoded_mcp_json = base64encode(local.mcp_json)
full_prompt = <<-EOT
full_prompt = <<-EOT
${var.system_prompt}
Your first task is:
@@ -211,6 +194,12 @@ resource "coder_script" "amazon_q" {
cd "$PREV_DIR"
echo "Extracted auth tarball"
if [ "${var.experiment_report_tasks}" = "true" ]; then
echo "Configuring Amazon Q to report tasks via Coder MCP..."
q mcp add --name coder --command "coder" --args "exp,mcp,server,--allowed-tools,coder_report_task" --env "CODER_MCP_APP_STATUS_SLUG=amazon-q" --scope global --force
echo "Added Coder MCP server to Amazon Q configuration"
fi
if [ -n "${local.encoded_post_install_script}" ]; then
echo "Running post-install script..."
echo "${local.encoded_post_install_script}" | base64 -d > /tmp/post_install.sh
@@ -218,13 +207,6 @@ resource "coder_script" "amazon_q" {
/tmp/post_install.sh
fi
if [ "${var.experiment_report_tasks}" = "true" ]; then
echo "Configuring Amazon Q to report tasks via Coder MCP..."
mkdir -p ~/.aws/amazonq
echo "${local.encoded_mcp_json}" | base64 -d > ~/.aws/amazonq/mcp.json
echo "Created the ~/.aws/amazonq/mcp.json configuration file"
fi
if [ "${var.experiment_use_tmux}" = "true" ] && [ "${var.experiment_use_screen}" = "true" ]; then
echo "Error: Both experiment_use_tmux and experiment_use_screen cannot be true simultaneously."
echo "Please set only one of them to true."
+3 -3
View File
@@ -13,7 +13,7 @@ Run the [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "2.0.3"
version = "2.0.5"
agent_id = coder_agent.example.id
folder = "/home/coder"
install_claude_code = true
@@ -84,7 +84,7 @@ resource "coder_agent" "main" {
module "claude-code" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/claude-code/coder"
version = "2.0.3"
version = "2.0.5"
agent_id = coder_agent.example.id
folder = "/home/coder"
install_claude_code = true
@@ -102,7 +102,7 @@ Run Claude Code as a standalone app in your workspace. This will install Claude
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "2.0.3"
version = "2.0.5"
agent_id = coder_agent.example.id
folder = "/home/coder"
install_claude_code = true
+9 -1
View File
@@ -100,7 +100,7 @@ variable "install_agentapi" {
variable "agentapi_version" {
type = string
description = "The version of AgentAPI to install."
default = "v0.2.3"
default = "v0.3.0"
}
locals {
@@ -289,3 +289,11 @@ resource "coder_ai_task" "claude_code" {
id = coder_app.claude_code_web.id
}
}
# As of https://github.com/coder/coder/commit/6ba4b5bbc95e2e528d7f5b1e31fffa200ae1a6db,
# there's a bug in Coder's Terraform statefile parsing which prevents it from seeing coder_apps
# in certain scenarios. This is a workaround to bypass this bug until we have a proper fix.
# For more details see https://github.com/coder/coder/issues/18776
resource "terraform_data" "claude_code_app_id" {
input = coder_app.claude_code_web.id
}
@@ -15,7 +15,7 @@ The devcontainers-cli module provides an easy way to install [`@devcontainers/cl
```tf
module "devcontainers-cli" {
source = "registry.coder.com/coder/devcontainers-cli/coder"
version = "1.0.31"
version = "1.0.32"
agent_id = coder_agent.example.id
}
```
@@ -45,6 +45,8 @@ const executeScriptInContainerWithPackageManager = async (
console.log(path);
await execContainer(id, [shell, "-c", "mkdir -p /tmp/coder-script-data"]);
const resp = await execContainer(
id,
[shell, "-c", instance.script],
@@ -52,6 +54,8 @@ const executeScriptInContainerWithPackageManager = async (
"--env",
"CODER_SCRIPT_BIN_DIR=/tmp/coder-script-data/bin",
"--env",
"CODER_SCRIPT_DATA_DIR=/tmp/coder-script-data",
"--env",
`PATH=${path}:/tmp/coder-script-data/bin`,
],
);
+7 -1
View File
@@ -1,5 +1,11 @@
#!/usr/bin/env sh
# We want to cd into `$CODER_SCRIPT_DATA_DIR` as the current directory
# might contain a `package.json` with `packageManager` set to something
# other than the detected package manager. When this happens, it can
# cause the installation to fail.
cd "$CODER_SCRIPT_DATA_DIR"
# If @devcontainers/cli is already installed, we can skip
if command -v devcontainer >/dev/null 2>&1; then
echo "🥳 @devcontainers/cli is already installed into $(which devcontainer)!"
@@ -34,7 +40,7 @@ install() {
# so that the devcontainer command is available
if [ -z "$PNPM_HOME" ]; then
PNPM_HOME="$CODER_SCRIPT_BIN_DIR"
export M_HOME
export PNPM_HOME
fi
pnpm add -g @devcontainers/cli
elif [ "$PACKAGE_MANAGER" = "yarn" ]; then
@@ -15,9 +15,9 @@ tags: [integration, vault, hashicorp, hvs]
>
> **Use these Coder registry modules instead:**
>
> - **[vault-token](https://registry.coder.com/modules/vault-token)** - Connect to Vault using access tokens
> - **[vault-jwt](https://registry.coder.com/modules/vault-jwt)** - Connect to Vault using JWT/OIDC authentication
> - **[vault-github](https://registry.coder.com/modules/vault-github)** - Connect to Vault using GitHub authentication
> - **[vault-token](https://registry.coder.com/modules/coder/vault-token)** - Connect to Vault using access tokens
> - **[vault-jwt](https://registry.coder.com/modules/coder/vault-jwt)** - Connect to Vault using JWT/OIDC authentication
> - **[vault-github](https://registry.coder.com/modules/coder/vault-github)** - Connect to Vault using GitHub authentication
>
> These modules work with both self-hosted Vault and HCP Vault Dedicated. For migration help, see the [official HashiCorp announcement](https://developer.hashicorp.com/hcp/docs/vault-secrets/end-of-sale-announcement).
@@ -26,7 +26,7 @@ This module lets you fetch all or selective secrets from a [HCP Vault Secrets](h
```tf
module "vault" {
source = "registry.coder.com/coder/hcp-vault-secrets/coder"
version = "1.0.33"
version = "1.0.34"
agent_id = coder_agent.example.id
app_name = "demo-app"
project_id = "aaa-bbb-ccc"
@@ -52,7 +52,7 @@ To fetch all secrets from the HCP Vault Secrets app, skip the `secrets` input.
```tf
module "vault" {
source = "registry.coder.com/coder/hcp-vault-secrets/coder"
version = "1.0.33"
version = "1.0.34"
agent_id = coder_agent.example.id
app_name = "demo-app"
project_id = "aaa-bbb-ccc"
@@ -66,7 +66,7 @@ To fetch selective secrets from the HCP Vault Secrets app, set the `secrets` inp
```tf
module "vault" {
source = "registry.coder.com/coder/hcp-vault-secrets/coder"
version = "1.0.33"
version = "1.0.34"
agent_id = coder_agent.example.id
app_name = "demo-app"
project_id = "aaa-bbb-ccc"
@@ -81,7 +81,7 @@ Set `client_id` and `client_secret` as module inputs.
```tf
module "vault" {
source = "registry.coder.com/coder/hcp-vault-secrets/coder"
version = "1.0.33"
version = "1.0.34"
agent_id = coder_agent.example.id
app_name = "demo-app"
project_id = "aaa-bbb-ccc"
@@ -16,7 +16,7 @@ A module that adds Jupyter Notebook in your Coder template.
module "jupyter-notebook" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jupyter-notebook/coder"
version = "1.1.1"
version = "1.2.0"
agent_id = coder_agent.example.id
}
```
@@ -48,13 +48,27 @@ variable "group" {
default = null
}
variable "requirements_path" {
type = string
description = "The path to requirements.txt with packages to preinstall"
default = ""
}
variable "pip_install_extra_packages" {
type = string
description = "List of extra packages to preinstall (example: numpy==1.26.4 pandas matplotlib<4 scikit-learn)"
default = ""
}
resource "coder_script" "jupyter-notebook" {
agent_id = var.agent_id
display_name = "jupyter-notebook"
icon = "/icon/jupyter.svg"
script = templatefile("${path.module}/run.sh", {
LOG_PATH : var.log_path,
PORT : var.port
PORT : var.port,
REQUIREMENTS_PATH : var.requirements_path,
PIP_INSTALL_EXTRA_PACKAGES : var.pip_install_extra_packages
})
run_on_start = true
}
@@ -20,6 +20,24 @@ else
echo "🥳 jupyter-notebook is already installed\n\n"
fi
# Install packages selected with REQUIREMENTS_PATH
if [ -n "${REQUIREMENTS_PATH}" ]; then
if [ -f "${REQUIREMENTS_PATH}" ]; then
echo "📄 Installing packages from ${REQUIREMENTS_PATH}..."
pipx -q runpip notebook install -r "${REQUIREMENTS_PATH}"
echo "🥳 Packages from ${REQUIREMENTS_PATH} have been installed\n\n"
else
echo "⚠️ REQUIREMENTS_PATH is set to '${REQUIREMENTS_PATH}' but the file does not exist!\n\n"
fi
fi
# Install packages selected with PIP_INSTALL_EXTRA_PACKAGES
if [ -n "${PIP_INSTALL_EXTRA_PACKAGES}" ]; then
echo "📦 Installing additional packages: ${PIP_INSTALL_EXTRA_PACKAGES}"
pipx -q runpip notebook install ${PIP_INSTALL_EXTRA_PACKAGES}
echo "🥳 Additional packages have been installed\n\n"
fi
echo "👷 Starting jupyter-notebook in background..."
echo "check logs at ${LOG_PATH}"
$HOME/.local/bin/jupyter-notebook --NotebookApp.ip='0.0.0.0' --ServerApp.port=${PORT} --no-browser --ServerApp.token='' --ServerApp.password='' > ${LOG_PATH} 2>&1 &
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+16
View File
@@ -0,0 +1,16 @@
---
display_name: "Eric Paulsen"
bio: "Field CTO, EMEA @ Coder"
avatar_url: "./.images/avatar.png"
github: "ericpaulsen"
linkedin: "https://www.linkedin.com/in/ericpaulsen17" # Optional
website: "https://ericpaulsen.io" # Optional
support_email: "ericpaulsen@hey.com" # Optional
status: "community"
---
# Eric Paulsen
I'm Eric Paulsen, Coder's EMEA Field CTO based in London, originating from Miami.
Outside of working with our customers, I enjoy teaching myself things,
playing volleyball, and dabbling in a bit of DJing & photography.
@@ -0,0 +1,51 @@
---
display_name: Kubernetes (Deployment) with Dynamic Username
description: Provision Kubernetes Deployments as Coder workspaces with your Username
icon: ../../../../.icons/kubernetes.svg
verified: true
tags: [kubernetes, container, username]
---
# Remote development on Kubernetes with dynamic usernames
Provision Kubernetes Pods as [Coder workspaces](https://coder.com/docs/workspaces) with this example template. This template
will run the workspace container as a non-root UID using your Coder username.
Here is the entrypoint logic in the template that enables Coder to source your username and write it to the Ubuntu operating system at start-up.
> These commands may differ if you run your workspace image with a distro other than Ubuntu.
```terraform
command = ["sh", "-c", <<EOF
# Create user and setup home directory
sudo useradd ${data.coder_workspace_owner.me.name} --home=/home/${data.coder_workspace_owner.me.name} --shell=/bin/bash --uid=1001 --user-group
sudo chown -R ${data.coder_workspace_owner.me.name}:${data.coder_workspace_owner.me.name} /home/${data.coder_workspace_owner.me.name}
# Switch to user and run agent
exec sudo --preserve-env=CODER_AGENT_TOKEN -u ${data.coder_workspace_owner.me.name} sh -c '${coder_agent.main.init_script}'
EOF
]
```
<!-- TODO: Add screenshot -->
## Prerequisites
### Infrastructure
**Cluster**: This template requires an existing Kubernetes cluster
**Container Image**: This template uses the [codercom/enterprise-base:ubuntu image](https://github.com/coder/enterprise-images/tree/main/images/base) with some dev tools preinstalled. To add additional tools, extend this image or build it yourself.
### Authentication
This template authenticates using a `~/.kube/config`, if present on the server, or via built-in authentication if the Coder provisioner is running on Kubernetes with an authorized ServiceAccount. To use another [authentication method](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs#authentication), edit the template.
## Architecture
This template provisions the following resources:
- Kubernetes Deployment (ephemeral)
- Kubernetes persistent volume claim (persistent on `/home/${username}`, where `${username}` is your Coder username)
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](https://coder.com/docs/dotfiles) their workspaces with dotfiles.
@@ -0,0 +1,327 @@
terraform {
required_providers {
coder = {
source = "coder/coder"
}
kubernetes = {
source = "hashicorp/kubernetes"
}
}
}
provider "coder" {
}
variable "use_kubeconfig" {
type = bool
description = <<-EOF
Use host kubeconfig? (true/false)
Set this to false if the Coder host is itself running as a Pod on the same
Kubernetes cluster as you are deploying workspaces to.
Set this to true if the Coder host is running outside the Kubernetes cluster
for workspaces. A valid "~/.kube/config" must be present on the Coder host.
EOF
default = false
}
variable "namespace" {
type = string
description = "The Kubernetes namespace to create workspaces in (must exist prior to creating workspaces). If the Coder host is itself running as a Pod on the same Kubernetes cluster as you are deploying workspaces to, set this to the same namespace."
}
data "coder_parameter" "cpu" {
name = "cpu"
display_name = "CPU"
description = "The number of CPU cores"
default = "2"
icon = "/icon/memory.svg"
mutable = true
option {
name = "2 Cores"
value = "2"
}
option {
name = "4 Cores"
value = "4"
}
option {
name = "6 Cores"
value = "6"
}
option {
name = "8 Cores"
value = "8"
}
}
data "coder_parameter" "memory" {
name = "memory"
display_name = "Memory"
description = "The amount of memory in GB"
default = "2"
icon = "/icon/memory.svg"
mutable = true
option {
name = "2 GB"
value = "2"
}
option {
name = "4 GB"
value = "4"
}
option {
name = "6 GB"
value = "6"
}
option {
name = "8 GB"
value = "8"
}
}
data "coder_parameter" "home_disk_size" {
name = "home_disk_size"
display_name = "Home disk size"
description = "The size of the home disk in GB"
default = "10"
type = "number"
icon = "/emojis/1f4be.png"
mutable = false
validation {
min = 1
max = 99999
}
}
provider "kubernetes" {
# Authenticate via ~/.kube/config or a Coder-specific ServiceAccount, depending on admin preferences
config_path = var.use_kubeconfig == true ? "~/.kube/config" : null
}
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
version = "1.3.1"
agent_id = coder_agent.main.id
accept_license = true
}
resource "coder_agent" "main" {
os = "linux"
arch = "amd64"
# The following metadata blocks are optional. They are used to display
# information about your workspace in the dashboard. You can remove them
# if you don't want to display any information.
# For basic resources, you can use the `coder stat` command.
# If you need more control, you can write your own script.
metadata {
display_name = "CPU Usage"
key = "0_cpu_usage"
script = "coder stat cpu"
interval = 10
timeout = 1
}
metadata {
display_name = "RAM Usage"
key = "1_ram_usage"
script = "coder stat mem"
interval = 10
timeout = 1
}
metadata {
display_name = "Home Disk"
key = "3_home_disk"
script = "coder stat disk --path $${HOME}"
interval = 60
timeout = 1
}
metadata {
display_name = "CPU Usage (Host)"
key = "4_cpu_usage_host"
script = "coder stat cpu --host"
interval = 10
timeout = 1
}
metadata {
display_name = "Memory Usage (Host)"
key = "5_mem_usage_host"
script = "coder stat mem --host"
interval = 10
timeout = 1
}
metadata {
display_name = "Load Average (Host)"
key = "6_load_host"
# get load avg scaled by number of cores
script = <<EOT
echo "`cat /proc/loadavg | awk '{ print $1 }'` `nproc`" | awk '{ printf "%0.2f", $1/$2 }'
EOT
interval = 60
timeout = 1
}
}
resource "kubernetes_persistent_volume_claim" "home" {
metadata {
name = "coder-${data.coder_workspace.me.id}-home"
namespace = var.namespace
labels = {
"app.kubernetes.io/name" = "coder-pvc"
"app.kubernetes.io/instance" = "coder-pvc-${data.coder_workspace.me.id}"
"app.kubernetes.io/part-of" = "coder"
//Coder-specific labels.
"com.coder.resource" = "true"
"com.coder.workspace.id" = data.coder_workspace.me.id
"com.coder.workspace.name" = data.coder_workspace.me.name
"com.coder.user.id" = data.coder_workspace_owner.me.id
"com.coder.user.username" = data.coder_workspace_owner.me.name
}
annotations = {
"com.coder.user.email" = data.coder_workspace_owner.me.email
}
}
wait_until_bound = false
spec {
access_modes = ["ReadWriteOnce"]
resources {
requests = {
storage = "${data.coder_parameter.home_disk_size.value}Gi"
}
}
}
}
resource "kubernetes_deployment" "main" {
count = data.coder_workspace.me.start_count
depends_on = [
kubernetes_persistent_volume_claim.home
]
wait_for_rollout = false
metadata {
name = "coder-${data.coder_workspace.me.id}"
namespace = var.namespace
labels = {
"app.kubernetes.io/name" = "coder-workspace"
"app.kubernetes.io/instance" = "coder-workspace-${data.coder_workspace.me.id}"
"app.kubernetes.io/part-of" = "coder"
"com.coder.resource" = "true"
"com.coder.workspace.id" = data.coder_workspace.me.id
"com.coder.workspace.name" = data.coder_workspace.me.name
"com.coder.user.id" = data.coder_workspace_owner.me.id
"com.coder.user.username" = data.coder_workspace_owner.me.name
}
annotations = {
"com.coder.user.email" = data.coder_workspace_owner.me.email
}
}
spec {
replicas = 1
selector {
match_labels = {
"app.kubernetes.io/name" = "coder-workspace"
"app.kubernetes.io/instance" = "coder-workspace-${data.coder_workspace.me.id}"
"app.kubernetes.io/part-of" = "coder"
"com.coder.resource" = "true"
"com.coder.workspace.id" = data.coder_workspace.me.id
"com.coder.workspace.name" = data.coder_workspace.me.name
"com.coder.user.id" = data.coder_workspace_owner.me.id
"com.coder.user.username" = data.coder_workspace_owner.me.name
}
}
strategy {
type = "Recreate"
}
template {
metadata {
labels = {
"app.kubernetes.io/name" = "coder-workspace"
"app.kubernetes.io/instance" = "coder-workspace-${data.coder_workspace.me.id}"
"app.kubernetes.io/part-of" = "coder"
"com.coder.resource" = "true"
"com.coder.workspace.id" = data.coder_workspace.me.id
"com.coder.workspace.name" = data.coder_workspace.me.name
"com.coder.user.id" = data.coder_workspace_owner.me.id
"com.coder.user.username" = data.coder_workspace_owner.me.name
}
}
spec {
container {
name = "dev"
image = "codercom/enterprise-base:ubuntu"
image_pull_policy = "Always"
command = ["sh", "-c", <<EOF
# Create user and setup home directory
sudo useradd ${lower(data.coder_workspace_owner.me.name)} --home=/home/${lower(data.coder_workspace_owner.me.name)} --shell=/bin/bash --uid=1001 --user-group
sudo chown -R ${lower(data.coder_workspace_owner.me.name)}:${lower(data.coder_workspace_owner.me.name)} /home/${lower(data.coder_workspace_owner.me.name)}
# Switch to user and run agent
exec sudo --preserve-env=CODER_AGENT_TOKEN -u ${lower(data.coder_workspace_owner.me.name)} sh -c '${coder_agent.main.init_script}'
EOF
]
env {
name = "CODER_AGENT_TOKEN"
value = coder_agent.main.token
}
resources {
requests = {
"cpu" = "250m"
"memory" = "512Mi"
}
limits = {
"cpu" = "${data.coder_parameter.cpu.value}"
"memory" = "${data.coder_parameter.memory.value}Gi"
}
}
volume_mount {
mount_path = "/home/${lower(data.coder_workspace_owner.me.name)}"
name = "home"
read_only = false
}
}
volume {
name = "home"
persistent_volume_claim {
claim_name = kubernetes_persistent_volume_claim.home.metadata.0.name
read_only = false
}
}
affinity {
// This affinity attempts to spread out all workspace pods evenly across
// nodes.
pod_anti_affinity {
preferred_during_scheduling_ignored_during_execution {
weight = 1
pod_affinity_term {
topology_key = "kubernetes.io/hostname"
label_selector {
match_expressions {
key = "app.kubernetes.io/name"
operator = "In"
values = ["coder-workspace"]
}
}
}
}
}
}
}
}
}
}