-
Notifications
You must be signed in to change notification settings - Fork 161
feat: add coder-labs/coder-cli template for operating a Coder deployment from an agent #1111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
DevelopmentCats
merged 6 commits into
coder:main
from
DevelopmentCats:devcats/coder-cli
Sep 10, 2026
+308
−0
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
bc9cc08
feat: add coder-labs/coder-cli template for operating a Coder deploym…
DevelopmentCats 2974d00
feat: add coder-labs/coder-cli template for operating a Coder deploym…
DevelopmentCats 81d3bea
docs: concisely document the coder-labs/coder-cli template
DevelopmentCats 2fb5ee0
copilot fixes
DevelopmentCats 35bf97a
refactor: make workspace image a variable instead of a parameter
DevelopmentCats 111f467
Merge branch 'main' into devcats/coder-cli
DevelopmentCats File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| --- | ||
| 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: true | ||
| tags: [docker, container, cli, admin, agent] | ||
| --- | ||
|
|
||
| # Coder CLI Workspace | ||
|
|
||
| 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. | ||
|
|
||
| ## Architecture | ||
|
|
||
| Provisions a Docker container with a persistent `/home/coder` volume, plus: | ||
|
|
||
| - `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`. | ||
|
|
||
| ## Using it from an agent | ||
|
|
||
| Once the workspace is up, `coder` is authenticated against the deployment that provisioned it: | ||
|
|
||
| ```bash | ||
| coder templates list | ||
| coder workspaces list | ||
| coder templates push <name> --directory . | ||
| coder ssh <workspace> | ||
| ``` | ||
|
|
||
| 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. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,271 @@ | ||
| terraform { | ||
| required_providers { | ||
| coder = { | ||
| source = "coder/coder" | ||
| } | ||
| docker = { | ||
| source = "kreuzwerker/docker" | ||
| } | ||
| } | ||
| } | ||
|
|
||
| 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" {} | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Parameters | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| 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" "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 | ||
| } | ||
|
DevelopmentCats marked this conversation as resolved.
|
||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # 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 <name> --directory . | ||
| coder workspaces list | ||
| coder ssh <workspace> | ||
| ──────────────────────────────────────────────────────────────── | ||
| 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/.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" | ||
| } | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Docker volume + container | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| resource "docker_volume" "home" { | ||
| name = "coder-${data.coder_workspace.me.id}-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" { | ||
| name = var.image | ||
| } | ||
|
|
||
| resource "docker_image" "workspace" { | ||
| name = var.image | ||
| 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", replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal")] | ||
| 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 | ||
| } | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.