From bc9cc0837e0684a793b6cc56ea428a6ca02278c3 Mon Sep 17 00:00:00 2001 From: DevCats <176868952+DevelopmentCats@users.noreply.github.com> Date: Thu, 10 Sep 2026 15:59:18 +0000 Subject: [PATCH 1/5] feat: add coder-labs/coder-cli template for operating a Coder deployment from an agent --- .../coder-labs/templates/coder-cli/README.md | 72 +++++ .../coder-labs/templates/coder-cli/main.tf | 286 ++++++++++++++++++ 2 files changed, 358 insertions(+) create mode 100644 registry/coder-labs/templates/coder-cli/README.md create mode 100644 registry/coder-labs/templates/coder-cli/main.tf diff --git a/registry/coder-labs/templates/coder-cli/README.md b/registry/coder-labs/templates/coder-cli/README.md new file mode 100644 index 000000000..603d9f85f --- /dev/null +++ b/registry/coder-labs/templates/coder-cli/README.md @@ -0,0 +1,72 @@ +--- +display_name: Coder CLI Workspace +description: A pre-authenticated Coder CLI workspace for operating a Coder deployment from the agent +icon: ../../../../.icons/coder.svg +verified: false +tags: [docker, container, cli, admin, agent] +--- + +# Coder CLI Workspace + +A portable Coder workspace for operating an existing Coder deployment +from the CLI. Built on the `codercom/oss-dogfood` image, so the workspace +ships with `coder`, `terraform`, `git`, `gh`, the `docker` CLI, Node, Go, +and Python out of the box. The workspace owner is auto-logged into the +CLI via the `coder/coder-login` registry module — no session tokens to +paste, no unauthenticated `coder` commands. + +This makes the template a natural fit for AI agents (Claude Code, etc.) +that need to manage a deployment on your behalf: the agent gets a +signed-in `coder` CLI plus the `coder-templates` and `coder-modules` +skills wired into `~/.claude/skills/`, ready to push templates, manage +workspaces, and inspect users or orgs. + +## Prerequisites + +- A Docker-capable provisioner on the target deployment. +- Outbound network access for the workspace to pull the dogfood image + and the registry modules. + +## Architecture + +The template provisions a Docker volume (`docker_volume.home`) mounted at +`/home/coder` with `lifecycle { ignore_changes = all }` so your home +directory, SSH keys, and CLI config persist across restarts. The container +is started with the Coder agent init script and grants access to the host +Docker daemon via `host.docker.internal` (host gateway). + +Modules pulled in on every start: + +- `coder-login` — signs the workspace owner into the Coder CLI for the + deployment the workspace lives on. +- `code-server` — browser-based editor rooted at `~/projects`. +- `git-clone` — optional; clones `git_repo_url` into `~/projects` on + first start. +- `claude-code` — optional; installs the Claude Code CLI (see below). + +## Using it from an agent + +Once the workspace is up, `coder` is authenticated against the deployment +that provisioned it. From an agent or a shell you can run, for example: + +```bash +coder templates list +coder workspaces list +coder users list +coder templates push --directory . +coder ssh +``` + +The `install_registry_skills` parameter (on by default) clones +`coder/registry` into `~/registry` and symlinks the `coder-templates` and +`coder-modules` agent skills into `~/.claude/skills/`, so an agent +authoring templates or modules from this workspace picks up the registry +contribution conventions automatically. + +## Claude Code + +Setting `enable_claude_code = true` installs the Claude Code CLI via the +official module. The module requires exactly one authentication method — +edit the `claude-code` module block in `main.tf` to pass +`anthropic_api_key`, `claude_code_oauth_token`, `enable_ai_gateway = true` +(Coder AI Gateway, Premium), or an override base URL. diff --git a/registry/coder-labs/templates/coder-cli/main.tf b/registry/coder-labs/templates/coder-cli/main.tf new file mode 100644 index 000000000..60dec1f6e --- /dev/null +++ b/registry/coder-labs/templates/coder-cli/main.tf @@ -0,0 +1,286 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + } + docker = { + source = "kreuzwerker/docker" + } + } +} + +provider "coder" {} +provider "docker" {} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} +data "coder_provisioner" "me" {} + +# --------------------------------------------------------------------------- +# Parameters +# --------------------------------------------------------------------------- + +data "coder_parameter" "image" { + name = "image" + display_name = "Workspace image" + description = "The base image for the workspace. Any image that ships the Coder CLI (or can have it installed) works; the dogfood image includes coder, terraform, git, gh, the docker CLI, Node, Go, and Python out of the box." + type = "string" + default = "codercom/oss-dogfood:latest" + mutable = false + option { + name = "Ubuntu 22.04 (oss-dogfood:latest)" + value = "codercom/oss-dogfood:latest" + icon = "/icon/coder.svg" + } + option { + name = "Ubuntu 26.04 (oss-dogfood:26.04)" + value = "codercom/oss-dogfood:26.04" + icon = "/icon/coder.svg" + } + option { + name = "Nix dogfood (experimental)" + value = "codercom/oss-dogfood-nix:latest" + icon = "/icon/nix.svg" + } +} + +data "coder_parameter" "cpu" { + name = "cpu" + display_name = "CPU cores" + type = "number" + default = "2" + mutable = true + validation { + min = 1 + max = 16 + } +} + +data "coder_parameter" "memory_gb" { + name = "memory_gb" + display_name = "Memory (GB)" + type = "number" + default = "4" + mutable = true + validation { + min = 1 + max = 64 + } +} + +data "coder_parameter" "git_repo_url" { + name = "git_repo_url" + display_name = "Git repository to clone (optional)" + description = "If set, cloned into ~/projects on first start. Leave blank to skip." + type = "string" + default = "" + mutable = true +} + +data "coder_parameter" "enable_claude_code" { + name = "enable_claude_code" + display_name = "Install Claude Code" + description = "Install the Claude Code CLI via the official module. The module requires exactly one authentication method; edit the module block in main.tf to pass anthropic_api_key, claude_code_oauth_token, enable_ai_gateway = true (Coder AI Gateway, Premium), or an override base URL." + type = "bool" + default = "false" + mutable = true +} + +data "coder_parameter" "install_registry_skills" { + name = "install_registry_skills" + display_name = "Install coder/registry agent skills" + description = <<-EOT + Clone coder/registry into ~/registry and symlink the coder-templates + and coder-modules agent skills into ~/.claude/skills/ so agents + (Claude Code, etc.) authoring registry content from this workspace + pick them up automatically. Disable if you don't need them. + EOT + type = "bool" + default = "true" + mutable = true +} + +# --------------------------------------------------------------------------- +# Coder agent +# --------------------------------------------------------------------------- + +resource "coder_agent" "main" { + arch = data.coder_provisioner.me.arch + os = "linux" + + startup_script_behavior = "blocking" + + env = { + INSTALL_REGISTRY_SKILLS = data.coder_parameter.install_registry_skills.value + } + + startup_script = <<-EOT + set -eu + mkdir -p "$HOME/projects" + # Friendly banner on every new shell + cat > "$HOME/.coder-welcome" <<'BANNER' + ──────────────────────────────────────────────────────────────── + Coder CLI workspace (dogfood image) + ──────────────────────────────────────────────────────────────── + You are auto-logged-in via the coder-login module. + coder templates list + coder templates push --directory . + coder workspaces list + coder ssh + ──────────────────────────────────────────────────────────────── + BANNER + if ! grep -q '.coder-welcome' "$HOME/.bashrc" 2>/dev/null; then + echo 'cat $HOME/.coder-welcome 2>/dev/null || true' >> "$HOME/.bashrc" + fi + + # ---- Install coder/registry agent skills ------------------------------ + # Clone coder/registry once and symlink the in-tree skills into + # ~/.claude/skills/ so any agent in this workspace (Claude Code, etc.) + # picks them up when authoring new templates or modules. + if [ "$INSTALL_REGISTRY_SKILLS" = "true" ]; then + if [ ! -d "$HOME/registry/.git" ]; then + echo "[coder-cli] Cloning coder/registry for agent skills ..." + git clone --depth=1 https://github.com/coder/registry.git "$HOME/registry" 2>&1 | tail -3 || true + fi + mkdir -p "$HOME/.claude/skills" + for skill in coder-templates coder-modules; do + src="$HOME/registry/.agents/skills/$skill" + if [ -f "$src/SKILL.md" ]; then + ln -sfn "$src" "$HOME/.claude/skills/$skill" + echo "[coder-cli] linked agent skill: $skill" + else + echo "[coder-cli] WARNING: skill source missing at $src" >&2 + fi + done + fi + EOT + + metadata { + display_name = "CPU usage" + key = "cpu_usage" + script = "coder stat cpu" + interval = 10 + timeout = 1 + } + metadata { + display_name = "Memory usage" + key = "mem_usage" + script = "coder stat mem" + interval = 10 + timeout = 1 + } + metadata { + display_name = "Home disk" + key = "home_disk" + script = "coder stat disk --path $HOME" + interval = 60 + timeout = 1 + } + metadata { + display_name = "Registry skills" + key = "registry_skills" + script = "ls /home/coder/.claude/skills 2>/dev/null | tr '\\n' ' ' | sed 's/ $//' || echo 'none'" + interval = 60 + timeout = 2 + } +} + +# --------------------------------------------------------------------------- +# Registry modules +# --------------------------------------------------------------------------- + +module "coder-login" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/coder-login/coder" + version = "1.1.1" + agent_id = coder_agent.main.id +} + +module "git-clone" { + count = data.coder_workspace.me.start_count != 0 && data.coder_parameter.git_repo_url.value != "" ? 1 : 0 + source = "registry.coder.com/coder/git-clone/coder" + version = "2.0.3" + agent_id = coder_agent.main.id + url = data.coder_parameter.git_repo_url.value + base_dir = "/home/coder/projects" +} + +module "code-server" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/code-server/coder" + version = "1.5.2" + agent_id = coder_agent.main.id + folder = "/home/coder/projects" +} + +module "claude-code" { + count = data.coder_workspace.me.start_count != 0 && data.coder_parameter.enable_claude_code.value ? 1 : 0 + source = "registry.coder.com/coder/claude-code/coder" + version = "5.4.1" + agent_id = coder_agent.main.id + workdir = "/home/coder/projects" +} + +# --------------------------------------------------------------------------- +# Docker volume + container +# --------------------------------------------------------------------------- + +resource "docker_volume" "home" { + name = "coder-${data.coder_workspace.me.id}-home" + lifecycle { + ignore_changes = all + } +} + +data "docker_registry_image" "workspace" { + name = data.coder_parameter.image.value +} + +resource "docker_image" "workspace" { + name = data.coder_parameter.image.value + pull_triggers = [data.docker_registry_image.workspace.sha256_digest] + keep_locally = true +} + +resource "docker_container" "workspace" { + count = data.coder_workspace.me.start_count + image = docker_image.workspace.image_id + name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" + hostname = lower(data.coder_workspace.me.name) + + entrypoint = ["sh", "-c", coder_agent.main.init_script] + env = [ + "CODER_AGENT_TOKEN=${coder_agent.main.token}", + ] + + cpu_shares = data.coder_parameter.cpu.value * 1024 + memory = data.coder_parameter.memory_gb.value * 1024 + + host { + host = "host.docker.internal" + ip = "host-gateway" + } + + volumes { + container_path = "/home/coder" + volume_name = docker_volume.home.name + read_only = false + } + + labels { + label = "coder.owner" + value = data.coder_workspace_owner.me.name + } + labels { + label = "coder.owner_id" + value = data.coder_workspace_owner.me.id + } + labels { + label = "coder.workspace_id" + value = data.coder_workspace.me.id + } + labels { + label = "coder.workspace_name" + value = data.coder_workspace.me.name + } +} From 2974d00297fac3887c6d0b78a0e81f7ed4e911eb Mon Sep 17 00:00:00 2001 From: DevCats <176868952+DevelopmentCats@users.noreply.github.com> Date: Thu, 10 Sep 2026 20:45:57 +0000 Subject: [PATCH 2/5] feat: add coder-labs/coder-cli template for operating a Coder deployment from an agent --- .../coder-labs/templates/coder-cli/README.md | 9 --------- registry/coder-labs/templates/coder-cli/main.tf | 17 ----------------- 2 files changed, 26 deletions(-) diff --git a/registry/coder-labs/templates/coder-cli/README.md b/registry/coder-labs/templates/coder-cli/README.md index 603d9f85f..197e43404 100644 --- a/registry/coder-labs/templates/coder-cli/README.md +++ b/registry/coder-labs/templates/coder-cli/README.md @@ -42,7 +42,6 @@ Modules pulled in on every start: - `code-server` — browser-based editor rooted at `~/projects`. - `git-clone` — optional; clones `git_repo_url` into `~/projects` on first start. -- `claude-code` — optional; installs the Claude Code CLI (see below). ## Using it from an agent @@ -62,11 +61,3 @@ The `install_registry_skills` parameter (on by default) clones `coder-modules` agent skills into `~/.claude/skills/`, so an agent authoring templates or modules from this workspace picks up the registry contribution conventions automatically. - -## Claude Code - -Setting `enable_claude_code = true` installs the Claude Code CLI via the -official module. The module requires exactly one authentication method — -edit the `claude-code` module block in `main.tf` to pass -`anthropic_api_key`, `claude_code_oauth_token`, `enable_ai_gateway = true` -(Coder AI Gateway, Premium), or an override base URL. diff --git a/registry/coder-labs/templates/coder-cli/main.tf b/registry/coder-labs/templates/coder-cli/main.tf index 60dec1f6e..ba1af85e8 100644 --- a/registry/coder-labs/templates/coder-cli/main.tf +++ b/registry/coder-labs/templates/coder-cli/main.tf @@ -77,15 +77,6 @@ data "coder_parameter" "git_repo_url" { mutable = true } -data "coder_parameter" "enable_claude_code" { - name = "enable_claude_code" - display_name = "Install Claude Code" - description = "Install the Claude Code CLI via the official module. The module requires exactly one authentication method; edit the module block in main.tf to pass anthropic_api_key, claude_code_oauth_token, enable_ai_gateway = true (Coder AI Gateway, Premium), or an override base URL." - type = "bool" - default = "false" - mutable = true -} - data "coder_parameter" "install_registry_skills" { name = "install_registry_skills" display_name = "Install coder/registry agent skills" @@ -213,14 +204,6 @@ module "code-server" { folder = "/home/coder/projects" } -module "claude-code" { - count = data.coder_workspace.me.start_count != 0 && data.coder_parameter.enable_claude_code.value ? 1 : 0 - source = "registry.coder.com/coder/claude-code/coder" - version = "5.4.1" - agent_id = coder_agent.main.id - workdir = "/home/coder/projects" -} - # --------------------------------------------------------------------------- # Docker volume + container # --------------------------------------------------------------------------- From 81d3bea3f33c01e8008bfa7f52f0a565027ec774 Mon Sep 17 00:00:00 2001 From: DevCats <176868952+DevelopmentCats@users.noreply.github.com> Date: Thu, 10 Sep 2026 20:49:34 +0000 Subject: [PATCH 3/5] docs: concisely document the coder-labs/coder-cli template --- .../coder-labs/templates/coder-cli/README.md | 42 ++++--------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/registry/coder-labs/templates/coder-cli/README.md b/registry/coder-labs/templates/coder-cli/README.md index 197e43404..99a129443 100644 --- a/registry/coder-labs/templates/coder-cli/README.md +++ b/registry/coder-labs/templates/coder-cli/README.md @@ -2,62 +2,36 @@ display_name: Coder CLI Workspace description: A pre-authenticated Coder CLI workspace for operating a Coder deployment from the agent icon: ../../../../.icons/coder.svg -verified: false +verified: true tags: [docker, container, cli, admin, agent] --- # Coder CLI Workspace -A portable Coder workspace for operating an existing Coder deployment -from the CLI. Built on the `codercom/oss-dogfood` image, so the workspace -ships with `coder`, `terraform`, `git`, `gh`, the `docker` CLI, Node, Go, -and Python out of the box. The workspace owner is auto-logged into the -CLI via the `coder/coder-login` registry module — no session tokens to -paste, no unauthenticated `coder` commands. - -This makes the template a natural fit for AI agents (Claude Code, etc.) -that need to manage a deployment on your behalf: the agent gets a -signed-in `coder` CLI plus the `coder-templates` and `coder-modules` -skills wired into `~/.claude/skills/`, ready to push templates, manage -workspaces, and inspect users or orgs. +A portable Docker workspace for operating an existing Coder deployment from the CLI. Built on `codercom/oss-dogfood`, the workspace ships the `coder` CLI pre-authenticated via the `coder/coder-login` module, so you can manage templates, workspaces, users, and orgs against the deployment that provisioned it without pasting session tokens. ## Prerequisites - A Docker-capable provisioner on the target deployment. -- Outbound network access for the workspace to pull the dogfood image - and the registry modules. +- Outbound network access for the workspace to pull the dogfood image and the registry modules. ## Architecture -The template provisions a Docker volume (`docker_volume.home`) mounted at -`/home/coder` with `lifecycle { ignore_changes = all }` so your home -directory, SSH keys, and CLI config persist across restarts. The container -is started with the Coder agent init script and grants access to the host -Docker daemon via `host.docker.internal` (host gateway). - -Modules pulled in on every start: +Provisions a Docker container with a persistent `/home/coder` volume, plus: -- `coder-login` — signs the workspace owner into the Coder CLI for the - deployment the workspace lives on. +- `coder-login` — signs the workspace owner into the Coder CLI. - `code-server` — browser-based editor rooted at `~/projects`. -- `git-clone` — optional; clones `git_repo_url` into `~/projects` on - first start. +- `git-clone` — optional; clones `git_repo_url` into `~/projects`. ## Using it from an agent -Once the workspace is up, `coder` is authenticated against the deployment -that provisioned it. From an agent or a shell you can run, for example: +Once the workspace is up, `coder` is authenticated against the deployment that provisioned it: ```bash coder templates list coder workspaces list -coder users list coder templates push --directory . coder ssh ``` -The `install_registry_skills` parameter (on by default) clones -`coder/registry` into `~/registry` and symlinks the `coder-templates` and -`coder-modules` agent skills into `~/.claude/skills/`, so an agent -authoring templates or modules from this workspace picks up the registry -contribution conventions automatically. +The `install_registry_skills` parameter (on by default) clones `coder/registry` and links the `coder-templates` and `coder-modules` agent skills into `~/.claude/skills/` for agents authoring registry content. From 2fb5ee0c7370a31cdf1d5df0f18cb81168273940 Mon Sep 17 00:00:00 2001 From: DevCats <176868952+DevelopmentCats@users.noreply.github.com> Date: Thu, 10 Sep 2026 20:59:16 +0000 Subject: [PATCH 4/5] copilot fixes --- .../coder-labs/templates/coder-cli/main.tf | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/registry/coder-labs/templates/coder-cli/main.tf b/registry/coder-labs/templates/coder-cli/main.tf index ba1af85e8..be433cb78 100644 --- a/registry/coder-labs/templates/coder-cli/main.tf +++ b/registry/coder-labs/templates/coder-cli/main.tf @@ -170,7 +170,7 @@ resource "coder_agent" "main" { metadata { display_name = "Registry skills" key = "registry_skills" - script = "ls /home/coder/.claude/skills 2>/dev/null | tr '\\n' ' ' | sed 's/ $//' || echo 'none'" + script = "ls $HOME/.claude/skills 2>/dev/null | tr '\\n' ' ' | sed 's/ $//' || echo 'none'" interval = 60 timeout = 2 } @@ -213,6 +213,22 @@ resource "docker_volume" "home" { lifecycle { ignore_changes = all } + labels { + label = "coder.owner" + value = data.coder_workspace_owner.me.name + } + labels { + label = "coder.owner_id" + value = data.coder_workspace_owner.me.id + } + labels { + label = "coder.workspace_id" + value = data.coder_workspace.me.id + } + labels { + label = "coder.workspace_name_at_creation" + value = data.coder_workspace.me.name + } } data "docker_registry_image" "workspace" { @@ -231,7 +247,7 @@ resource "docker_container" "workspace" { name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" hostname = lower(data.coder_workspace.me.name) - entrypoint = ["sh", "-c", coder_agent.main.init_script] + entrypoint = ["sh", "-c", replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal")] env = [ "CODER_AGENT_TOKEN=${coder_agent.main.token}", ] From 35bf97a5271d5d933037e4aefeef78f844cad3cf Mon Sep 17 00:00:00 2001 From: DevCats <176868952+DevelopmentCats@users.noreply.github.com> Date: Thu, 10 Sep 2026 21:13:49 +0000 Subject: [PATCH 5/5] refactor: make workspace image a variable instead of a parameter --- .../coder-labs/templates/coder-cli/main.tf | 38 ++++++------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/registry/coder-labs/templates/coder-cli/main.tf b/registry/coder-labs/templates/coder-cli/main.tf index be433cb78..b3d69a1d3 100644 --- a/registry/coder-labs/templates/coder-cli/main.tf +++ b/registry/coder-labs/templates/coder-cli/main.tf @@ -12,6 +12,16 @@ terraform { provider "coder" {} provider "docker" {} +variable "image" { + description = <<-EOF + Container image for the workspace. Must include the Coder CLI; the + dogfood image ships coder, terraform, git, gh, the docker CLI, Node, + Go, and Python out of the box. + EOF + type = string + default = "codercom/oss-dogfood:latest" +} + data "coder_workspace" "me" {} data "coder_workspace_owner" "me" {} data "coder_provisioner" "me" {} @@ -20,30 +30,6 @@ data "coder_provisioner" "me" {} # Parameters # --------------------------------------------------------------------------- -data "coder_parameter" "image" { - name = "image" - display_name = "Workspace image" - description = "The base image for the workspace. Any image that ships the Coder CLI (or can have it installed) works; the dogfood image includes coder, terraform, git, gh, the docker CLI, Node, Go, and Python out of the box." - type = "string" - default = "codercom/oss-dogfood:latest" - mutable = false - option { - name = "Ubuntu 22.04 (oss-dogfood:latest)" - value = "codercom/oss-dogfood:latest" - icon = "/icon/coder.svg" - } - option { - name = "Ubuntu 26.04 (oss-dogfood:26.04)" - value = "codercom/oss-dogfood:26.04" - icon = "/icon/coder.svg" - } - option { - name = "Nix dogfood (experimental)" - value = "codercom/oss-dogfood-nix:latest" - icon = "/icon/nix.svg" - } -} - data "coder_parameter" "cpu" { name = "cpu" display_name = "CPU cores" @@ -232,11 +218,11 @@ resource "docker_volume" "home" { } data "docker_registry_image" "workspace" { - name = data.coder_parameter.image.value + name = var.image } resource "docker_image" "workspace" { - name = data.coder_parameter.image.value + name = var.image pull_triggers = [data.docker_registry_image.workspace.sha256_digest] keep_locally = true }