From 718e6f9b768b97364e843107aa5727fb06e6f0d1 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Mon, 23 Jun 2025 17:40:43 +0000 Subject: [PATCH 01/36] remove ea feature status --- docs/manifest.json | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/docs/manifest.json b/docs/manifest.json index 5fbb98f94b006..2f991296fb922 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -294,19 +294,16 @@ "description": "Run containerized development environments in your Coder workspace using the dev containers specification.", "path": "./user-guides/devcontainers/index.md", "icon_path": "./images/icons/container.svg", - "state": ["early access"], "children": [ { "title": "Working with dev containers", "description": "Access dev containers via SSH, your IDE, or web terminal.", - "path": "./user-guides/devcontainers/working-with-dev-containers.md", - "state": ["early access"] + "path": "./user-guides/devcontainers/working-with-dev-containers.md" }, { "title": "Troubleshooting dev containers", "description": "Diagnose and resolve common issues with dev containers in your Coder workspace.", - "path": "./user-guides/devcontainers/troubleshooting-dev-containers.md", - "state": ["early access"] + "path": "./user-guides/devcontainers/troubleshooting-dev-containers.md" } ] }, @@ -606,8 +603,7 @@ { "title": "Configure a template for dev containers", "description": "How to use configure your template for dev containers", - "path": "./admin/templates/extending-templates/devcontainers.md", - "state": ["early access"] + "path": "./admin/templates/extending-templates/devcontainers.md" }, { "title": "Process Logging", From ba7a36c4ae5e0a378ac2de198beacd3c463eb3e8 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Tue, 24 Jun 2025 18:32:26 +0000 Subject: [PATCH 02/36] add advanced dev container doc --- .../advanced-dev-containers.md | 575 ++++++++++++++++++ docs/manifest.json | 9 +- 2 files changed, 583 insertions(+), 1 deletion(-) create mode 100644 docs/admin/templates/extending-templates/advanced-dev-containers.md diff --git a/docs/admin/templates/extending-templates/advanced-dev-containers.md b/docs/admin/templates/extending-templates/advanced-dev-containers.md new file mode 100644 index 0000000000000..d7dfca08d6b3a --- /dev/null +++ b/docs/admin/templates/extending-templates/advanced-dev-containers.md @@ -0,0 +1,575 @@ +# Advanced dev container configuration + +This guide covers advanced configurations for [dev containers](./devcontainers.md) in [Coder templates](../index.md). + +## Multiple dev containers + +Run multiple dev containers in a single workspace for microservices or multi-component development: + +```terraform +# Frontend dev container + +resource "coder_devcontainer" "frontend" { + count = data.coder_workspace.me.start_count + agent_id = coder_agent.dev.id + workspace_folder = "/home/coder/frontend" +} + +# Backend dev container + +resource "coder_devcontainer" "backend" { + count = data.coder_workspace.me.start_count + agent_id = coder_agent.dev.id + workspace_folder = "/home/coder/backend" +} + +# Database dev container + +resource "coder_devcontainer" "database" { + count = data.coder_workspace.me.start_count + agent_id = coder_agent.dev.id + workspace_folder = "/home/coder/database" +} + +# Clone multiple repositories + +module "git-clone-frontend" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/modules/git-clone/coder" + version = "~> 1.0" + + agent_id = coder_agent.dev.id + url = "https://github.com/your-org/frontend.git" + path = "/home/coder/frontend" +} + +module "git-clone-backend" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/modules/git-clone/coder" + version = "~> 1.0" + + agent_id = coder_agent.dev.id + url = "https://github.com/your-org/backend.git" + path = "/home/coder/backend" +} +``` + +Each dev container will appear as a separate agent in the Coder UI, allowing developers to connect to different environments within the same workspace. + +## Conditional startup with user control + +Allow users to selectively enable dev containers: + +```terraform +data "coder_parameter" "enable_frontend" { + type = "bool" + name = "Enable frontend dev container" + default = true + description = "Start the frontend dev container automatically" + mutable = true + order = 3 +} + +data "coder_parameter" "enable_backend" { + type = "bool" + name = "Enable backend dev container" + default = true + description = "Start the backend dev container automatically" + mutable = true + order = 4 +} + +resource "coder_devcontainer" "frontend" { + count = data.coder_parameter.enable_frontend.value ? data.coder_workspace.me.start_count : 0 + agent_id = coder_agent.dev.id + workspace_folder = "/home/coder/frontend" +} + +resource "coder_devcontainer" "backend" { + count = data.coder_parameter.enable_backend.value ? data.coder_workspace.me.start_count : 0 + agent_id = coder_agent.dev.id + workspace_folder = "/home/coder/backend" +} +``` + +## Repository selection patterns + +### Dropdown with predefined projects + +```terraform +data "coder_parameter" "project" { + name = "Project" + description = "Select a project to work on" + type = "string" + mutable = true + order = 1 + + option { + name = "E-commerce Frontend" + description = "React-based e-commerce frontend" + value = "https://github.com/your-org/ecommerce-frontend.git" + icon = "/icon/react.svg" + } + + option { + name = "Payment Service" + description = "Node.js payment processing service" + value = "https://github.com/your-org/payment-service.git" + icon = "/icon/nodejs.svg" + } + + option { + name = "User Management API" + description = "Python user management API" + value = "https://github.com/your-org/user-api.git" + icon = "/icon/python.svg" + } +} + +module "git-clone" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/modules/git-clone/coder" + version = "~> 1.0" + + agent_id = coder_agent.dev.id + url = data.coder_parameter.project.value + path = "/home/coder/project" +} +``` + +### Team-based repository access + +```terraform +data "coder_parameter" "team" { + name = "Team" + description = "Select your team" + type = "string" + mutable = true + order = 1 + + option { + name = "Frontend Team" + value = "frontend" + icon = "/icon/frontend.svg" + } + + option { + name = "Backend Team" + value = "backend" + icon = "/icon/backend.svg" + } + + option { + name = "DevOps Team" + value = "devops" + icon = "/icon/devops.svg" + } +} + +locals { + team_repos = { + frontend = [ + "https://github.com/your-org/web-app.git", + "https://github.com/your-org/mobile-app.git" + ] + backend = [ + "https://github.com/your-org/api-service.git", + "https://github.com/your-org/auth-service.git" + ] + devops = [ + "https://github.com/your-org/infrastructure.git", + "https://github.com/your-org/monitoring.git" + ] + } +} + +module "git-clone-team-repos" { + count = length(local.team_repos[data.coder_parameter.team.value]) * data.coder_workspace.me.start_count + source = "registry.coder.com/modules/git-clone/coder" + version = "~> 1.0" + + agent_id = coder_agent.dev.id + url = local.team_repos[data.coder_parameter.team.value][count.index % length(local.team_repos[data.coder_parameter.team.value])] + path = "/home/coder/repos/$sename(local.team_repos[data.coder_parameter.team.value][count.index % length(local.team_repos[data.coder_parameter.team.value])])}" +} +``` + +## Advanced infrastructure configurations + +### Resource limits and monitoring + +```terraform +resource "docker_container" "workspace" { + count = data.coder_workspace.me.start_count + +# ... other configuration + +# Set resource limits + + memory = 4096 # 4GB + cpus = 2.0 # 2 CPU cores + +# Enable swap accounting + + memory_swap = 8192 # 8GB including swap +} + +resource "coder_agent" "dev" { + +# ... other configuration + +# Monitor dev container status + + metadata { + display_name = "Dev Containers" + key = "devcontainers" + script = "docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' | grep -v NAMES || echo 'No dev containers running'" + interval = 30 + timeout = 5 + } + + metadata { + display_name = "Docker Resource Usage" + key = "docker_resources" + script = "docker stats --no-stream --format 'table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}' | head -5" + interval = 60 + timeout = 10 + } +} +``` + +### Custom Docker networks + +```terraform +resource "docker_network" "dev_network" { + name = "coder-$ta.coder_workspace.me.id}-dev" +} + +resource "docker_container" "workspace" { + count = data.coder_workspace.me.start_count + +# ... other configuration + + networks_advanced { + name = docker_network.dev_network.name + } +} +``` + +### Volume management for performance + +```terraform +resource "docker_volume" "node_modules" { + name = "coder-$ta.coder_workspace.me.id}-node-modules" + lifecycle { + ignore_changes = all + } +} + +resource "docker_volume" "go_cache" { + name = "coder-$ta.coder_workspace.me.id}-go-cache" + lifecycle { + ignore_changes = all + } +} + +resource "docker_container" "workspace" { + count = data.coder_workspace.me.start_count + +# ... other configuration + +# Persist node_modules for faster installs + + volumes { + container_path = "/home/coder/project/node_modules" + volume_name = docker_volume.node_modules.name + } + +# Persist Go module cache + + volumes { + container_path = "/home/coder/go/pkg/mod" + volume_name = docker_volume.go_cache.name + } +} +``` + +## Dev container template example + +You can test the Coder dev container integration and features with this example template. + +Example template infrastructure requirements: + +- Docker host with Docker daemon and socket available at `/var/run/docker.sock`. +- Network access to pull `codercom/enterprise-base:ubuntu` image. +- Access to Coder registry modules at `dev.registry.coder.com` and `registry.coder.com`. + +Customization needed: + +- Replace the default repository URL with your preferred repository. +- Adjust volume paths if your setup differs from `/home/coder`. +- Modify resource limits if needed for your workloads. + +
Expand for the example template: + +```terraform +terraform { + required_providers { + coder = { + source = "coder/coder" + version = "~> 2.5" + } + docker = { + source = "kreuzwerker/docker" + version = "~> 3.0" + } + } +} + +provider "coder" {} +provider "docker" {} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} +data "coder_provisioner" "me" {} + +data "coder_parameter" "repo_url" { + name = "Repository URL" + description = "Git repository with devcontainer.json" + type = "string" + default = "https://github.com/microsoft/vscode-remote-try-node.git" + mutable = true + order = 1 +} + +data "coder_parameter" "enable_devcontainer" { + type = "bool" + name = "Enable dev container" + default = true + description = "Automatically start the dev container" + mutable = true + order = 2 +} + +# Create persistent volume for home directory + +resource "docker_volume" "home_volume" { + name = "coder-$ta.coder_workspace.me.id}-home" + lifecycle { + ignore_changes = all + } +} + +# Main workspace container + +resource "docker_container" "workspace" { + count = data.coder_workspace.me.start_count + image = "codercom/enterprise-base:ubuntu" + name = "coder-$ta.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" + +# Hostname makes the shell more user friendly + + hostname = data.coder_workspace.me.name + +# Required environment variables + + env = [ + "CODER_AGENT_TOKEN=${coder_agent.dev.token}", + "CODER_AGENT_DEVCONTAINERS_ENABLE=true", + ] + +# Mount home directory for persistence + + volumes { + container_path = "/home/coder" + volume_name = docker_volume.home_volume.name + read_only = false + } + +# Mount Docker socket for dev container support + + volumes { + container_path = "/var/run/docker.sock" + host_path = "/var/run/docker.sock" + read_only = false + } + +# Use the Docker host gateway for Coder access + + host { + host = "host.docker.internal" + ip = "host-gateway" + } +} + +# Coder agent + +resource "coder_agent" "dev" { + arch = data.coder_provisioner.me.arch + os = "linux" + dir = "/home/coder" + + startup_script_behavior = "blocking" + startup_script = <<-EOT + set -e + + # Start Docker service + sudo service docker start + + # Wait for Docker to be ready + timeout 60 bash -c 'until docker info >/dev/null 2>&1; do sleep 1; done' + + echo "Workspace ready!" + EOT + + shutdown_script = <<-EOT + sudo service docker stop + EOT + +# Git configuration + + env = { + GIT_AUTHOR_NAME = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name) + GIT_AUTHOR_EMAIL = data.coder_workspace_owner.me.email + GIT_COMMITTER_NAME = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name) + GIT_COMMITTER_EMAIL = data.coder_workspace_owner.me.email + } +} + +# Install devcontainers CLI + +module "devcontainers-cli" { + count = data.coder_workspace.me.start_count + source = "dev.registry.coder.com/modules/devcontainers-cli/coder" + agent_id = coder_agent.dev.id +} + +# Clone repository + +module "git-clone" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/modules/git-clone/coder" + version = "~> 1.0" + + agent_id = coder_agent.dev.id + url = data.coder_parameter.repo_url.value + path = "/home/coder/project" +} + +# Auto-start dev container + +resource "coder_devcontainer" "project" { + count = data.coder_parameter.enable_devcontainer.value ? data.coder_workspace.me.start_count : 0 + agent_id = coder_agent.dev.id + workspace_folder = "/home/coder/project" +} + +# Add code-server for web-based development + +module "code-server" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/modules/code-server/coder" + version = "~> 1.0" + agent_id = coder_agent.dev.id + order = 1 +} +``` + +
+ +## Troubleshooting and debugging + +You can troubleshoot dev container issues within a template or use the +[troubleshooting dev containers](../../../user-guides/devcontainers/troubleshooting-dev-containers.md) +documentation for issues that affect dev containers within user workspaces. + +### Debug startup issues + +```terraform +resource "coder_agent" "dev" { + +# ... other configuration + + startup_script = <<-EOT + set -e + + # Enable debug logging + exec > >(tee -a /tmp/startup.log) 2>&1 + echo "=== Startup Debug Log $(date) ===" + + # Start Docker service with verbose logging + sudo service docker start + + # Wait for Docker and log status + echo "Waiting for Docker daemon..." + timeout 60 bash -c 'until docker info >/dev/null 2>&1; do + echo "Docker not ready, waiting..." + sleep 2 + done' + + echo "Docker daemon ready!" + docker version + + # Log dev container status + echo "=== Dev Container Status ===" + docker ps -a + + echo "Workspace startup complete!" + EOT +} +``` + +### Add health checks and monitoring + +```terraform +resource "coder_agent" "dev" { + +# ... other configuration + + metadata { + display_name = "Docker Service Status" + key = "docker_status" + script = "systemctl is-active docker || echo 'Docker service not running'" + interval = 30 + timeout = 5 + } + + metadata { + display_name = "Dev Container Health" + key = "devcontainer_health" + script = <<-EOT + containers=$(docker ps --filter "label=devcontainer.local_folder" --format "{{.Names}}") + if [ -z "$containers" ]; then + echo "No dev containers running" + else + echo "Running: $containers" + fi + EOT + interval = 60 + timeout = 10 + } +} +``` + +### Dev containers fail to start + +1. Check Docker service status: + + ```shell + systemctl status docker + ``` + +1. Verify Docker socket permissions. +1. Review startup logs in `/tmp/startup.log`. + +### Poor performance with multiple containers + +1. Implement volume caching for package managers. +1. Set appropriate resource limits. +1. Use Docker networks for container communication. + +### Repository access problems + +1. Verify Git credentials are configured. +1. Check network connectivity to repository hosts. +1. Ensure repository URLs are accessible from the workspace. diff --git a/docs/manifest.json b/docs/manifest.json index 2f991296fb922..c7227120a3510 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -603,7 +603,14 @@ { "title": "Configure a template for dev containers", "description": "How to use configure your template for dev containers", - "path": "./admin/templates/extending-templates/devcontainers.md" + "path": "./admin/templates/extending-templates/devcontainers.md", + "children": [ + { + "title": "Advanced dev container configurations", + "description": "Enhance your dev container configurations with multiple containers, repo selection, monitoring, and more.", + "path": "./admin/templates/extending-templates/advanced-dev-containers.md" + } + ] }, { "title": "Process Logging", From ef8a4eafb0f56369e52ccb3adec30e5417cee626 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Wed, 25 Jun 2025 14:49:37 +0000 Subject: [PATCH 03/36] update devcontainers --- .../advanced-dev-containers.md | 16 + .../extending-templates/devcontainers.md | 282 +++++++++++++++--- 2 files changed, 251 insertions(+), 47 deletions(-) diff --git a/docs/admin/templates/extending-templates/advanced-dev-containers.md b/docs/admin/templates/extending-templates/advanced-dev-containers.md index d7dfca08d6b3a..08e337483e436 100644 --- a/docs/admin/templates/extending-templates/advanced-dev-containers.md +++ b/docs/admin/templates/extending-templates/advanced-dev-containers.md @@ -56,6 +56,22 @@ module "git-clone-backend" { Each dev container will appear as a separate agent in the Coder UI, allowing developers to connect to different environments within the same workspace. +## Add a Personal devcontainer.json alongside a repository-specific one + +Keep a canonical `devcontainer.json` in the repo, then let each developer add an +untracked `devcontainer.local.json` (or another file referenced via `"extends"`). + +```jsonc +// devcontainer.local.json (ignored by Git) +{ + "extends": "./devcontainer.json", + "features": { + "ghcr.io/devcontainers/features/node:1": { "version": "20" } + }, + "postStartCommand": "npm i -g tldr" +} +``` + ## Conditional startup with user control Allow users to selectively enable dev containers: diff --git a/docs/admin/templates/extending-templates/devcontainers.md b/docs/admin/templates/extending-templates/devcontainers.md index d4284bf48efde..3a4b09ee8a062 100644 --- a/docs/admin/templates/extending-templates/devcontainers.md +++ b/docs/admin/templates/extending-templates/devcontainers.md @@ -1,13 +1,50 @@ # Configure a template for dev containers -To enable dev containers in workspaces, configure your template with the dev containers +Dev containers provide a consistent, reproducible development environment using the +[Development Containers specification](https://containers.dev/). +Coder's dev container support allows developers to work in fully configured environments with their preferred tools and extensions. + +To enable dev containers in workspaces, [configure your template](../creating-templates.md) with the dev containers modules and configurations outlined in this doc. +## Why use dev containers + +Dev containers improve consistency across environments by letting developers define their development setup. +When integrated with Coder templates, they provide: + +- **Project-specific environments**: Each repository can define its own tools, extensions, and configuration. +- **Zero setup time**: Developers get fully configured environments without manual installation. +- **Consistency across teams**: Everyone works in identical environments regardless of their local machine. +- **Version control**: Development environment changes are tracked alongside code changes. + +## Prerequisites + +Dev containers require Docker to build and run containers. +Ensure your workspace infrastructure has Docker configured with container creation permissions and sufficient resources. + +To confirm that Docker is configured correctly, create a test workspace and confirm that `docker ps` runs. +If it doesn't, follow the steps in [Docker in workspaces](./docker-in-workspaces.md). + +## Enable Dev Containers Integration + +To enable the dev containers integration in your workspace, add the `CODER_AGENT_DEVCONTAINERS_ENABLE` environment variable to your existing `coder_agent` block: + +```terraform +env = { + CODER_AGENT_DEVCONTAINERS_ENABLE = "true" + # existing variables ... +} +``` + +This environment variable is required for the Coder agent to detect and manage dev containers. +Without it, the agent will not attempt to start or connect to dev containers even if the +`coder_devcontainer` resource is defined. + ## Install the Dev Containers CLI Use the [devcontainers-cli](https://registry.coder.com/modules/devcontainers-cli) module -to ensure the `@devcontainers/cli` is installed in your workspace: +to install `@devcontainers/cli` in your workspace: ```terraform module "devcontainers-cli" { @@ -17,7 +54,44 @@ module "devcontainers-cli" { } ``` -Alternatively, install the devcontainer CLI manually in your base image. +Alternatively, install the devcontainer CLI manually in your base image: + +```shell +RUN npm install -g @devcontainers/cli +``` + +## Define the dev container resource + +Point the resource at the folder that contains `devcontainer.json`: + +```terraform +resource "coder_devcontainer" "project" { + count = data.coder_workspace.me.start_count + agent_id = coder_agent.dev.id + workspace_folder = "/home/coder/project" +} +``` + +## Clone the repository + +This step is optional, but it ensures that the project is present before the dev container starts. + +```terraform +module "git_clone" { + count = data.coder_workspace.me.start_count + source = "dev.registry.coder.com/modules/git-clone/coder" + agent_id = coder_agent.dev.id + url = "https://github.com/example/project.git" + path = "/home/coder/project" +} + +resource "coder_devcontainer" "project" { + count = data.coder_workspace.me.start_count + agent_id = coder_agent.dev.id + workspace_folder = module.git_clone[0].path + depends_on = [module.git_clone] +} +``` ## Configure Automatic Dev Container Startup @@ -34,46 +108,67 @@ resource "coder_devcontainer" "my-repository" { } ``` -> [!NOTE] -> -> The `workspace_folder` attribute must specify the location of the dev -> container's workspace and should point to a valid project folder containing a -> `devcontainer.json` file. +The `workspace_folder` attribute must specify the location of the dev container's workspace and should point to a +valid project folder that contains a `devcontainer.json` file. - +## Dev container features -> [!TIP] -> -> Consider using the [`git-clone`](https://registry.coder.com/modules/git-clone) -> module to ensure your repository is cloned into the workspace folder and ready -> for automatic startup. +Enhance your dev container experience with additional features. +For more advanced use cases, consult the [advanced dev containers doc](./advanced-dev-containers.md). -## Enable Dev Containers Integration +### Custom applications + +```jsonc +{ + "customizations": { + "coder": { + "apps": { + "flask-app": { + "command": "python app.py", + "icon": "/icon/flask.svg", + "subdomain": true, + "healthcheck": { + "url": "http://localhost:5000/health", + "interval": 10, + "threshold": 10 + } + } + } + } + } +} +``` + +### Agent naming + +Coder names dev container agents in this order: -To enable the dev containers integration in your workspace, you must set the -`CODER_AGENT_DEVCONTAINERS_ENABLE` environment variable to `true` in your -workspace container: +1. `customizations.coder.agent.name` in `devcontainer.json` +2. `name` in `devcontainer.json` +3. Directory name that contains the config +4. `devcontainer` (default) + +### Multiple dev containers ```terraform -resource "docker_container" "workspace" { - count = data.coder_workspace.me.start_count - image = "codercom/oss-dogfood:latest" - env = [ - "CODER_AGENT_DEVCONTAINERS_ENABLE=true", - # ... Other environment variables. - ] - # ... Other container configuration. +resource "coder_devcontainer" "frontend" { + count = data.coder_workspace.me.start_count + agent_id = coder_agent.dev.id + workspace_folder = "/home/coder/frontend" +} + +resource "coder_devcontainer" "backend" { + count = data.coder_workspace.me.start_count + agent_id = coder_agent.dev.id + workspace_folder = "/home/coder/backend" } ``` -This environment variable is required for the Coder agent to detect and manage -dev containers. Without it, the agent will not attempt to start or connect to -dev containers even if the `coder_devcontainer` resource is defined. +## Complete Template Examples -## Complete Template Example +You can test the Coder dev container integration and features with these starter templates. -Here's a simplified template example that enables the dev containers -integration: +
Docker-based template (privileged) ```terraform terraform { @@ -83,44 +178,137 @@ terraform { } } -provider "coder" {} data "coder_workspace" "me" {} data "coder_workspace_owner" "me" {} resource "coder_agent" "dev" { - arch = "amd64" - os = "linux" + os = "linux" + arch = "amd64" + env = { CODER_AGENT_DEVCONTAINERS_ENABLE = "true" } + startup_script_behavior = "blocking" - startup_script = "sudo service docker start" - shutdown_script = "sudo service docker stop" - # ... + startup_script = "sudo service docker start" + shutdown_script = "sudo service docker stop" } -module "devcontainers-cli" { +module "devcontainers_cli" { count = data.coder_workspace.me.start_count source = "dev.registry.coder.com/modules/devcontainers-cli/coder" agent_id = coder_agent.dev.id } -resource "coder_devcontainer" "my-repository" { +module "git_clone" { + count = data.coder_workspace.me.start_count + source = "dev.registry.coder.com/modules/git-clone/coder" + agent_id = coder_agent.dev.id + url = "https://github.com/example/project.git" + path = "/home/coder/project" +} + +resource "coder_devcontainer" "project" { count = data.coder_workspace.me.start_count agent_id = coder_agent.dev.id - workspace_folder = "/home/coder/my-repository" + workspace_folder = module.git_clone[0].path + depends_on = [module.git_clone] } resource "docker_container" "workspace" { + count = data.coder_workspace.me.start_count + image = "codercom/enterprise-base:ubuntu" + name = "coder-$ta.coder_workspace_owner.me.name}-$ta.coder_workspace.me.name}" + privileged = true # or mount /var/run/docker.sock +} +``` + +
+ +
Kubernetes-based template (Sysbox runtime) + +```terraform +terraform { + required_providers { + coder = { source = "coder/coder" } + kubernetes = { source = "hashicorp/kubernetes" } + } +} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + env = { CODER_AGENT_DEVCONTAINERS_ENABLE = "true" } + + startup_script_behavior = "blocking" + startup_script = "sudo service docker start" +} + +module "devcontainers_cli" { + count = data.coder_workspace.me.start_count + source = "dev.registry.coder.com/modules/devcontainers-cli/coder" + agent_id = coder_agent.dev.id +} + +module "git_clone" { + count = data.coder_workspace.me.start_count + source = "dev.registry.coder.com/modules/git-clone/coder" + agent_id = coder_agent.dev.id + url = "https://github.com/example/project.git" + path = "/home/coder/project" +} + +resource "coder_devcontainer" "project" { + count = data.coder_workspace.me.start_count + agent_id = coder_agent.dev.id + workspace_folder = module.git_clone[0].path + depends_on = [module.git_clone] +} + +resource "kubernetes_pod" "workspace" { count = data.coder_workspace.me.start_count - image = "codercom/oss-dogfood:latest" - env = [ - "CODER_AGENT_DEVCONTAINERS_ENABLE=true", - # ... Other environment variables. - ] - # ... Other container configuration. + + metadata { + name = "coder-$ta.coder_workspace_owner.me.name}-$ta.coder_workspace.me.name}" + namespace = "coder-workspaces" + } + + spec { + container { + name = "dev" + image = "codercom/enterprise-base:ubuntu" + + security_context { privileged = true } # or use Sysbox / rootless + env { name = "CODER_AGENT_TOKEN" value = coder_agent.dev.token } + } + } } ``` + + +## Troubleshoot common issues + +### Dev container does not start + +1. `CODER_AGENT_DEVCONTAINERS_ENABLE=true` missing. +1. Docker daemon not running inside the workspace. +1. `devcontainer.json` missing or mislocated. +1. Build errors: check agent logs. + +### Permission errors + +- Docker socket not mounted or user lacks access. +- Workspace not `privileged` and no rootless runtime. + +### Slow builds + +- Allocate more CPU/RAM. +- Use image caching or pre-build common images. + ## Next Steps +- [Advanced dev containers](./advanced-dev-containers.md) - [Dev Containers Integration](../../../user-guides/devcontainers/index.md) - [Working with Dev Containers](../../../user-guides/devcontainers/working-with-dev-containers.md) - [Troubleshooting Dev Containers](../../../user-guides/devcontainers/troubleshooting-dev-containers.md) From f4db3da966691e026d02f3c994f80b405e3550b3 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Wed, 25 Jun 2025 19:41:13 +0000 Subject: [PATCH 04/36] new workspace screenshot --- .../workspace-running-with-topbar.png | Bin 58980 -> 64145 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/images/screenshots/workspace-running-with-topbar.png b/docs/images/screenshots/workspace-running-with-topbar.png index ab3f6a78a9e6eb35b269cae377c0beb810e3280a..0af0b6527e7583c3f274fa8485ed4c29aeb058c0 100644 GIT binary patch literal 64145 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 NP}BK4_b-bl{{x%hb?5*9 literal 58980 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
-Xkf+#CEFrVgWJf z`<+vCzHF6AHkrj*Az0Di9x%KG7^_q@sR_-vEaqCoS9vdJ_>ShwZIP>OYZ-q?VD|&N z+M-?8*C%)reEqmK(L0WVKbsl>T#u4q?wn=7^ho%n7v*lNGjR_Mt?Tp3JpJ%yj=34t z_nMRU6s=n9(x~VSEHKHBnBBMDec|bP$QkuJfSo*lvsL_Hk2ELiR6#f38}yJFz=7c! zkIV5p0$o&^EA)M5EOog=tcGYzIitjlf=XUq{_9Dd+i@FPAh82JnJY`WD#(v6hP~}T zhu78H9IL>otMeA!-0jnQVh70c{Aw)8(Z`Qv?CjDuWu&u|S}uu&`rfrI-lH6tpZEK2 zeKZ~R2gsLQgohsZ&&9fv>Ytp(@)G4RS(Qq@I`(@~1%y^Lyl;R$UW8Uo)oPQTC(3M) zsX&1bhkQNw!24u|R@(km3f-2&_&79b>u5zlKQO2XKcOJVAz*(!NBj$;?oo*SJ~m;0 zI36ob!@2bHUEqrQerlx+jt!~D-3dMT$M>OS+i)h-b13im%f(iY`axGpS72-MipYrc zT5TWLydv(DE@cFbqaG5{oePK4Z@=A_3_79Q2M>Mv)wj{am?W1rXzP8+@|kv z*lc2THmfFS<1k((Zr%|e(XwjR>Nd-EP*a`Y(z4)x{6xe%*bQ>;R{UME8TtfSDfes~vJfnIl6EQCka8uf4?mmFOH)Y7OFtj#tPf zG7e<73YCg)5MQYTP-%5avYE-$K9zuhhN9qf0?XoIG#om*NlY%PV85Z$MU|LT=r|mXOUq=C!}N15 zr)7DkBiX66zCRoc@ID_7M;*3K<$8jC-%cGliPt!! z73udg*S_@WsvB>Z$wpv5C9of@Z2rfp@x;RB)#l0}=F35#A;Zlg=vV=0d-y#lo*UfF z$)cht3B6f`=i?RIeb^(S+7Vh%2+B;a>xzBAYmw%@L&t)sN5EKG{g`6OZ}S&O9PXNC zxhnjM5;n+GGfNy7Tv;o;QxG?O5a_ER%Ga8*%2&V1Y|&l`uyZbs!UH(oTP0cdr^EbN zNANGV(3rxsZL?VxGX?rub8Ga@S{S8h8Egg}z2v|Q=sOswc*fZ8L6rc zC<5-+mM?>-z`pPMg4VvhJ|A|VQmK>5G^`NOmxBDzmc!Ta7`<_hBD-I`|E|%&mCgOI z0p4^0dgM)JW@dX@EtK43v&x~IGM>hr{VKVG=*RQJ^l_oix|pKx4pUsc3hh4N*kEwo zGK$$!r8&8zbbJWb`{HwZ_Lj8CSc;*D)!aAE-2P9j^!&NO zLl|$_GW_|YuuO^}cUjpVc6%fquXc@Q3gkHqn$4$8rX$%5L_%p)(a}#Uz}&{dinJpM z*d82++8s?#WXMOnfaQEDXe4i2(%X9Bq0aqUCzpoqGSwBg-R-FW0*i#dDp@AxFs9>` zQez|qg)wpMBq?YC1#>-Hz>nB%+=ac#Dy6ar-I#2EEOO6H0L&3*Pf$qY%n5qA+O*z- zms%H}i`?Q22i9!gE!2W<^E@k}#&6N=6O8?{__B zdM-RNyyg0HJ6&*`&rE6PI8{SKliBuIGGr8=IJ*qL9IQUOZ(BZPvFHuZYN69f{Qj)< zK_t9ng9{2$A~tA35JvgM&2@sDADi-SQEgb=GZki>Yw4k|akc)Ow|qF?`{nXI$7U5O z@5^A#eEwk_5Llc#;2?rTUwyM zAkO}F>2}?wR?G9{R*ErT7d-t;zyECbc6%;$c~`^`zA|cp9Sdstp{o

Hzw@;YXR; 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 z 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 u=HVT}oxGor7GJJZ@DfDm6%B}|h From df0719631bc49dccfc7b6a60fb2d4d25da334357 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Thu, 26 Jun 2025 18:59:45 +0000 Subject: [PATCH 05/36] consistent and working examples --- .../extending-templates/devcontainers.md | 113 ++++++++++-------- 1 file changed, 61 insertions(+), 52 deletions(-) diff --git a/docs/admin/templates/extending-templates/devcontainers.md b/docs/admin/templates/extending-templates/devcontainers.md index 3a4b09ee8a062..cd31c42333fd3 100644 --- a/docs/admin/templates/extending-templates/devcontainers.md +++ b/docs/admin/templates/extending-templates/devcontainers.md @@ -19,11 +19,35 @@ When integrated with Coder templates, they provide: ## Prerequisites -Dev containers require Docker to build and run containers. -Ensure your workspace infrastructure has Docker configured with container creation permissions and sufficient resources. +- Dev containers require Docker to build and run containers inside the workspace. -To confirm that Docker is configured correctly, create a test workspace and confirm that `docker ps` runs. -If it doesn't, follow the steps in [Docker in workspaces](./docker-in-workspaces.md). + Ensure your workspace infrastructure has Docker configured with container creation permissions and sufficient resources. + + To confirm that Docker is configured correctly, create a test workspace and confirm that `docker ps` runs. + If it doesn't, follow the steps in [Docker in workspaces](./docker-in-workspaces.md). + +- The `devcontainers-cli` module requires npm. + + - Use an image that already includes npm, such as `codercom/enterprise-node:ubuntu` + -

If your template doesn't already include npm, install it at runtime with the `nodejs` module: + + 1. This block should be before the `devcontainers-cli` block in `main.tf`: + + ```terraform + module "nodejs" { + count = data.coder_workspace.me.start_count + source = "dev.registry.coder.com/modules/nodejs/coder" + agent_id = coder_agent.main.id + } + ``` + + 1. Add `depends_on` to the `devcontainers-cli` module block: + + ```terraform + depends_on = [module.nodejs] + ``` + +
## Enable Dev Containers Integration @@ -50,7 +74,7 @@ to install `@devcontainers/cli` in your workspace: module "devcontainers-cli" { count = data.coder_workspace.me.start_count source = "dev.registry.coder.com/modules/devcontainers-cli/coder" - agent_id = coder_agent.dev.id + agent_id = coder_agent.main.id } ``` @@ -62,12 +86,12 @@ RUN npm install -g @devcontainers/cli ## Define the dev container resource -Point the resource at the folder that contains `devcontainer.json`: +If you don't use [`git_clone`](#clone-the-repository), point the resource at the folder that contains `devcontainer.json`: ```terraform resource "coder_devcontainer" "project" { count = data.coder_workspace.me.start_count - agent_id = coder_agent.dev.id + agent_id = coder_agent.main.id workspace_folder = "/home/coder/project" } ``` @@ -76,41 +100,26 @@ resource "coder_devcontainer" "project" { This step is optional, but it ensures that the project is present before the dev container starts. +Note that if you use the `git_clone` module, place it before the `coder_devcontainer` resource +and update or replace that resource to point at `/home/coder/project/${module.git_clone[0].folder_name}` so that it is only defined once: + ```terraform module "git_clone" { count = data.coder_workspace.me.start_count source = "dev.registry.coder.com/modules/git-clone/coder" - agent_id = coder_agent.dev.id + agent_id = coder_agent.main.id url = "https://github.com/example/project.git" - path = "/home/coder/project" + base_dir = "/home/coder/project" } resource "coder_devcontainer" "project" { count = data.coder_workspace.me.start_count - agent_id = coder_agent.dev.id - workspace_folder = module.git_clone[0].path + agent_id = coder_agent.main.id + workspace_folder = "/home/coder/project/${module.git_clone[0].folder_name}" depends_on = [module.git_clone] } ``` -## Configure Automatic Dev Container Startup - -The -[`coder_devcontainer`](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/devcontainer) -resource automatically starts a dev container in your workspace, ensuring it's -ready when you access the workspace: - -```terraform -resource "coder_devcontainer" "my-repository" { - count = data.coder_workspace.me.start_count - agent_id = coder_agent.dev.id - workspace_folder = "/home/coder/my-repository" -} -``` - -The `workspace_folder` attribute must specify the location of the dev container's workspace and should point to a -valid project folder that contains a `devcontainer.json` file. - ## Dev container features Enhance your dev container experience with additional features. @@ -144,22 +153,22 @@ For more advanced use cases, consult the [advanced dev containers doc](./advance Coder names dev container agents in this order: 1. `customizations.coder.agent.name` in `devcontainer.json` -2. `name` in `devcontainer.json` -3. Directory name that contains the config -4. `devcontainer` (default) +1. `name` in `devcontainer.json` +1. Directory name that contains the config +1. `devcontainer` (default) ### Multiple dev containers ```terraform resource "coder_devcontainer" "frontend" { count = data.coder_workspace.me.start_count - agent_id = coder_agent.dev.id + agent_id = coder_agent.main.id workspace_folder = "/home/coder/frontend" } resource "coder_devcontainer" "backend" { count = data.coder_workspace.me.start_count - agent_id = coder_agent.dev.id + agent_id = coder_agent.main.id workspace_folder = "/home/coder/backend" } ``` @@ -181,7 +190,7 @@ terraform { data "coder_workspace" "me" {} data "coder_workspace_owner" "me" {} -resource "coder_agent" "dev" { +resource "coder_agent" "main" { os = "linux" arch = "amd64" env = { CODER_AGENT_DEVCONTAINERS_ENABLE = "true" } @@ -194,28 +203,28 @@ resource "coder_agent" "dev" { module "devcontainers_cli" { count = data.coder_workspace.me.start_count source = "dev.registry.coder.com/modules/devcontainers-cli/coder" - agent_id = coder_agent.dev.id + agent_id = coder_agent.main.id } module "git_clone" { count = data.coder_workspace.me.start_count source = "dev.registry.coder.com/modules/git-clone/coder" - agent_id = coder_agent.dev.id + agent_id = coder_agent.main.id url = "https://github.com/example/project.git" - path = "/home/coder/project" + base_dir = "/home/coder/project" } resource "coder_devcontainer" "project" { count = data.coder_workspace.me.start_count - agent_id = coder_agent.dev.id - workspace_folder = module.git_clone[0].path + agent_id = coder_agent.main.id + workspace_folder = "/home/coder/project/${module.git_clone[0].folder_name}" depends_on = [module.git_clone] } resource "docker_container" "workspace" { count = data.coder_workspace.me.start_count - image = "codercom/enterprise-base:ubuntu" - name = "coder-$ta.coder_workspace_owner.me.name}-$ta.coder_workspace.me.name}" + image = "codercom/enterprise-node:ubuntu" + name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" privileged = true # or mount /var/run/docker.sock } ``` @@ -235,7 +244,7 @@ terraform { data "coder_workspace" "me" {} data "coder_workspace_owner" "me" {} -resource "coder_agent" "dev" { +resource "coder_agent" "main" { os = "linux" arch = "amd64" env = { CODER_AGENT_DEVCONTAINERS_ENABLE = "true" } @@ -247,21 +256,21 @@ resource "coder_agent" "dev" { module "devcontainers_cli" { count = data.coder_workspace.me.start_count source = "dev.registry.coder.com/modules/devcontainers-cli/coder" - agent_id = coder_agent.dev.id + agent_id = coder_agent.main.id } module "git_clone" { count = data.coder_workspace.me.start_count source = "dev.registry.coder.com/modules/git-clone/coder" - agent_id = coder_agent.dev.id + agent_id = coder_agent.main.id url = "https://github.com/example/project.git" - path = "/home/coder/project" + base_dir = "/home/coder/project" } resource "coder_devcontainer" "project" { count = data.coder_workspace.me.start_count - agent_id = coder_agent.dev.id - workspace_folder = module.git_clone[0].path + agent_id = coder_agent.main.id + workspace_folder = "/home/coder/project/${module.git_clone[0].folder_name}" depends_on = [module.git_clone] } @@ -269,23 +278,23 @@ resource "kubernetes_pod" "workspace" { count = data.coder_workspace.me.start_count metadata { - name = "coder-$ta.coder_workspace_owner.me.name}-$ta.coder_workspace.me.name}" + name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" namespace = "coder-workspaces" } spec { container { - name = "dev" + name = "main" image = "codercom/enterprise-base:ubuntu" security_context { privileged = true } # or use Sysbox / rootless - env { name = "CODER_AGENT_TOKEN" value = coder_agent.dev.token } + env { name = "CODER_AGENT_TOKEN" value = coder_agent.main.token } } } } ``` - +
## Troubleshoot common issues From 18998b70f933f8a8320dd98e2630b1f748bce7f6 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Thu, 26 Jun 2025 19:59:44 +0000 Subject: [PATCH 06/36] edits; prep advanced doc --- .../advanced-dev-containers.md | 64 +++++++++---------- .../extending-templates/devcontainers.md | 2 +- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/admin/templates/extending-templates/advanced-dev-containers.md b/docs/admin/templates/extending-templates/advanced-dev-containers.md index 08e337483e436..6e721ec350423 100644 --- a/docs/admin/templates/extending-templates/advanced-dev-containers.md +++ b/docs/admin/templates/extending-templates/advanced-dev-containers.md @@ -11,24 +11,24 @@ Run multiple dev containers in a single workspace for microservices or multi-com resource "coder_devcontainer" "frontend" { count = data.coder_workspace.me.start_count - agent_id = coder_agent.dev.id - workspace_folder = "/home/coder/frontend" + agent_id = coder_agent.main.id + workspace_folder = "/home/coder/frontend/${module.git_clone_frontend[0].folder_name}" } # Backend dev container resource "coder_devcontainer" "backend" { count = data.coder_workspace.me.start_count - agent_id = coder_agent.dev.id - workspace_folder = "/home/coder/backend" + agent_id = coder_agent.main.id + workspace_folder = "/home/coder/backend/${module.git_clone_frontend[0].folder_name}" } # Database dev container resource "coder_devcontainer" "database" { count = data.coder_workspace.me.start_count - agent_id = coder_agent.dev.id - workspace_folder = "/home/coder/database" + agent_id = coder_agent.main.id + workspace_folder = "/home/coder/database/${module.git_clone_frontend[0].folder_name}" } # Clone multiple repositories @@ -38,9 +38,9 @@ module "git-clone-frontend" { source = "registry.coder.com/modules/git-clone/coder" version = "~> 1.0" - agent_id = coder_agent.dev.id + agent_id = coder_agent.main.id url = "https://github.com/your-org/frontend.git" - path = "/home/coder/frontend" + base_dir = "/home/coder/frontend/${module.git_clone_frontend[0].folder_name}" } module "git-clone-backend" { @@ -48,9 +48,9 @@ module "git-clone-backend" { source = "registry.coder.com/modules/git-clone/coder" version = "~> 1.0" - agent_id = coder_agent.dev.id + agent_id = coder_agent.main.id url = "https://github.com/your-org/backend.git" - path = "/home/coder/backend" + base_dir = "/home/coder/backend/${module.git_clone_frontend[0].folder_name}" } ``` @@ -97,14 +97,14 @@ data "coder_parameter" "enable_backend" { resource "coder_devcontainer" "frontend" { count = data.coder_parameter.enable_frontend.value ? data.coder_workspace.me.start_count : 0 - agent_id = coder_agent.dev.id - workspace_folder = "/home/coder/frontend" + agent_id = coder_agent.main.id + workspace_folder = "/home/coder/frontend/${module.git_clone_frontend[0].folder_name}" } resource "coder_devcontainer" "backend" { count = data.coder_parameter.enable_backend.value ? data.coder_workspace.me.start_count : 0 - agent_id = coder_agent.dev.id - workspace_folder = "/home/coder/backend" + agent_id = coder_agent.main.id + workspace_folder = "/home/coder/backend/${module.git_clone_frontend[0].folder_name}" } ``` @@ -147,9 +147,9 @@ module "git-clone" { source = "registry.coder.com/modules/git-clone/coder" version = "~> 1.0" - agent_id = coder_agent.dev.id + agent_id = coder_agent.main.id url = data.coder_parameter.project.value - path = "/home/coder/project" + base_dir = "/home/coder/project" } ``` @@ -204,9 +204,9 @@ module "git-clone-team-repos" { source = "registry.coder.com/modules/git-clone/coder" version = "~> 1.0" - agent_id = coder_agent.dev.id + agent_id = coder_agent.main.id url = local.team_repos[data.coder_parameter.team.value][count.index % length(local.team_repos[data.coder_parameter.team.value])] - path = "/home/coder/repos/$sename(local.team_repos[data.coder_parameter.team.value][count.index % length(local.team_repos[data.coder_parameter.team.value])])}" + base_dir = "/home/coder/repos/$sename(local.team_repos[data.coder_parameter.team.value][count.index % length(local.team_repos[data.coder_parameter.team.value])])}" } ``` @@ -230,7 +230,7 @@ resource "docker_container" "workspace" { memory_swap = 8192 # 8GB including swap } -resource "coder_agent" "dev" { +resource "coder_agent" "main" { # ... other configuration @@ -258,7 +258,7 @@ resource "coder_agent" "dev" { ```terraform resource "docker_network" "dev_network" { - name = "coder-$ta.coder_workspace.me.id}-dev" + name = "coder-${data.coder_workspace.me.id}-dev } resource "docker_container" "workspace" { @@ -276,14 +276,14 @@ resource "docker_container" "workspace" { ```terraform resource "docker_volume" "node_modules" { - name = "coder-$ta.coder_workspace.me.id}-node-modules" + name = "coder-${data.coder_workspace.me.id}-node-modules" lifecycle { ignore_changes = all } } resource "docker_volume" "go_cache" { - name = "coder-$ta.coder_workspace.me.id}-go-cache" + name = "coder-${data.coder_workspace.me.id}-go-cache" lifecycle { ignore_changes = all } @@ -370,7 +370,7 @@ data "coder_parameter" "enable_devcontainer" { # Create persistent volume for home directory resource "docker_volume" "home_volume" { - name = "coder-$ta.coder_workspace.me.id}-home" + name = "coder-${data.coder_workspace.me.id}-home" lifecycle { ignore_changes = all } @@ -381,7 +381,7 @@ resource "docker_volume" "home_volume" { resource "docker_container" "workspace" { count = data.coder_workspace.me.start_count image = "codercom/enterprise-base:ubuntu" - name = "coder-$ta.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" + name = "coder-${data.coder_workspace.me.name}-${lower(data.coder_workspace.me.name)}" # Hostname makes the shell more user friendly @@ -420,7 +420,7 @@ resource "docker_container" "workspace" { # Coder agent -resource "coder_agent" "dev" { +resource "coder_agent" "main" { arch = data.coder_provisioner.me.arch os = "linux" dir = "/home/coder" @@ -457,7 +457,7 @@ resource "coder_agent" "dev" { module "devcontainers-cli" { count = data.coder_workspace.me.start_count source = "dev.registry.coder.com/modules/devcontainers-cli/coder" - agent_id = coder_agent.dev.id + agent_id = coder_agent.main.id } # Clone repository @@ -467,16 +467,16 @@ module "git-clone" { source = "registry.coder.com/modules/git-clone/coder" version = "~> 1.0" - agent_id = coder_agent.dev.id + agent_id = coder_agent.main.id url = data.coder_parameter.repo_url.value - path = "/home/coder/project" + base_dir = "/home/coder/project" } # Auto-start dev container resource "coder_devcontainer" "project" { count = data.coder_parameter.enable_devcontainer.value ? data.coder_workspace.me.start_count : 0 - agent_id = coder_agent.dev.id + agent_id = coder_agent.main.id workspace_folder = "/home/coder/project" } @@ -486,7 +486,7 @@ module "code-server" { count = data.coder_workspace.me.start_count source = "registry.coder.com/modules/code-server/coder" version = "~> 1.0" - agent_id = coder_agent.dev.id + agent_id = coder_agent.main.id order = 1 } ``` @@ -502,7 +502,7 @@ documentation for issues that affect dev containers within user workspaces. ### Debug startup issues ```terraform -resource "coder_agent" "dev" { +resource "coder_agent" "main" { # ... other configuration @@ -538,7 +538,7 @@ resource "coder_agent" "dev" { ### Add health checks and monitoring ```terraform -resource "coder_agent" "dev" { +resource "coder_agent" "main" { # ... other configuration diff --git a/docs/admin/templates/extending-templates/devcontainers.md b/docs/admin/templates/extending-templates/devcontainers.md index cd31c42333fd3..e75e650b378f6 100644 --- a/docs/admin/templates/extending-templates/devcontainers.md +++ b/docs/admin/templates/extending-templates/devcontainers.md @@ -41,7 +41,7 @@ When integrated with Coder templates, they provide: } ``` - 1. Add `depends_on` to the `devcontainers-cli` module block: + 1. In the `devcontainers-cli` module block, add: ```terraform depends_on = [module.nodejs] From 048cf5ba01e71d6c19f850d1f0cab300f551b715 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Thu, 26 Jun 2025 20:45:42 +0000 Subject: [PATCH 07/36] advanced features --- .../advanced-dev-containers.md | 573 +++--------------- 1 file changed, 101 insertions(+), 472 deletions(-) diff --git a/docs/admin/templates/extending-templates/advanced-dev-containers.md b/docs/admin/templates/extending-templates/advanced-dev-containers.md index 6e721ec350423..14495caab711f 100644 --- a/docs/admin/templates/extending-templates/advanced-dev-containers.md +++ b/docs/admin/templates/extending-templates/advanced-dev-containers.md @@ -1,148 +1,129 @@ # Advanced dev container configuration -This guide covers advanced configurations for [dev containers](./devcontainers.md) in [Coder templates](../index.md). +This page extends [devcontainers.md](./devcontainers.md) with patterns for multiple dev containers, +user-controlled startup, repository selection, and infrastructure tuning. -## Multiple dev containers +## Run multiple dev containers -Run multiple dev containers in a single workspace for microservices or multi-component development: +Run independent dev containers in the same workspace so each component appears as its own agent. -```terraform -# Frontend dev container - -resource "coder_devcontainer" "frontend" { - count = data.coder_workspace.me.start_count - agent_id = coder_agent.main.id - workspace_folder = "/home/coder/frontend/${module.git_clone_frontend[0].folder_name}" -} - -# Backend dev container - -resource "coder_devcontainer" "backend" { - count = data.coder_workspace.me.start_count - agent_id = coder_agent.main.id - workspace_folder = "/home/coder/backend/${module.git_clone_frontend[0].folder_name}" -} +In this example, there are three: `frontend`, `backend`, and a `database`: -# Database dev container +```terraform +# Clone each repo +module "git_clone_frontend" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/modules/git-clone/coder" + version = "~> 1.0" -resource "coder_devcontainer" "database" { - count = data.coder_workspace.me.start_count - agent_id = coder_agent.main.id - workspace_folder = "/home/coder/database/${module.git_clone_frontend[0].folder_name}" + agent_id = coder_agent.main.id + url = "https://github.com/your-org/frontend.git" + base_dir = "/home/coder/frontend" } -# Clone multiple repositories - -module "git-clone-frontend" { +module "git_clone_backend" { count = data.coder_workspace.me.start_count source = "registry.coder.com/modules/git-clone/coder" version = "~> 1.0" agent_id = coder_agent.main.id - url = "https://github.com/your-org/frontend.git" - base_dir = "/home/coder/frontend/${module.git_clone_frontend[0].folder_name}" + url = "https://github.com/your-org/backend.git" + base_dir = "/home/coder/backend" } -module "git-clone-backend" { +module "git_clone_database" { count = data.coder_workspace.me.start_count source = "registry.coder.com/modules/git-clone/coder" version = "~> 1.0" agent_id = coder_agent.main.id - url = "https://github.com/your-org/backend.git" - base_dir = "/home/coder/backend/${module.git_clone_frontend[0].folder_name}" + url = "https://github.com/your-org/database.git" + base_dir = "/home/coder/database" +} + +# Dev container resources +resource "coder_devcontainer" "frontend" { + count = data.coder_workspace.me.start_count + agent_id = coder_agent.main.id + workspace_folder = "/home/coder/frontend/${module.git_clone_frontend[0].folder_name}" + depends_on = [module.git_clone_frontend] +} + +resource "coder_devcontainer" "backend" { + count = data.coder_workspace.me.start_count + agent_id = coder_agent.main.id + workspace_folder = "/home/coder/backend/${module.git_clone_backend[0].folder_name}" + depends_on = [module.git_clone_backend] +} + +resource "coder_devcontainer" "database" { + count = data.coder_workspace.me.start_count + agent_id = coder_agent.main.id + workspace_folder = "/home/coder/database/${module.git_clone_database[0].folder_name}" + depends_on = [module.git_clone_database] } ``` -Each dev container will appear as a separate agent in the Coder UI, allowing developers to connect to different environments within the same workspace. +Each dev container appears as a separate agent, so developers can connect to any +component in the workspace. -## Add a Personal devcontainer.json alongside a repository-specific one +## Personal overrides -Keep a canonical `devcontainer.json` in the repo, then let each developer add an -untracked `devcontainer.local.json` (or another file referenced via `"extends"`). +Let developers extend the repo’s `devcontainer.json` with an ignored (by Git) `devcontainer.local.json` file + so they can add personal tools without changing the canonical config: ```jsonc -// devcontainer.local.json (ignored by Git) +// devcontainer.local.json (in .gitignore) { "extends": "./devcontainer.json", "features": { - "ghcr.io/devcontainers/features/node:1": { "version": "20" } + "ghcr.io/devcontainers/features/node": { "version": "20" } }, "postStartCommand": "npm i -g tldr" } ``` -## Conditional startup with user control +## Conditional startup -Allow users to selectively enable dev containers: +Use `coder_parameter` booleans to let workspace creators choose which dev containers start automatically, +reducing resource usage for unneeded components: ```terraform data "coder_parameter" "enable_frontend" { type = "bool" - name = "Enable frontend dev container" + name = "Enable frontend container" default = true - description = "Start the frontend dev container automatically" mutable = true order = 3 } -data "coder_parameter" "enable_backend" { - type = "bool" - name = "Enable backend dev container" - default = true - description = "Start the backend dev container automatically" - mutable = true - order = 4 -} - resource "coder_devcontainer" "frontend" { count = data.coder_parameter.enable_frontend.value ? data.coder_workspace.me.start_count : 0 agent_id = coder_agent.main.id workspace_folder = "/home/coder/frontend/${module.git_clone_frontend[0].folder_name}" -} - -resource "coder_devcontainer" "backend" { - count = data.coder_parameter.enable_backend.value ? data.coder_workspace.me.start_count : 0 - agent_id = coder_agent.main.id - workspace_folder = "/home/coder/backend/${module.git_clone_frontend[0].folder_name}" + depends_on = [module.git_clone_frontend] } ``` -## Repository selection patterns +## Repository-selection patterns + +Prompt users to pick a repository or team at workspace creation time and clone the selected repo(s) automatically into the workspace: -### Dropdown with predefined projects +### Dropdown selector ```terraform data "coder_parameter" "project" { - name = "Project" - description = "Select a project to work on" - type = "string" - mutable = true - order = 1 - - option { - name = "E-commerce Frontend" - description = "React-based e-commerce frontend" - value = "https://github.com/your-org/ecommerce-frontend.git" - icon = "/icon/react.svg" - } - - option { - name = "Payment Service" - description = "Node.js payment processing service" - value = "https://github.com/your-org/payment-service.git" - icon = "/icon/nodejs.svg" - } + name = "Project" + description = "Choose a project" + type = "string" + mutable = true + order = 1 - option { - name = "User Management API" - description = "Python user management API" - value = "https://github.com/your-org/user-api.git" - icon = "/icon/python.svg" - } + option { name = "E-commerce FE" value = "https://github.com/org/ecom-fe.git" icon = "/icon/react.svg" } + option { name = "Payment API" value = "https://github.com/org/pay.git" icon = "/icon/nodejs.svg" } } -module "git-clone" { +module "git_clone_selected" { count = data.coder_workspace.me.start_count source = "registry.coder.com/modules/git-clone/coder" version = "~> 1.0" @@ -153,439 +134,87 @@ module "git-clone" { } ``` -### Team-based repository access +### Team-based selection ```terraform data "coder_parameter" "team" { - name = "Team" - description = "Select your team" - type = "string" - mutable = true - order = 1 - - option { - name = "Frontend Team" - value = "frontend" - icon = "/icon/frontend.svg" - } - - option { - name = "Backend Team" - value = "backend" - icon = "/icon/backend.svg" - } + name = "Team" + type = "string" + mutable = true + order = 1 - option { - name = "DevOps Team" - value = "devops" - icon = "/icon/devops.svg" - } + option { name = "Frontend" value = "frontend" icon = "/icon/react.svg" } + option { name = "Backend" value = "backend" icon = "/icon/nodejs.svg" } } locals { - team_repos = { - frontend = [ - "https://github.com/your-org/web-app.git", - "https://github.com/your-org/mobile-app.git" - ] - backend = [ - "https://github.com/your-org/api-service.git", - "https://github.com/your-org/auth-service.git" - ] - devops = [ - "https://github.com/your-org/infrastructure.git", - "https://github.com/your-org/monitoring.git" - ] + repos = { + frontend = ["https://github.com/your-org/web.git"] + backend = ["https://github.com/your-org/api.git"] } } -module "git-clone-team-repos" { - count = length(local.team_repos[data.coder_parameter.team.value]) * data.coder_workspace.me.start_count +module "git_clone_team" { + count = length(local.repos[data.coder_parameter.team.value]) * data.coder_workspace.me.start_count source = "registry.coder.com/modules/git-clone/coder" version = "~> 1.0" agent_id = coder_agent.main.id - url = local.team_repos[data.coder_parameter.team.value][count.index % length(local.team_repos[data.coder_parameter.team.value])] - base_dir = "/home/coder/repos/$sename(local.team_repos[data.coder_parameter.team.value][count.index % length(local.team_repos[data.coder_parameter.team.value])])}" + url = local.repos[data.coder_parameter.team.value][count.index] + base_dir = "/home/coder/${replace(basename(url), \".git\", \"\")}" } ``` -## Advanced infrastructure configurations +## Infrastructure tuning + +Adjust workspace infrastructure to set memory/CPU limits, attach a custom Docker network, +or add persistent volumes—to improve performance and isolation for dev containers: -### Resource limits and monitoring +### Resource limits ```terraform resource "docker_container" "workspace" { - count = data.coder_workspace.me.start_count - -# ... other configuration - -# Set resource limits - - memory = 4096 # 4GB - cpus = 2.0 # 2 CPU cores - -# Enable swap accounting - - memory_swap = 8192 # 8GB including swap -} - -resource "coder_agent" "main" { - -# ... other configuration - -# Monitor dev container status - - metadata { - display_name = "Dev Containers" - key = "devcontainers" - script = "docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' | grep -v NAMES || echo 'No dev containers running'" - interval = 30 - timeout = 5 - } + count = data.coder_workspace.me.start_count + image = "codercom/enterprise-base:ubuntu" - metadata { - display_name = "Docker Resource Usage" - key = "docker_resources" - script = "docker stats --no-stream --format 'table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}' | head -5" - interval = 60 - timeout = 10 + resources { + memory = 4096 # MiB + cpus = 2 } + memory_swap = 8192 } ``` -### Custom Docker networks +### Custom network ```terraform -resource "docker_network" "dev_network" { - name = "coder-${data.coder_workspace.me.id}-dev +resource "docker_network" "dev" { + name = "coder-${data.coder_workspace.me.id}-dev" } resource "docker_container" "workspace" { - count = data.coder_workspace.me.start_count - -# ... other configuration - - networks_advanced { - name = docker_network.dev_network.name - } + networks_advanced { name = docker_network.dev.name } } ``` -### Volume management for performance +### Volume caching ```terraform resource "docker_volume" "node_modules" { name = "coder-${data.coder_workspace.me.id}-node-modules" - lifecycle { - ignore_changes = all - } -} - -resource "docker_volume" "go_cache" { - name = "coder-${data.coder_workspace.me.id}-go-cache" - lifecycle { - ignore_changes = all - } + lifecycle { ignore_changes = all } } resource "docker_container" "workspace" { - count = data.coder_workspace.me.start_count - -# ... other configuration - -# Persist node_modules for faster installs - volumes { container_path = "/home/coder/project/node_modules" volume_name = docker_volume.node_modules.name } - -# Persist Go module cache - - volumes { - container_path = "/home/coder/go/pkg/mod" - volume_name = docker_volume.go_cache.name - } } ``` -## Dev container template example - -You can test the Coder dev container integration and features with this example template. - -Example template infrastructure requirements: - -- Docker host with Docker daemon and socket available at `/var/run/docker.sock`. -- Network access to pull `codercom/enterprise-base:ubuntu` image. -- Access to Coder registry modules at `dev.registry.coder.com` and `registry.coder.com`. - -Customization needed: - -- Replace the default repository URL with your preferred repository. -- Adjust volume paths if your setup differs from `/home/coder`. -- Modify resource limits if needed for your workloads. - -
Expand for the example template: - -```terraform -terraform { - required_providers { - coder = { - source = "coder/coder" - version = "~> 2.5" - } - docker = { - source = "kreuzwerker/docker" - version = "~> 3.0" - } - } -} - -provider "coder" {} -provider "docker" {} - -data "coder_workspace" "me" {} -data "coder_workspace_owner" "me" {} -data "coder_provisioner" "me" {} - -data "coder_parameter" "repo_url" { - name = "Repository URL" - description = "Git repository with devcontainer.json" - type = "string" - default = "https://github.com/microsoft/vscode-remote-try-node.git" - mutable = true - order = 1 -} - -data "coder_parameter" "enable_devcontainer" { - type = "bool" - name = "Enable dev container" - default = true - description = "Automatically start the dev container" - mutable = true - order = 2 -} - -# Create persistent volume for home directory - -resource "docker_volume" "home_volume" { - name = "coder-${data.coder_workspace.me.id}-home" - lifecycle { - ignore_changes = all - } -} - -# Main workspace container - -resource "docker_container" "workspace" { - count = data.coder_workspace.me.start_count - image = "codercom/enterprise-base:ubuntu" - name = "coder-${data.coder_workspace.me.name}-${lower(data.coder_workspace.me.name)}" - -# Hostname makes the shell more user friendly - - hostname = data.coder_workspace.me.name - -# Required environment variables - - env = [ - "CODER_AGENT_TOKEN=${coder_agent.dev.token}", - "CODER_AGENT_DEVCONTAINERS_ENABLE=true", - ] - -# Mount home directory for persistence - - volumes { - container_path = "/home/coder" - volume_name = docker_volume.home_volume.name - read_only = false - } - -# Mount Docker socket for dev container support - - volumes { - container_path = "/var/run/docker.sock" - host_path = "/var/run/docker.sock" - read_only = false - } - -# Use the Docker host gateway for Coder access - - host { - host = "host.docker.internal" - ip = "host-gateway" - } -} - -# Coder agent - -resource "coder_agent" "main" { - arch = data.coder_provisioner.me.arch - os = "linux" - dir = "/home/coder" - - startup_script_behavior = "blocking" - startup_script = <<-EOT - set -e - - # Start Docker service - sudo service docker start - - # Wait for Docker to be ready - timeout 60 bash -c 'until docker info >/dev/null 2>&1; do sleep 1; done' - - echo "Workspace ready!" - EOT - - shutdown_script = <<-EOT - sudo service docker stop - EOT - -# Git configuration - - env = { - GIT_AUTHOR_NAME = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name) - GIT_AUTHOR_EMAIL = data.coder_workspace_owner.me.email - GIT_COMMITTER_NAME = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name) - GIT_COMMITTER_EMAIL = data.coder_workspace_owner.me.email - } -} - -# Install devcontainers CLI - -module "devcontainers-cli" { - count = data.coder_workspace.me.start_count - source = "dev.registry.coder.com/modules/devcontainers-cli/coder" - agent_id = coder_agent.main.id -} - -# Clone repository - -module "git-clone" { - count = data.coder_workspace.me.start_count - source = "registry.coder.com/modules/git-clone/coder" - version = "~> 1.0" - - agent_id = coder_agent.main.id - url = data.coder_parameter.repo_url.value - base_dir = "/home/coder/project" -} - -# Auto-start dev container - -resource "coder_devcontainer" "project" { - count = data.coder_parameter.enable_devcontainer.value ? data.coder_workspace.me.start_count : 0 - agent_id = coder_agent.main.id - workspace_folder = "/home/coder/project" -} - -# Add code-server for web-based development - -module "code-server" { - count = data.coder_workspace.me.start_count - source = "registry.coder.com/modules/code-server/coder" - version = "~> 1.0" - agent_id = coder_agent.main.id - order = 1 -} -``` - -
- -## Troubleshooting and debugging - -You can troubleshoot dev container issues within a template or use the -[troubleshooting dev containers](../../../user-guides/devcontainers/troubleshooting-dev-containers.md) -documentation for issues that affect dev containers within user workspaces. - -### Debug startup issues - -```terraform -resource "coder_agent" "main" { - -# ... other configuration - - startup_script = <<-EOT - set -e - - # Enable debug logging - exec > >(tee -a /tmp/startup.log) 2>&1 - echo "=== Startup Debug Log $(date) ===" - - # Start Docker service with verbose logging - sudo service docker start - - # Wait for Docker and log status - echo "Waiting for Docker daemon..." - timeout 60 bash -c 'until docker info >/dev/null 2>&1; do - echo "Docker not ready, waiting..." - sleep 2 - done' - - echo "Docker daemon ready!" - docker version - - # Log dev container status - echo "=== Dev Container Status ===" - docker ps -a - - echo "Workspace startup complete!" - EOT -} -``` - -### Add health checks and monitoring - -```terraform -resource "coder_agent" "main" { - -# ... other configuration - - metadata { - display_name = "Docker Service Status" - key = "docker_status" - script = "systemctl is-active docker || echo 'Docker service not running'" - interval = 30 - timeout = 5 - } - - metadata { - display_name = "Dev Container Health" - key = "devcontainer_health" - script = <<-EOT - containers=$(docker ps --filter "label=devcontainer.local_folder" --format "{{.Names}}") - if [ -z "$containers" ]; then - echo "No dev containers running" - else - echo "Running: $containers" - fi - EOT - interval = 60 - timeout = 10 - } -} -``` - -### Dev containers fail to start - -1. Check Docker service status: - - ```shell - systemctl status docker - ``` - -1. Verify Docker socket permissions. -1. Review startup logs in `/tmp/startup.log`. - -### Poor performance with multiple containers - -1. Implement volume caching for package managers. -1. Set appropriate resource limits. -1. Use Docker networks for container communication. - -### Repository access problems +## Troubleshooting tips -1. Verify Git credentials are configured. -1. Check network connectivity to repository hosts. -1. Ensure repository URLs are accessible from the workspace. +1. Run `docker ps` inside the workspace to ensure Docker is available. +1. Check `/tmp/startup.log` for agent logs. +1. Verify the workspace image includes Node/npm or add the `nodejs` module before the `devcontainers_cli` module. From 5dc41a5d15831306cf6c7bc4f5d10e3d9005dbed Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Thu, 26 Jun 2025 20:54:02 +0000 Subject: [PATCH 08/36] better examples --- .../templates/extending-templates/advanced-dev-containers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/admin/templates/extending-templates/advanced-dev-containers.md b/docs/admin/templates/extending-templates/advanced-dev-containers.md index 14495caab711f..d47d71cee625f 100644 --- a/docs/admin/templates/extending-templates/advanced-dev-containers.md +++ b/docs/admin/templates/extending-templates/advanced-dev-containers.md @@ -175,13 +175,13 @@ or add persistent volumes—to improve performance and isolation for dev contain ```terraform resource "docker_container" "workspace" { count = data.coder_workspace.me.start_count - image = "codercom/enterprise-base:ubuntu" + image = "codercom/enterprise-node:ubuntu" resources { memory = 4096 # MiB cpus = 2 + memory_swap = 8192 } - memory_swap = 8192 } ``` From be23e859b90ef18eda46d77a18d0a5ba6ba4b689 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Thu, 26 Jun 2025 21:22:30 +0000 Subject: [PATCH 09/36] init user-facing update --- docs/user-guides/devcontainers/index.md | 26 +++---------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/docs/user-guides/devcontainers/index.md b/docs/user-guides/devcontainers/index.md index ed817fe853416..d15b5d91e6846 100644 --- a/docs/user-guides/devcontainers/index.md +++ b/docs/user-guides/devcontainers/index.md @@ -1,16 +1,8 @@ # Dev Containers Integration -> [!NOTE] -> -> The Coder dev containers integration is an [early access](../../install/releases/feature-stages.md) feature. -> -> While functional for testing and feedback, it may change significantly before general availability. - -The dev containers integration is an early access feature that enables seamless -creation and management of dev containers in Coder workspaces. This feature -leverages the [`@devcontainers/cli`](https://github.com/devcontainers/cli) and -[Docker](https://www.docker.com) to provide a streamlined development -experience. +The dev containers integration enables seamless creation and management of dev containers in Coder workspaces. +This feature leverages the [`@devcontainers/cli`](https://github.com/devcontainers/cli) and [Docker](https://www.docker.com) +to provide a streamlined development experience. This implementation is different from the existing [Envbuilder-based dev containers](../../admin/templates/managing-templates/devcontainers/index.md) @@ -61,18 +53,6 @@ When a workspace with the dev containers integration starts: - Support for automatic port forwarding inside the container - Full native SSH support to dev containers -## Limitations during Early Access - -During the early access phase, the dev containers integration has the following -limitations: - -- Changes to the `devcontainer.json` file require manual container recreation -- Automatic port forwarding only works for ports specified in `appPort` -- SSH access requires using the `--container` flag -- Some devcontainer features may not work as expected - -These limitations will be addressed in future updates as the feature matures. - ## Comparison with Envbuilder-based Dev Containers | Feature | Dev Containers (Early Access) | Envbuilder Dev Containers | From ec77a258621bc70aa7da5e93b982378485b95050 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Fri, 27 Jun 2025 13:54:40 +0000 Subject: [PATCH 10/36] remove coder_agent_devcontainers_enable requirement --- .../extending-templates/devcontainers.md | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/docs/admin/templates/extending-templates/devcontainers.md b/docs/admin/templates/extending-templates/devcontainers.md index e75e650b378f6..3149932360873 100644 --- a/docs/admin/templates/extending-templates/devcontainers.md +++ b/docs/admin/templates/extending-templates/devcontainers.md @@ -49,21 +49,6 @@ When integrated with Coder templates, they provide: -## Enable Dev Containers Integration - -To enable the dev containers integration in your workspace, add the `CODER_AGENT_DEVCONTAINERS_ENABLE` environment variable to your existing `coder_agent` block: - -```terraform -env = { - CODER_AGENT_DEVCONTAINERS_ENABLE = "true" - # existing variables ... -} -``` - -This environment variable is required for the Coder agent to detect and manage dev containers. -Without it, the agent will not attempt to start or connect to dev containers even if the -`coder_devcontainer` resource is defined. - ## Install the Dev Containers CLI Use the @@ -193,7 +178,6 @@ data "coder_workspace_owner" "me" {} resource "coder_agent" "main" { os = "linux" arch = "amd64" - env = { CODER_AGENT_DEVCONTAINERS_ENABLE = "true" } startup_script_behavior = "blocking" startup_script = "sudo service docker start" @@ -247,7 +231,6 @@ data "coder_workspace_owner" "me" {} resource "coder_agent" "main" { os = "linux" arch = "amd64" - env = { CODER_AGENT_DEVCONTAINERS_ENABLE = "true" } startup_script_behavior = "blocking" startup_script = "sudo service docker start" @@ -298,11 +281,21 @@ resource "kubernetes_pod" "workspace" { ## Troubleshoot common issues +### Disable Dev Containers Integration + +To disable the dev containers integration in your workspace, add the `CODER_AGENT_DEVCONTAINERS_ENABLE` environment variable to your existing `coder_agent` block: + +```terraform +env = { + CODER_AGENT_DEVCONTAINERS_ENABLE = "false" + # existing variables ... +} +``` + ### Dev container does not start -1. `CODER_AGENT_DEVCONTAINERS_ENABLE=true` missing. 1. Docker daemon not running inside the workspace. -1. `devcontainer.json` missing or mislocated. +1. `devcontainer.json` missing or is in the wrong place. 1. Build errors: check agent logs. ### Permission errors From 06d714c4574e52360d9cf77ceea7e63bafaa1568 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Fri, 27 Jun 2025 15:42:03 +0000 Subject: [PATCH 11/36] update features; add personal devcontainer file --- docs/user-guides/devcontainers/index.md | 74 ++++++++++++++----------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/docs/user-guides/devcontainers/index.md b/docs/user-guides/devcontainers/index.md index d15b5d91e6846..202525aa4c7df 100644 --- a/docs/user-guides/devcontainers/index.md +++ b/docs/user-guides/devcontainers/index.md @@ -4,16 +4,11 @@ The dev containers integration enables seamless creation and management of dev c This feature leverages the [`@devcontainers/cli`](https://github.com/devcontainers/cli) and [Docker](https://www.docker.com) to provide a streamlined development experience. -This implementation is different from the existing -[Envbuilder-based dev containers](../../admin/templates/managing-templates/devcontainers/index.md) -offering. - ## Prerequisites -- Coder version 2.22.0 or later -- Coder CLI version 2.22.0 or later +- Coder version 2.24.0 or later +- Coder CLI version 2.24.0 or later - A template with: - - Dev containers integration enabled - A Docker-compatible workspace image - Appropriate permissions to execute Docker commands inside your workspace @@ -30,32 +25,45 @@ which allows for extensive customization of your development setup. When a workspace with the dev containers integration starts: 1. The workspace initializes the Docker environment. -1. The integration detects repositories with a `.devcontainer` directory or a - `devcontainer.json` file. -1. The integration builds and starts the dev container based on the - configuration. +1. The integration detects repositories with a `.devcontainer` directory or a `devcontainer.json` file. +1. The integration builds (or rebuilds) and starts the dev container based on the configuration. 1. Your workspace automatically detects the running dev container. +1. If the configuration changes, the workspace prompts you to rebuild the dev container. ## Features -### Available Now +### Detection & Lifecycle + +- Automatic discovery of `.devcontainer` configs. +- Change detection with rebuild prompts. +- Rebuild containers with one click from the Coder dashboard or from the CLI. + +### Connectivity + +- Full SSH access directly into dev containers (`coder ssh --container ...`). +- Automatic port forwarding based on `appPort`, `forwardPorts`, or `docker-compose.yml`. -- Automatic dev container detection from repositories -- Seamless dev container startup during workspace initialization -- Integrated IDE experience in dev containers with VS Code -- Direct service access in dev containers -- Limited SSH access to dev containers +## Personal overrides -### Coming Soon +To add tools or tweaks that enhance your personal experience, create a `devcontainer.local.json` file in the same +directory as the project’s `devcontainer.json`: -- Dev container change detection -- On-demand dev container recreation -- Support for automatic port forwarding inside the container -- Full native SSH support to dev containers +```jsonc +{ + "extends": "./devcontainer.json", + "features": { + "ghcr.io/devcontainers/features/node": { "version": "20" } + }, + "postStartCommand": "npm i -g tldr" +} +``` + +Add the file name to your project's `.gitignore` or to your +[global exclude file](https://docs.github.com/en/get-started/git-basics/ignoring-files#configuring-ignored-files-for-all-repositories-on-your-computer). ## Comparison with Envbuilder-based Dev Containers -| Feature | Dev Containers (Early Access) | Envbuilder Dev Containers | +| Feature | Dev Containers | Envbuilder Dev Containers | |----------------|----------------------------------------|----------------------------------------------| | Implementation | Direct `@devcontainers/cli` and Docker | Coder's Envbuilder | | Target users | Individual developers | Platform teams and administrators | @@ -63,17 +71,17 @@ When a workspace with the dev containers integration starts: | Management | User-controlled | Admin-controlled | | Requirements | Docker access in workspace | Compatible with more restricted environments | -Choose the appropriate solution based on your team's needs and infrastructure -constraints. For additional details on Envbuilder's dev container support, see -the +Choose the appropriate solution based on your team's needs and infrastructure constraints. +For additional details on Envbuilder's dev container support, see the [Envbuilder devcontainer spec support documentation](https://github.com/coder/envbuilder/blob/main/docs/devcontainer-spec-support.md). +## Known Limitations + +Currently, dev containers are not compatible with the [prebuilt workspaces](../../admin/templates/extending-templates/prebuilt-workspaces.md). + +If your template allows for prebuilt workspaces, do not select a prebuilt workspace if you plan to use a dev container. + ## Next Steps -- Explore the [dev container specification](https://containers.dev/) to learn - more about advanced configuration options -- Read about [dev container features](https://containers.dev/features) to - enhance your development environment -- Check the - [VS Code dev containers documentation](https://code.visualstudio.com/docs/devcontainers/containers) - for IDE-specific features +- [Dev container specification](https://containers.dev/) +- [VS Code dev containers documentation](https://code.visualstudio.com/docs/devcontainers/containers) From 8f392c0194ae5e623ce3aa77ec2882c74c45701c Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:02:03 +0000 Subject: [PATCH 12/36] update troubleshooting --- .../troubleshooting-dev-containers.md | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/docs/user-guides/devcontainers/troubleshooting-dev-containers.md b/docs/user-guides/devcontainers/troubleshooting-dev-containers.md index ca27516a81cc0..4d186e7b77f3a 100644 --- a/docs/user-guides/devcontainers/troubleshooting-dev-containers.md +++ b/docs/user-guides/devcontainers/troubleshooting-dev-containers.md @@ -1,6 +1,9 @@ # Troubleshooting dev containers -## Dev Container Not Starting +If you encounter issues with dev containers in your workspace, review the steps here as well as the dev containers +[user](./index.md) and [admin](../../admin/templates/extending-templates/devcontainers.md#troubleshoot-common-issues) documentation. + +## Container does not start If your dev container fails to start: @@ -9,8 +12,22 @@ If your dev container fails to start: - `/tmp/coder-agent.log` - `/tmp/coder-startup-script.log` - `/tmp/coder-script-[script_id].log` + - `/tmp/devcontainer-build.log` 1. Verify that Docker is running in your workspace. 1. Ensure the `devcontainer.json` file is valid. 1. Check that the repository has been cloned correctly. -1. Verify the resource limits in your workspace are sufficient. +1. Ensure the workspace image has Node/npm and the `devcontainers-cli` module installed. +1. Verify that the resource limits in your workspace are sufficient. + +## Rebuild prompt does not appear + +1. Confirm that you saved `devcontainer.json` (or changes to `devcontainer.local.json`) in the correct repo path detected by Coder. +1. Run `coder devcontainer rebuild` manually. +1. Check agent logs for `devcontainer build` errors. + +## Known Limitations + +Currently, dev containers are not compatible with the [prebuilt workspaces](../../admin/templates/extending-templates/prebuilt-workspaces.md). + +If your template allows for prebuilt workspaces, do not select a prebuilt workspace if you plan to use a dev container. From 2fe52912e458434ffa5c609adc09185e9f64ad96 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:40:18 +0000 Subject: [PATCH 13/36] add comparison doc; crosslink --- .../dev-containers-envbuilder.md | 73 +++++++++++++++++++ .../extending-templates/devcontainers.md | 3 + docs/manifest.json | 7 +- docs/user-guides/devcontainers/index.md | 6 +- 4 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 docs/admin/templates/extending-templates/dev-containers-envbuilder.md diff --git a/docs/admin/templates/extending-templates/dev-containers-envbuilder.md b/docs/admin/templates/extending-templates/dev-containers-envbuilder.md new file mode 100644 index 0000000000000..79d20e3aed84d --- /dev/null +++ b/docs/admin/templates/extending-templates/dev-containers-envbuilder.md @@ -0,0 +1,73 @@ +# Choose an approach to Dev Containers in Coder + +Coder supports two independent ways to run Dev Containers inside a workspace. + +Both implement the [Dev Container specification](https://containers.dev/), but they differ in how the container is built, +who controls it, and which runtime requirements exist. + +Use this page to decide which path fits your project or platform needs. + +## Options at a glance + +| Capability / Trait | Dev Containers integration (CLI-based) | Envbuilder Dev Containers | +|------------------------------------------|------------------------------------------|-------------------------------------------| +| Build engine | `@devcontainers/cli` + Docker | Envbuilder transforms the workspace image | +| Runs separate Docker container | Yes (parent workspace + child container) | No (modifies the parent container) | +| Multiple Dev Containers per workspace | Yes | No | +| Rebuild when `devcontainer.json` changes | Yes (auto-prompt) | Limited (requires full workspace rebuild) | +| Docker required in workspace | Yes | No (works in restricted envs) | +| Admin vs. developer control | Developer decides per repo | Platform admin manages via template | +| Templates | Standard `devcontainer.json` | Terraform + Envbuilder blocks | +| Suitable for CI / AI agents | Yes. Deterministic, composable | Less ideal. No isolated container | + +## When to choose the Dev Containers integration + +Choose the new integration if: + +- Your workspace image can run Docker (DinD, Sysbox, or a mounted Docker socket). +- You need multiple Dev Containers (like `frontend`, `backend`, `db`) in a single workspace. +- Developers should own their environment and rebuild on demand. +- You rely on features such as automatic port forwarding, full SSH into containers, or change-detection prompts. + +[Dev Container integration](./devcontainers.md) documentation. + +## When to choose Envbuilder + +Envbuilder remains a solid choice when: + +- Docker isn’t available or allowed inside the workspace image. +- The platform team wants tight control over container contents via Terraform. +- A single layered environment is sufficient (no need for separate sub-containers). +- You already have Envbuilder templates in production and they meet current needs. + +[Envbuilder Dev Container](../managing-templates/devcontainers/add-devcontainer.md#envbuilder-terraform-provider) documentation. + +## How to migrate from Envbuilder to the Dev Containers integration + +1. Ensure the workspace image can run Docker and has sufficient resources: + + ```shell + docker ps + ``` + +1. Remove any Envbuilder blocks that reference `coder_dev_envbuilder` from the template. +1. Add (or keep) a standard `.devcontainer/` folder with `devcontainer.json` in the repository. +1. Add the `devcontainers-cli` module: + + ```terraform + module "devcontainers_cli" { + source = "dev.registry.coder.com/modules/devcontainers-cli/coder" + agent_id = coder_agent.main.id + } + ``` + +1. Start a new workspace. + Coder detects and launches the dev container automatically. +1. Verify ports, SSH, and rebuild prompts function as expected. + +## Related reading + +- [Dev Containers Integration](./index.md) +- [Troubleshooting Dev Containers](./troubleshooting-dev-containers.md) +- [Envbuilder documentation](https://github.com/coder/envbuilder) +- [Dev Container specification](https://containers.dev/) diff --git a/docs/admin/templates/extending-templates/devcontainers.md b/docs/admin/templates/extending-templates/devcontainers.md index 3149932360873..425c48f397e61 100644 --- a/docs/admin/templates/extending-templates/devcontainers.md +++ b/docs/admin/templates/extending-templates/devcontainers.md @@ -17,6 +17,9 @@ When integrated with Coder templates, they provide: - **Consistency across teams**: Everyone works in identical environments regardless of their local machine. - **Version control**: Development environment changes are tracked alongside code changes. +Visit [Choose an approach to Dev Containers in Coder](./dev-containers-envbuilder.md) for an in-depth comparison between +the Dev Container integration and the legacy Envbuilder integration. + ## Prerequisites - Dev containers require Docker to build and run containers inside the workspace. diff --git a/docs/manifest.json b/docs/manifest.json index c7227120a3510..d5f2c04b41f50 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -602,13 +602,18 @@ }, { "title": "Configure a template for dev containers", - "description": "How to use configure your template for dev containers", + "description": "How to configure your template for dev containers", "path": "./admin/templates/extending-templates/devcontainers.md", "children": [ { "title": "Advanced dev container configurations", "description": "Enhance your dev container configurations with multiple containers, repo selection, monitoring, and more.", "path": "./admin/templates/extending-templates/advanced-dev-containers.md" + }, + { + "title": "Choose an approach to Dev Containers in Coder", + "description": "How to choose the right Dev Container integration.", + "path": "./admin/templates/extending-templates/dev-containers-envbuilder.md" } ] }, diff --git a/docs/user-guides/devcontainers/index.md b/docs/user-guides/devcontainers/index.md index 202525aa4c7df..ed687cd412bfc 100644 --- a/docs/user-guides/devcontainers/index.md +++ b/docs/user-guides/devcontainers/index.md @@ -72,8 +72,8 @@ Add the file name to your project's `.gitignore` or to your | Requirements | Docker access in workspace | Compatible with more restricted environments | Choose the appropriate solution based on your team's needs and infrastructure constraints. -For additional details on Envbuilder's dev container support, see the -[Envbuilder devcontainer spec support documentation](https://github.com/coder/envbuilder/blob/main/docs/devcontainer-spec-support.md). + +Visit [Choose an approach to Dev Containers in Coder](../../admin/templates/extending-templates/dev-containers-envbuilder.md) for a more in-depth comparison. ## Known Limitations @@ -83,5 +83,5 @@ If your template allows for prebuilt workspaces, do not select a prebuilt worksp ## Next Steps -- [Dev container specification](https://containers.dev/) +- [Dev Container specification](https://containers.dev/) - [VS Code dev containers documentation](https://code.visualstudio.com/docs/devcontainers/containers) From 33d3eed75722e57b769d77d6423a033471857d56 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:43:03 +0000 Subject: [PATCH 14/36] shorten title --- .../templates/extending-templates/dev-containers-envbuilder.md | 2 +- docs/admin/templates/extending-templates/devcontainers.md | 2 +- docs/manifest.json | 2 +- docs/user-guides/devcontainers/index.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/admin/templates/extending-templates/dev-containers-envbuilder.md b/docs/admin/templates/extending-templates/dev-containers-envbuilder.md index 79d20e3aed84d..2220eef5b6671 100644 --- a/docs/admin/templates/extending-templates/dev-containers-envbuilder.md +++ b/docs/admin/templates/extending-templates/dev-containers-envbuilder.md @@ -1,4 +1,4 @@ -# Choose an approach to Dev Containers in Coder +# Choose an approach to Dev Containers Coder supports two independent ways to run Dev Containers inside a workspace. diff --git a/docs/admin/templates/extending-templates/devcontainers.md b/docs/admin/templates/extending-templates/devcontainers.md index 425c48f397e61..c6f1754417f2d 100644 --- a/docs/admin/templates/extending-templates/devcontainers.md +++ b/docs/admin/templates/extending-templates/devcontainers.md @@ -17,7 +17,7 @@ When integrated with Coder templates, they provide: - **Consistency across teams**: Everyone works in identical environments regardless of their local machine. - **Version control**: Development environment changes are tracked alongside code changes. -Visit [Choose an approach to Dev Containers in Coder](./dev-containers-envbuilder.md) for an in-depth comparison between +Visit [Choose an approach to Dev Containers](./dev-containers-envbuilder.md) for an in-depth comparison between the Dev Container integration and the legacy Envbuilder integration. ## Prerequisites diff --git a/docs/manifest.json b/docs/manifest.json index d5f2c04b41f50..16429852da51c 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -611,7 +611,7 @@ "path": "./admin/templates/extending-templates/advanced-dev-containers.md" }, { - "title": "Choose an approach to Dev Containers in Coder", + "title": "Choose an approach to Dev Containers", "description": "How to choose the right Dev Container integration.", "path": "./admin/templates/extending-templates/dev-containers-envbuilder.md" } diff --git a/docs/user-guides/devcontainers/index.md b/docs/user-guides/devcontainers/index.md index ed687cd412bfc..da7942f0b70c5 100644 --- a/docs/user-guides/devcontainers/index.md +++ b/docs/user-guides/devcontainers/index.md @@ -73,7 +73,7 @@ Add the file name to your project's `.gitignore` or to your Choose the appropriate solution based on your team's needs and infrastructure constraints. -Visit [Choose an approach to Dev Containers in Coder](../../admin/templates/extending-templates/dev-containers-envbuilder.md) for a more in-depth comparison. +Visit [Choose an approach to Dev Containers](../../admin/templates/extending-templates/dev-containers-envbuilder.md) for a more in-depth comparison. ## Known Limitations From 747822e3da90553cb5990a89f0ead32c81cdc80b Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:49:06 +0000 Subject: [PATCH 15/36] clarify envbuilder doc titles --- .../devcontainers/add-devcontainer.md | 2 +- .../devcontainer-releases-known-issues.md | 2 +- .../devcontainer-security-caching.md | 2 +- .../managing-templates/devcontainers/index.md | 2 +- docs/manifest.json | 16 ++++++++-------- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/admin/templates/managing-templates/devcontainers/add-devcontainer.md b/docs/admin/templates/managing-templates/devcontainers/add-devcontainer.md index 5d2ac0a07f9e2..26446bc0cab94 100644 --- a/docs/admin/templates/managing-templates/devcontainers/add-devcontainer.md +++ b/docs/admin/templates/managing-templates/devcontainers/add-devcontainer.md @@ -1,4 +1,4 @@ -# Add a dev container template to Coder +# Add a dev container template with Envbuilder A Coder administrator adds a dev container-compatible template to Coder (Envbuilder). This allows the template to prompt for the developer for their dev diff --git a/docs/admin/templates/managing-templates/devcontainers/devcontainer-releases-known-issues.md b/docs/admin/templates/managing-templates/devcontainers/devcontainer-releases-known-issues.md index b8ba3bfddd21e..84a9ccd374fcf 100644 --- a/docs/admin/templates/managing-templates/devcontainers/devcontainer-releases-known-issues.md +++ b/docs/admin/templates/managing-templates/devcontainers/devcontainer-releases-known-issues.md @@ -1,4 +1,4 @@ -# Dev container releases and known issues +# Envbuilder dev container releases and known issues ## Release channels diff --git a/docs/admin/templates/managing-templates/devcontainers/devcontainer-security-caching.md b/docs/admin/templates/managing-templates/devcontainers/devcontainer-security-caching.md index a0ae51624fc6d..327b2f71a5461 100644 --- a/docs/admin/templates/managing-templates/devcontainers/devcontainer-security-caching.md +++ b/docs/admin/templates/managing-templates/devcontainers/devcontainer-security-caching.md @@ -1,4 +1,4 @@ -# Dev container security and caching +# Envbuilder dev container security and caching Ensure Envbuilder can only pull pre-approved images and artifacts by configuring it with your existing HTTP proxies, firewalls, and artifact managers. diff --git a/docs/admin/templates/managing-templates/devcontainers/index.md b/docs/admin/templates/managing-templates/devcontainers/index.md index a4ec140765a4c..fb6d7750f1de1 100644 --- a/docs/admin/templates/managing-templates/devcontainers/index.md +++ b/docs/admin/templates/managing-templates/devcontainers/index.md @@ -1,4 +1,4 @@ -# Dev containers +# Envbuilder Dev Containers A Development Container is an [open-source specification](https://containers.dev/implementors/spec/) for diff --git a/docs/manifest.json b/docs/manifest.json index 16429852da51c..9c0c9c1301fbb 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -481,23 +481,23 @@ "path": "./admin/templates/managing-templates/change-management.md" }, { - "title": "Dev containers", - "description": "Learn about using development containers in templates", + "title": "Envbuilder Dev Containers", + "description": "Learn about using Envbuilder to manage development containers in templates", "path": "./admin/templates/managing-templates/devcontainers/index.md", "children": [ { - "title": "Add a dev container template", - "description": "How to add a dev container template to Coder", + "title": "Add a dev container template with Envbuilder", + "description": "Use Envbuilder to add a dev container template to Coder", "path": "./admin/templates/managing-templates/devcontainers/add-devcontainer.md" }, { - "title": "Dev container security and caching", - "description": "Configure dev container authentication and caching", + "title": "Envbuilder dev container security and caching", + "description": "Configure dev container authentication and caching with Envbuilder", "path": "./admin/templates/managing-templates/devcontainers/devcontainer-security-caching.md" }, { - "title": "Dev container releases and known issues", - "description": "Dev container releases and known issues", + "title": "Envbuilder dev container releases and known issues", + "description": "Envbuilder dev container releases and known issues", "path": "./admin/templates/managing-templates/devcontainers/devcontainer-releases-known-issues.md" } ] From d9f818ccf71f81f72d76b553ffdc9c867b13a9b0 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:01:25 +0000 Subject: [PATCH 16/36] update envbuilder doc with dev container integration consideration --- .../managing-templates/devcontainers/index.md | 99 ++++--------------- 1 file changed, 21 insertions(+), 78 deletions(-) diff --git a/docs/admin/templates/managing-templates/devcontainers/index.md b/docs/admin/templates/managing-templates/devcontainers/index.md index fb6d7750f1de1..3f7887b902fe4 100644 --- a/docs/admin/templates/managing-templates/devcontainers/index.md +++ b/docs/admin/templates/managing-templates/devcontainers/index.md @@ -14,91 +14,33 @@ pre-approved by platform teams in registries like workflows, reduces the need for tickets and approvals, and promotes greater independence for developers. -## Prerequisites - -An administrator should construct or choose a base image and create a template -that includes a `devcontainer_builder` image before a developer team configures -dev containers. - -## Benefits of devcontainers - -There are several benefits to adding a dev container-compatible template to -Coder: - -- Reliability through standardization -- Scalability for growing teams -- Improved security -- Performance efficiency -- Cost Optimization - -### Reliability through standardization - -Use dev containers to empower development teams to personalize their own -environments while maintaining consistency and security through an approved and -hardened base image. - -Standardized environments ensure uniform behavior across machines and team -members, eliminating "it works on my machine" issues and creating a stable -foundation for development and testing. Containerized setups reduce dependency -conflicts and misconfigurations, enhancing build stability. - -### Scalability for growing teams +This doc explains how to use Envbuilder to integrate dev containers in a template. -Dev containers allow organizations to handle multiple projects and teams -efficiently. +For the Docker-based Dev Containers integration, follow the [Configure a template for dev containers](../../extending-templates/devcontainers.md) documentation. -You can leverage platforms like Kubernetes to allocate resources on demand, -optimizing costs and ensuring fair distribution of quotas. Developer teams can -use efficient custom images and independently configure the contents of their -version-controlled dev containers. - -This approach allows organizations to scale seamlessly, reducing the maintenance -burden on the administrators that support diverse projects while allowing -development teams to maintain their own images and onboard new users quickly. - -### Improved security - -Since Coder and Envbuilder run on your own infrastructure, you can use firewalls -and cluster-level policies to ensure Envbuilder only downloads packages from -your secure registry powered by JFrog Artifactory or Sonatype Nexus. -Additionally, Envbuilder can be configured to push the full image back to your -registry for additional security scanning. - -This means that Coder admins can require hardened base images and packages, -while still allowing developer self-service. - -Envbuilder runs inside a small container image but does not require a Docker -daemon in order to build a dev container. This is useful in environments where -you may not have access to a Docker socket for security reasons, but still need -to work with a container. - -### Performance efficiency - -Create a unique image for each project to reduce the dependency size of any -given project. - -Envbuilder has various caching modes to ensure workspaces start as fast as -possible, such as layer caching and even full image caching and fetching via the -[Envbuilder Terraform provider](https://registry.terraform.io/providers/coder/envbuilder/latest/docs). +## Prerequisites -### Cost optimization +An administrator should construct or choose a base image and create a template +that includes an Envbuilder container image `coder/envbuilder` before a developer team configures dev containers. -By creating unique images per-project, you remove unnecessary dependencies and -reduce the workspace size and resource consumption of any given project. Full -image caching ensures optimal start and stop times. +## Benefits of Envbuilder -## When to use a dev container +Key differences compared with the [Docker-based integration](../../extending-templates/devcontainers.md): -Dev containers are a good fit for developer teams who are familiar with Docker -and are already using containerized development environments. If you have a -large number of projects with different toolchains, dependencies, or that depend -on a particular Linux distribution, dev containers make it easier to quickly -switch between projects. +| Capability / Trait | Dev Containers integration (CLI-based) | Envbuilder Dev Containers | +|------------------------------------------|------------------------------------------|-------------------------------------------| +| Build engine | `@devcontainers/cli` + Docker | Envbuilder transforms the workspace image | +| Runs separate Docker container | Yes (parent workspace + child container) | No (modifies the parent container) | +| Multiple Dev Containers per workspace | Yes | No | +| Rebuild when `devcontainer.json` changes | Yes (auto-prompt) | Limited (requires full workspace rebuild) | +| Docker required in workspace | Yes | No (works in restricted envs) | +| Admin vs. developer control | Developer decides per repo | Platform admin manages via template | +| Templates | Standard `devcontainer.json` | Terraform + Envbuilder blocks | +| Suitable for CI / AI agents | Yes. Deterministic, composable | Less ideal. No isolated container | -They may also be a great fit for more restricted environments where you may not -have access to a Docker daemon since it doesn't need one to work. +Consult the full comparison at [Choose an approach to Dev Containers](../../extending-templates/dev-containers-envbuilder.md). -## Devcontainer Features +## Dev container Features [Dev container Features](https://containers.dev/implementors/features/) allow owners of a project to specify self-contained units of code and runtime @@ -119,4 +61,5 @@ of the Coder control plane and even run within a CI/CD pipeline. ## Next steps -- [Add a dev container template](./add-devcontainer.md) +- [Add an Envbuilder dev container template](./add-devcontainer.md) +- [Choose an approach to Dev Containers](../../extending-templates/dev-containers-envbuilder.md) From c10eb7e7507edc208c6c4ad299ed8ab552b39ee1 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:08:40 +0000 Subject: [PATCH 17/36] update envbuilder add-devcontainer with dev container integration links --- .../devcontainers/add-devcontainer.md | 19 ++++++++++--------- docs/manifest.json | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/admin/templates/managing-templates/devcontainers/add-devcontainer.md b/docs/admin/templates/managing-templates/devcontainers/add-devcontainer.md index 26446bc0cab94..348e04cbc5a0c 100644 --- a/docs/admin/templates/managing-templates/devcontainers/add-devcontainer.md +++ b/docs/admin/templates/managing-templates/devcontainers/add-devcontainer.md @@ -1,14 +1,14 @@ -# Add a dev container template with Envbuilder +# Add an Envbuilder dev container template -A Coder administrator adds a dev container-compatible template to Coder -(Envbuilder). This allows the template to prompt for the developer for their dev -container repository's URL as a -[parameter](../../extending-templates/parameters.md) when they create their -workspace. Envbuilder clones the repo and builds a container from the -`devcontainer.json` specified in the repo. +This guide shows platform administrators how to add an Envbuilder dev container template to Coder. -You can create template files through the Coder dashboard, CLI, or you can -choose a template from the +This allows the template to prompt for the developer for their dev container repository's URL as a +[parameter](../../extending-templates/parameters.md) when they create their workspace. +Envbuilder clones the repo and builds a container from the `devcontainer.json` specified in the repo. + +For the Docker-based Dev Containers integration, follow the [Configure a template for dev containers](../../extending-templates/devcontainers.md) documentation instead. + +You can create template files through the Coder dashboard, CLI, or you can choose a template from the [Coder registry](https://registry.coder.com/templates):
@@ -143,4 +143,5 @@ Lifecycle scripts are managed by project developers. ## Next steps +- [Choose an approach to Dev Containers](../../extending-templates/dev-containers-envbuilder.md) - [Dev container security and caching](./devcontainer-security-caching.md) diff --git a/docs/manifest.json b/docs/manifest.json index 9c0c9c1301fbb..5480b13910d4a 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -486,7 +486,7 @@ "path": "./admin/templates/managing-templates/devcontainers/index.md", "children": [ { - "title": "Add a dev container template with Envbuilder", + "title": "Add an Envbuilder dev container template", "description": "Use Envbuilder to add a dev container template to Coder", "path": "./admin/templates/managing-templates/devcontainers/add-devcontainer.md" }, From 3f8970e6aa716de6ee856c7bd943a47b839d3243 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:11:30 +0000 Subject: [PATCH 18/36] envbuilder tweaks --- .../devcontainers/devcontainer-security-caching.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/admin/templates/managing-templates/devcontainers/devcontainer-security-caching.md b/docs/admin/templates/managing-templates/devcontainers/devcontainer-security-caching.md index 327b2f71a5461..3e531f0fc90a7 100644 --- a/docs/admin/templates/managing-templates/devcontainers/devcontainer-security-caching.md +++ b/docs/admin/templates/managing-templates/devcontainers/devcontainer-security-caching.md @@ -1,7 +1,7 @@ # Envbuilder dev container security and caching -Ensure Envbuilder can only pull pre-approved images and artifacts by configuring -it with your existing HTTP proxies, firewalls, and artifact managers. +Envbuilder can pull only pre-approved images and artifacts when you configure it with your enterprise proxies, +firewalls, and artifact managers. ## Configure registry authentication From 0d0a9bacbd7e19b4eca37f7f6565e9855e67b955 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:14:42 +0000 Subject: [PATCH 19/36] link fix --- .../extending-templates/dev-containers-envbuilder.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/admin/templates/extending-templates/dev-containers-envbuilder.md b/docs/admin/templates/extending-templates/dev-containers-envbuilder.md index 2220eef5b6671..572afff4b2b56 100644 --- a/docs/admin/templates/extending-templates/dev-containers-envbuilder.md +++ b/docs/admin/templates/extending-templates/dev-containers-envbuilder.md @@ -68,6 +68,6 @@ Envbuilder remains a solid choice when: ## Related reading - [Dev Containers Integration](./index.md) -- [Troubleshooting Dev Containers](./troubleshooting-dev-containers.md) -- [Envbuilder documentation](https://github.com/coder/envbuilder) +- [Troubleshooting Dev Containers](../../../user-guides/devcontainers/troubleshooting-dev-containers.md) +- [Envbuilder on GitHub](https://github.com/coder/envbuilder) - [Dev Container specification](https://containers.dev/) From 88e7c0f70800aa035f4a079152ab25b63eebcd0e Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:20:52 +0000 Subject: [PATCH 20/36] tweak to devcontainer.local note --- .../extending-templates/advanced-dev-containers.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/admin/templates/extending-templates/advanced-dev-containers.md b/docs/admin/templates/extending-templates/advanced-dev-containers.md index d47d71cee625f..f6ffe40bf125a 100644 --- a/docs/admin/templates/extending-templates/advanced-dev-containers.md +++ b/docs/admin/templates/extending-templates/advanced-dev-containers.md @@ -70,10 +70,9 @@ component in the workspace. ## Personal overrides Let developers extend the repo’s `devcontainer.json` with an ignored (by Git) `devcontainer.local.json` file - so they can add personal tools without changing the canonical config: +so they can add personal tools without changing the canonical configuration: ```jsonc -// devcontainer.local.json (in .gitignore) { "extends": "./devcontainer.json", "features": { @@ -83,6 +82,9 @@ Let developers extend the repo’s `devcontainer.json` with an ignored (by Git) } ``` +Add the file name to your project's `.gitignore` or the user's +[global exclude file](https://docs.github.com/en/get-started/git-basics/ignoring-files#configuring-ignored-files-for-all-repositories-on-your-computer). + ## Conditional startup Use `coder_parameter` booleans to let workspace creators choose which dev containers start automatically, From 8f5e613141f75df2f1d57a18a58b5db364aa2fa1 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:28:43 +0000 Subject: [PATCH 21/36] ap title case --- .../extending-templates/advanced-dev-containers.md | 14 +++++++------- .../dev-containers-envbuilder.md | 12 ++++++------ .../templates/extending-templates/devcontainers.md | 14 +++++++------- .../devcontainers/add-devcontainer.md | 8 ++++---- .../devcontainer-releases-known-issues.md | 6 +++--- .../devcontainers/devcontainer-security-caching.md | 8 ++++---- .../managing-templates/devcontainers/index.md | 2 +- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/admin/templates/extending-templates/advanced-dev-containers.md b/docs/admin/templates/extending-templates/advanced-dev-containers.md index f6ffe40bf125a..4a2daec8a4fc5 100644 --- a/docs/admin/templates/extending-templates/advanced-dev-containers.md +++ b/docs/admin/templates/extending-templates/advanced-dev-containers.md @@ -1,9 +1,9 @@ -# Advanced dev container configuration +# Advanced Dev Container Configuration This page extends [devcontainers.md](./devcontainers.md) with patterns for multiple dev containers, user-controlled startup, repository selection, and infrastructure tuning. -## Run multiple dev containers +## Run Multiple Dev Containers Run independent dev containers in the same workspace so each component appears as its own agent. @@ -67,7 +67,7 @@ resource "coder_devcontainer" "database" { Each dev container appears as a separate agent, so developers can connect to any component in the workspace. -## Personal overrides +## Personal Overrides Let developers extend the repo’s `devcontainer.json` with an ignored (by Git) `devcontainer.local.json` file so they can add personal tools without changing the canonical configuration: @@ -85,7 +85,7 @@ so they can add personal tools without changing the canonical configuration: Add the file name to your project's `.gitignore` or the user's [global exclude file](https://docs.github.com/en/get-started/git-basics/ignoring-files#configuring-ignored-files-for-all-repositories-on-your-computer). -## Conditional startup +## Conditional Startup Use `coder_parameter` booleans to let workspace creators choose which dev containers start automatically, reducing resource usage for unneeded components: @@ -107,7 +107,7 @@ resource "coder_devcontainer" "frontend" { } ``` -## Repository-selection patterns +## Repository-selection Patterns Prompt users to pick a repository or team at workspace creation time and clone the selected repo(s) automatically into the workspace: @@ -167,7 +167,7 @@ module "git_clone_team" { } ``` -## Infrastructure tuning +## Infrastructure Tuning Adjust workspace infrastructure to set memory/CPU limits, attach a custom Docker network, or add persistent volumes—to improve performance and isolation for dev containers: @@ -215,7 +215,7 @@ resource "docker_container" "workspace" { } ``` -## Troubleshooting tips +## Troubleshooting 1. Run `docker ps` inside the workspace to ensure Docker is available. 1. Check `/tmp/startup.log` for agent logs. diff --git a/docs/admin/templates/extending-templates/dev-containers-envbuilder.md b/docs/admin/templates/extending-templates/dev-containers-envbuilder.md index 572afff4b2b56..07c15d8e352ce 100644 --- a/docs/admin/templates/extending-templates/dev-containers-envbuilder.md +++ b/docs/admin/templates/extending-templates/dev-containers-envbuilder.md @@ -1,4 +1,4 @@ -# Choose an approach to Dev Containers +# Choose an Approach To Dev Containers Coder supports two independent ways to run Dev Containers inside a workspace. @@ -7,7 +7,7 @@ who controls it, and which runtime requirements exist. Use this page to decide which path fits your project or platform needs. -## Options at a glance +## Options at a Glance | Capability / Trait | Dev Containers integration (CLI-based) | Envbuilder Dev Containers | |------------------------------------------|------------------------------------------|-------------------------------------------| @@ -20,7 +20,7 @@ Use this page to decide which path fits your project or platform needs. | Templates | Standard `devcontainer.json` | Terraform + Envbuilder blocks | | Suitable for CI / AI agents | Yes. Deterministic, composable | Less ideal. No isolated container | -## When to choose the Dev Containers integration +## When To Choose the Dev Containers Integration Choose the new integration if: @@ -31,7 +31,7 @@ Choose the new integration if: [Dev Container integration](./devcontainers.md) documentation. -## When to choose Envbuilder +## When To Choose Envbuilder Envbuilder remains a solid choice when: @@ -42,7 +42,7 @@ Envbuilder remains a solid choice when: [Envbuilder Dev Container](../managing-templates/devcontainers/add-devcontainer.md#envbuilder-terraform-provider) documentation. -## How to migrate from Envbuilder to the Dev Containers integration +## How To Migrate From Envbuilder to the Dev Containers Integration 1. Ensure the workspace image can run Docker and has sufficient resources: @@ -65,7 +65,7 @@ Envbuilder remains a solid choice when: Coder detects and launches the dev container automatically. 1. Verify ports, SSH, and rebuild prompts function as expected. -## Related reading +## Related Reading - [Dev Containers Integration](./index.md) - [Troubleshooting Dev Containers](../../../user-guides/devcontainers/troubleshooting-dev-containers.md) diff --git a/docs/admin/templates/extending-templates/devcontainers.md b/docs/admin/templates/extending-templates/devcontainers.md index c6f1754417f2d..82d5e9ab7c51a 100644 --- a/docs/admin/templates/extending-templates/devcontainers.md +++ b/docs/admin/templates/extending-templates/devcontainers.md @@ -1,4 +1,4 @@ -# Configure a template for dev containers +# Configure a Template for Dev Containers Dev containers provide a consistent, reproducible development environment using the [Development Containers specification](https://containers.dev/). @@ -7,7 +7,7 @@ Coder's dev container support allows developers to work in fully configured envi To enable dev containers in workspaces, [configure your template](../creating-templates.md) with the dev containers modules and configurations outlined in this doc. -## Why use dev containers +## Why Use Dev Containers Dev containers improve consistency across environments by letting developers define their development setup. When integrated with Coder templates, they provide: @@ -72,7 +72,7 @@ Alternatively, install the devcontainer CLI manually in your base image: RUN npm install -g @devcontainers/cli ``` -## Define the dev container resource +## Define the Dev Container Resource If you don't use [`git_clone`](#clone-the-repository), point the resource at the folder that contains `devcontainer.json`: @@ -84,7 +84,7 @@ resource "coder_devcontainer" "project" { } ``` -## Clone the repository +## Clone the Repository This step is optional, but it ensures that the project is present before the dev container starts. @@ -108,7 +108,7 @@ resource "coder_devcontainer" "project" { } ``` -## Dev container features +## Dev Container Features Enhance your dev container experience with additional features. For more advanced use cases, consult the [advanced dev containers doc](./advanced-dev-containers.md). @@ -282,9 +282,9 @@ resource "kubernetes_pod" "workspace" { -## Troubleshoot common issues +## Troubleshoot Common Issues -### Disable Dev Containers Integration +### Disable dev containers integration To disable the dev containers integration in your workspace, add the `CODER_AGENT_DEVCONTAINERS_ENABLE` environment variable to your existing `coder_agent` block: diff --git a/docs/admin/templates/managing-templates/devcontainers/add-devcontainer.md b/docs/admin/templates/managing-templates/devcontainers/add-devcontainer.md index 348e04cbc5a0c..83bb8cfecf680 100644 --- a/docs/admin/templates/managing-templates/devcontainers/add-devcontainer.md +++ b/docs/admin/templates/managing-templates/devcontainers/add-devcontainer.md @@ -1,4 +1,4 @@ -# Add an Envbuilder dev container template +# Add an Envbuilder Dev Container Template This guide shows platform administrators how to add an Envbuilder dev container template to Coder. @@ -118,7 +118,7 @@ their development environments: # … ``` -## Example templates +## Example Templates | Template | Description | |---------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -132,7 +132,7 @@ Your template can prompt the user for a repo URL with ![Dev container parameter screen](../../../../images/templates/devcontainers.png) -## Dev container lifecycle scripts +## Dev Container Lifecycle Scripts The `onCreateCommand`, `updateContentCommand`, `postCreateCommand`, and `postStartCommand` lifecycle scripts are run each time the container is started. @@ -141,7 +141,7 @@ a user begins using the workspace. Lifecycle scripts are managed by project developers. -## Next steps +## Next Steps - [Choose an approach to Dev Containers](../../extending-templates/dev-containers-envbuilder.md) - [Dev container security and caching](./devcontainer-security-caching.md) diff --git a/docs/admin/templates/managing-templates/devcontainers/devcontainer-releases-known-issues.md b/docs/admin/templates/managing-templates/devcontainers/devcontainer-releases-known-issues.md index 84a9ccd374fcf..6824d409dfe29 100644 --- a/docs/admin/templates/managing-templates/devcontainers/devcontainer-releases-known-issues.md +++ b/docs/admin/templates/managing-templates/devcontainers/devcontainer-releases-known-issues.md @@ -1,6 +1,6 @@ -# Envbuilder dev container releases and known issues +# Envbuilder Dev Container Releases and Known Issues -## Release channels +## Release Channels Envbuilder provides two release channels: @@ -18,7 +18,7 @@ Refer to the [Envbuilder GitHub repository](https://github.com/coder/envbuilder/) for more information and to submit feature requests or bug reports. -## Known issues +## Known Issues Visit the [Envbuilder repository](https://github.com/coder/envbuilder/blob/main/docs/devcontainer-spec-support.md) diff --git a/docs/admin/templates/managing-templates/devcontainers/devcontainer-security-caching.md b/docs/admin/templates/managing-templates/devcontainers/devcontainer-security-caching.md index 3e531f0fc90a7..7ce5e06287a3c 100644 --- a/docs/admin/templates/managing-templates/devcontainers/devcontainer-security-caching.md +++ b/docs/admin/templates/managing-templates/devcontainers/devcontainer-security-caching.md @@ -1,16 +1,16 @@ -# Envbuilder dev container security and caching +# Envbuilder Dev Container Security and Caching Envbuilder can pull only pre-approved images and artifacts when you configure it with your enterprise proxies, firewalls, and artifact managers. -## Configure registry authentication +## Configure Registry Authentication You may need to authenticate to your container registry, such as Artifactory, or Git provider such as GitLab, to use Envbuilder. See the [Envbuilder documentation](https://github.com/coder/envbuilder/blob/main/docs/container-registry-auth.md) for more information. -## Layer and image caching +## Layer and Image Caching To improve build times, dev containers can be cached. There are two main forms of caching: @@ -60,7 +60,7 @@ If you are building your own Dev container template, you can consult the You may also wish to consult a [documented example usage of the `envbuilder_cached_image` resource](https://github.com/coder/terraform-provider-envbuilder/blob/main/examples/resources/envbuilder_cached_image/envbuilder_cached_image_resource.tf). -## Next steps +## Next Steps - [Dev container releases and known issues](./devcontainer-releases-known-issues.md) - [Dotfiles](../../../../user-guides/workspace-dotfiles.md) diff --git a/docs/admin/templates/managing-templates/devcontainers/index.md b/docs/admin/templates/managing-templates/devcontainers/index.md index 3f7887b902fe4..3fe243588677e 100644 --- a/docs/admin/templates/managing-templates/devcontainers/index.md +++ b/docs/admin/templates/managing-templates/devcontainers/index.md @@ -59,7 +59,7 @@ open-source project. This means that Envbuilder can be used with Coder, but it is not required. It also means that dev container builds can scale independently of the Coder control plane and even run within a CI/CD pipeline. -## Next steps +## Next Steps - [Add an Envbuilder dev container template](./add-devcontainer.md) - [Choose an approach to Dev Containers](../../extending-templates/dev-containers-envbuilder.md) From e83d5647767c9b60f69190f07db58bafd466ccc0 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:52:56 +0000 Subject: [PATCH 22/36] leftover ea language --- docs/user-guides/devcontainers/index.md | 1 + .../working-with-dev-containers.md | 26 +++++-------------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/docs/user-guides/devcontainers/index.md b/docs/user-guides/devcontainers/index.md index da7942f0b70c5..ddf7411848395 100644 --- a/docs/user-guides/devcontainers/index.md +++ b/docs/user-guides/devcontainers/index.md @@ -85,3 +85,4 @@ If your template allows for prebuilt workspaces, do not select a prebuilt worksp - [Dev Container specification](https://containers.dev/) - [VS Code dev containers documentation](https://code.visualstudio.com/docs/devcontainers/containers) +- [Choose an approach to Dev Containers](../../admin/templates/extending-templates/dev-containers-envbuilder.md) diff --git a/docs/user-guides/devcontainers/working-with-dev-containers.md b/docs/user-guides/devcontainers/working-with-dev-containers.md index a4257f91d420e..db8c7d82e3958 100644 --- a/docs/user-guides/devcontainers/working-with-dev-containers.md +++ b/docs/user-guides/devcontainers/working-with-dev-containers.md @@ -5,6 +5,8 @@ visual representation of the running environment: ![Dev container integration in Coder dashboard](../../images/user-guides/devcontainers/devcontainer-agent-ports.png) +This page assumes you have a [dev containers integration](./index.md) ready. + ## SSH Access You can SSH into your dev container directly using the Coder CLI: @@ -42,31 +44,15 @@ work. ## Port Forwarding -During the early access phase, port forwarding is limited to ports defined via -[`appPort`](https://containers.dev/implementors/json_reference/#image-specific) -in your `devcontainer.json` file. - -> [!NOTE] -> -> Support for automatic port forwarding via the `forwardPorts` property in -> `devcontainer.json` is planned for a future release. - -For example, with this `devcontainer.json` configuration: - -```json -{ - "appPort": ["8080:8080", "4000:3000"] -} -``` - -You can forward these ports to your local machine using: +Coder automatically forwards any port declared in `appPort`, `forwardPorts`, +or exposed by `docker-compose.yml`. +Use the dashboard to open a forwarded port, or the CLI: ```console coder port-forward my-workspace --tcp 8080,4000 ``` -This forwards port 8080 (local) -> 8080 (agent) -> 8080 (dev container) and port -4000 (local) -> 4000 (agent) -> 3000 (dev container). +If you need a port that isn’t declared, pass it explicitly to `coder port-forward`. ## Dev Container Features From 03c60bfafb4198e0e0796081d5afece3ca566d04 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:58:28 +0000 Subject: [PATCH 23/36] update ssh note and example --- .../devcontainers/working-with-dev-containers.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/user-guides/devcontainers/working-with-dev-containers.md b/docs/user-guides/devcontainers/working-with-dev-containers.md index db8c7d82e3958..95f26084cd714 100644 --- a/docs/user-guides/devcontainers/working-with-dev-containers.md +++ b/docs/user-guides/devcontainers/working-with-dev-containers.md @@ -12,14 +12,19 @@ This page assumes you have a [dev containers integration](./index.md) ready. You can SSH into your dev container directly using the Coder CLI: ```console -coder ssh --container keen_dijkstra my-workspace +coder ssh --container my-container-name my-workspace ``` +Remember to replace: + +- `my-container-name` with the dev container agent name. +- `my-workspace` with your workspace's name. + > [!NOTE] > -> SSH access is not yet compatible with the `coder config-ssh` command for use -> with OpenSSH. You would need to manually modify your SSH config to include the -> `--container` flag in the `ProxyCommand`. +> Starting with Coder v2.24.0, `coder config-ssh` works with dev containers. +> If you’re using an older Coder version, add `--container ` to the +> `ProxyCommand` entry in your SSH config. ## Web Terminal Access From cfcef3c5e06be1ff55641f4e0ac67b6c0a169b51 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Fri, 27 Jun 2025 20:22:42 +0000 Subject: [PATCH 24/36] update example template --- .../extending-templates/devcontainers.md | 66 +++++++++++++------ 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/docs/admin/templates/extending-templates/devcontainers.md b/docs/admin/templates/extending-templates/devcontainers.md index 82d5e9ab7c51a..8da09c756b6b2 100644 --- a/docs/admin/templates/extending-templates/devcontainers.md +++ b/docs/admin/templates/extending-templates/devcontainers.md @@ -167,53 +167,79 @@ You can test the Coder dev container integration and features with these starter
Docker-based template (privileged) +This version uses a Docker-in-Docker image, so it works even if the host doesn’t expose a Docker socket. + ```terraform terraform { required_providers { - coder = { source = "coder/coder" } - docker = { source = "kreuzwerker/docker" } + coder = { + source = "coder/coder" + } + docker = { + source = "kreuzwerker/docker" + } } } -data "coder_workspace" "me" {} -data "coder_workspace_owner" "me" {} +provider "coder" {} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +resource "docker_container" "workspace" { + count = data.coder_workspace.me.start_count + image = "codercom/enterprise-base:ubuntu" # includes Coder agent + name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" + + # share host Docker + volumes { + host_path = "/var/run/docker.sock" + container_path = "/var/run/docker.sock" + } + + must_run = true # keep container alive + + env = [ + "CODER_AGENT_TOKEN=${coder_agent.main.token}", + "CODER_AGENT_URL=${data.coder_workspace.me.access_url}" # lets built-in entrypoint phone home + ] +} resource "coder_agent" "main" { os = "linux" arch = "amd64" - - startup_script_behavior = "blocking" - startup_script = "sudo service docker start" - shutdown_script = "sudo service docker stop" } -module "devcontainers_cli" { +# install node +module "nodejs" { count = data.coder_workspace.me.start_count - source = "dev.registry.coder.com/modules/devcontainers-cli/coder" + source = "dev.registry.coder.com/modules/nodejs/coder" agent_id = coder_agent.main.id } +module "devcontainers_cli" { + count = data.coder_workspace.me.start_count + source = "dev.registry.coder.com/modules/devcontainers-cli/coder" + agent_id = coder_agent.main.id + depends_on = [module.nodejs] # npm first +} + +# clone a repo module "git_clone" { count = data.coder_workspace.me.start_count source = "dev.registry.coder.com/modules/git-clone/coder" agent_id = coder_agent.main.id - url = "https://github.com/example/project.git" - base_dir = "/home/coder/project" + url = "https://github.com/devcontainers/template-starter.git" + base_dir = "/home/coder/project" } +# launch the Dev Container resource "coder_devcontainer" "project" { count = data.coder_workspace.me.start_count agent_id = coder_agent.main.id workspace_folder = "/home/coder/project/${module.git_clone[0].folder_name}" depends_on = [module.git_clone] } - -resource "docker_container" "workspace" { - count = data.coder_workspace.me.start_count - image = "codercom/enterprise-node:ubuntu" - name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" - privileged = true # or mount /var/run/docker.sock -} ```
@@ -249,7 +275,7 @@ module "git_clone" { count = data.coder_workspace.me.start_count source = "dev.registry.coder.com/modules/git-clone/coder" agent_id = coder_agent.main.id - url = "https://github.com/example/project.git" + url = "https://github.com/coder/coder.git" base_dir = "/home/coder/project" } From acdbe4d867ee4fabf7d5ab18d29f9db6ea303a6c Mon Sep 17 00:00:00 2001 From: Edward Angert Date: Tue, 1 Jul 2025 11:25:06 -0400 Subject: [PATCH 25/36] Apply suggestions from code review Co-authored-by: Mathias Fredriksson --- .../extending-templates/devcontainers.md | 19 ++++++++++--------- docs/user-guides/devcontainers/index.md | 4 ++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/admin/templates/extending-templates/devcontainers.md b/docs/admin/templates/extending-templates/devcontainers.md index 8da09c756b6b2..fef62e1a36633 100644 --- a/docs/admin/templates/extending-templates/devcontainers.md +++ b/docs/admin/templates/extending-templates/devcontainers.md @@ -97,13 +97,13 @@ module "git_clone" { source = "dev.registry.coder.com/modules/git-clone/coder" agent_id = coder_agent.main.id url = "https://github.com/example/project.git" - base_dir = "/home/coder/project" + base_dir = "/home/coder" } resource "coder_devcontainer" "project" { count = data.coder_workspace.me.start_count agent_id = coder_agent.main.id - workspace_folder = "/home/coder/project/${module.git_clone[0].folder_name}" + workspace_folder = "/home/coder/${module.git_clone[0].folder_name}" depends_on = [module.git_clone] } ``` @@ -119,8 +119,9 @@ For more advanced use cases, consult the [advanced dev containers doc](./advance { "customizations": { "coder": { - "apps": { - "flask-app": { + "apps": [ + { + "slug": "flask-app", "command": "python app.py", "icon": "/icon/flask.svg", "subdomain": true, @@ -130,7 +131,7 @@ For more advanced use cases, consult the [advanced dev containers doc](./advance "threshold": 10 } } - } + ] } } } @@ -140,10 +141,10 @@ For more advanced use cases, consult the [advanced dev containers doc](./advance Coder names dev container agents in this order: -1. `customizations.coder.agent.name` in `devcontainer.json` -1. `name` in `devcontainer.json` -1. Directory name that contains the config -1. `devcontainer` (default) +1. `customizations.coder.name` in `devcontainer.json` +1. Resource name used in terraform (`resource "coder_devcontainer" "name"`) +1. Project directory name (name of folder containing `devcontainer.json` or `.devcontainer` folder) +1. If project directory name is already taken, the name is expanded to include the parent folder (`/home/coder/some/project` -> `project` (taken) -> `some-project`) ### Multiple dev containers diff --git a/docs/user-guides/devcontainers/index.md b/docs/user-guides/devcontainers/index.md index ddf7411848395..5a8d9e3e7af01 100644 --- a/docs/user-guides/devcontainers/index.md +++ b/docs/user-guides/devcontainers/index.md @@ -40,8 +40,8 @@ When a workspace with the dev containers integration starts: ### Connectivity -- Full SSH access directly into dev containers (`coder ssh --container ...`). -- Automatic port forwarding based on `appPort`, `forwardPorts`, or `docker-compose.yml`. +- Full SSH access directly into dev containers (`coder ssh .`). +- Automatic port forwarding. ## Personal overrides From d3fd3b8e1105d77815c22c5e3fbb3e2067b5cf5e Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Tue, 1 Jul 2025 16:04:00 +0000 Subject: [PATCH 26/36] edits from code review --- .../advanced-dev-containers.md | 18 ------------------ .../dev-containers-envbuilder.md | 1 - .../managing-templates/devcontainers/index.md | 17 +---------------- docs/user-guides/devcontainers/index.md | 18 ------------------ 4 files changed, 1 insertion(+), 53 deletions(-) diff --git a/docs/admin/templates/extending-templates/advanced-dev-containers.md b/docs/admin/templates/extending-templates/advanced-dev-containers.md index 4a2daec8a4fc5..44d1c1047a0c3 100644 --- a/docs/admin/templates/extending-templates/advanced-dev-containers.md +++ b/docs/admin/templates/extending-templates/advanced-dev-containers.md @@ -67,24 +67,6 @@ resource "coder_devcontainer" "database" { Each dev container appears as a separate agent, so developers can connect to any component in the workspace. -## Personal Overrides - -Let developers extend the repo’s `devcontainer.json` with an ignored (by Git) `devcontainer.local.json` file -so they can add personal tools without changing the canonical configuration: - -```jsonc -{ - "extends": "./devcontainer.json", - "features": { - "ghcr.io/devcontainers/features/node": { "version": "20" } - }, - "postStartCommand": "npm i -g tldr" -} -``` - -Add the file name to your project's `.gitignore` or the user's -[global exclude file](https://docs.github.com/en/get-started/git-basics/ignoring-files#configuring-ignored-files-for-all-repositories-on-your-computer). - ## Conditional Startup Use `coder_parameter` booleans to let workspace creators choose which dev containers start automatically, diff --git a/docs/admin/templates/extending-templates/dev-containers-envbuilder.md b/docs/admin/templates/extending-templates/dev-containers-envbuilder.md index 07c15d8e352ce..c52d9f7fbd198 100644 --- a/docs/admin/templates/extending-templates/dev-containers-envbuilder.md +++ b/docs/admin/templates/extending-templates/dev-containers-envbuilder.md @@ -16,7 +16,6 @@ Use this page to decide which path fits your project or platform needs. | Multiple Dev Containers per workspace | Yes | No | | Rebuild when `devcontainer.json` changes | Yes (auto-prompt) | Limited (requires full workspace rebuild) | | Docker required in workspace | Yes | No (works in restricted envs) | -| Admin vs. developer control | Developer decides per repo | Platform admin manages via template | | Templates | Standard `devcontainer.json` | Terraform + Envbuilder blocks | | Suitable for CI / AI agents | Yes. Deterministic, composable | Less ideal. No isolated container | diff --git a/docs/admin/templates/managing-templates/devcontainers/index.md b/docs/admin/templates/managing-templates/devcontainers/index.md index 3fe243588677e..9b1c00a58701e 100644 --- a/docs/admin/templates/managing-templates/devcontainers/index.md +++ b/docs/admin/templates/managing-templates/devcontainers/index.md @@ -23,22 +23,7 @@ For the Docker-based Dev Containers integration, follow the [Configure a templat An administrator should construct or choose a base image and create a template that includes an Envbuilder container image `coder/envbuilder` before a developer team configures dev containers. -## Benefits of Envbuilder - -Key differences compared with the [Docker-based integration](../../extending-templates/devcontainers.md): - -| Capability / Trait | Dev Containers integration (CLI-based) | Envbuilder Dev Containers | -|------------------------------------------|------------------------------------------|-------------------------------------------| -| Build engine | `@devcontainers/cli` + Docker | Envbuilder transforms the workspace image | -| Runs separate Docker container | Yes (parent workspace + child container) | No (modifies the parent container) | -| Multiple Dev Containers per workspace | Yes | No | -| Rebuild when `devcontainer.json` changes | Yes (auto-prompt) | Limited (requires full workspace rebuild) | -| Docker required in workspace | Yes | No (works in restricted envs) | -| Admin vs. developer control | Developer decides per repo | Platform admin manages via template | -| Templates | Standard `devcontainer.json` | Terraform + Envbuilder blocks | -| Suitable for CI / AI agents | Yes. Deterministic, composable | Less ideal. No isolated container | - -Consult the full comparison at [Choose an approach to Dev Containers](../../extending-templates/dev-containers-envbuilder.md). +Compare the differences between [Envbuilder and the Dev Containers integration](../../extending-templates/dev-containers-envbuilder.md). ## Dev container Features diff --git a/docs/user-guides/devcontainers/index.md b/docs/user-guides/devcontainers/index.md index 5a8d9e3e7af01..0934f8b5e7cb0 100644 --- a/docs/user-guides/devcontainers/index.md +++ b/docs/user-guides/devcontainers/index.md @@ -43,24 +43,6 @@ When a workspace with the dev containers integration starts: - Full SSH access directly into dev containers (`coder ssh .`). - Automatic port forwarding. -## Personal overrides - -To add tools or tweaks that enhance your personal experience, create a `devcontainer.local.json` file in the same -directory as the project’s `devcontainer.json`: - -```jsonc -{ - "extends": "./devcontainer.json", - "features": { - "ghcr.io/devcontainers/features/node": { "version": "20" } - }, - "postStartCommand": "npm i -g tldr" -} -``` - -Add the file name to your project's `.gitignore` or to your -[global exclude file](https://docs.github.com/en/get-started/git-basics/ignoring-files#configuring-ignored-files-for-all-repositories-on-your-computer). - ## Comparison with Envbuilder-based Dev Containers | Feature | Dev Containers | Envbuilder Dev Containers | From 6ee9fd4da5c68f2db46c3226f3c92f38262dbd17 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Tue, 1 Jul 2025 16:58:47 +0000 Subject: [PATCH 27/36] remove full examples; adjustments from review --- .../advanced-dev-containers.md | 4 - .../extending-templates/devcontainers.md | 186 +----------------- 2 files changed, 6 insertions(+), 184 deletions(-) diff --git a/docs/admin/templates/extending-templates/advanced-dev-containers.md b/docs/admin/templates/extending-templates/advanced-dev-containers.md index 44d1c1047a0c3..54ff99133620b 100644 --- a/docs/admin/templates/extending-templates/advanced-dev-containers.md +++ b/docs/admin/templates/extending-templates/advanced-dev-containers.md @@ -46,21 +46,18 @@ resource "coder_devcontainer" "frontend" { count = data.coder_workspace.me.start_count agent_id = coder_agent.main.id workspace_folder = "/home/coder/frontend/${module.git_clone_frontend[0].folder_name}" - depends_on = [module.git_clone_frontend] } resource "coder_devcontainer" "backend" { count = data.coder_workspace.me.start_count agent_id = coder_agent.main.id workspace_folder = "/home/coder/backend/${module.git_clone_backend[0].folder_name}" - depends_on = [module.git_clone_backend] } resource "coder_devcontainer" "database" { count = data.coder_workspace.me.start_count agent_id = coder_agent.main.id workspace_folder = "/home/coder/database/${module.git_clone_database[0].folder_name}" - depends_on = [module.git_clone_database] } ``` @@ -85,7 +82,6 @@ resource "coder_devcontainer" "frontend" { count = data.coder_parameter.enable_frontend.value ? data.coder_workspace.me.start_count : 0 agent_id = coder_agent.main.id workspace_folder = "/home/coder/frontend/${module.git_clone_frontend[0].folder_name}" - depends_on = [module.git_clone_frontend] } ``` diff --git a/docs/admin/templates/extending-templates/devcontainers.md b/docs/admin/templates/extending-templates/devcontainers.md index fef62e1a36633..9309b6bc12cff 100644 --- a/docs/admin/templates/extending-templates/devcontainers.md +++ b/docs/admin/templates/extending-templates/devcontainers.md @@ -31,26 +31,7 @@ the Dev Container integration and the legacy Envbuilder integration. - The `devcontainers-cli` module requires npm. - - Use an image that already includes npm, such as `codercom/enterprise-node:ubuntu` - -
If your template doesn't already include npm, install it at runtime with the `nodejs` module: - - 1. This block should be before the `devcontainers-cli` block in `main.tf`: - - ```terraform - module "nodejs" { - count = data.coder_workspace.me.start_count - source = "dev.registry.coder.com/modules/nodejs/coder" - agent_id = coder_agent.main.id - } - ``` - - 1. In the `devcontainers-cli` module block, add: - - ```terraform - depends_on = [module.nodejs] - ``` - -
+ - You can use an image that already includes npm, such as `codercom/enterprise-node:ubuntu`. ## Install the Dev Containers CLI @@ -66,7 +47,7 @@ module "devcontainers-cli" { } ``` -Alternatively, install the devcontainer CLI manually in your base image: +Alternatively, install `devcontainer/cli` manually in your base image: ```shell RUN npm install -g @devcontainers/cli @@ -77,7 +58,7 @@ RUN npm install -g @devcontainers/cli If you don't use [`git_clone`](#clone-the-repository), point the resource at the folder that contains `devcontainer.json`: ```terraform -resource "coder_devcontainer" "project" { +resource "coder_devcontainer" "project" { # `project` in this example is how users will connect to the dev container: `ssh://project..me.coder` count = data.coder_workspace.me.start_count agent_id = coder_agent.main.id workspace_folder = "/home/coder/project" @@ -88,8 +69,8 @@ resource "coder_devcontainer" "project" { This step is optional, but it ensures that the project is present before the dev container starts. -Note that if you use the `git_clone` module, place it before the `coder_devcontainer` resource -and update or replace that resource to point at `/home/coder/project/${module.git_clone[0].folder_name}` so that it is only defined once: +Note that if you use the `git_clone` module, update or replace the `coder_devcontainer` resource +to point to `/home/coder/project/${module.git_clone[0].folder_name}` so that it is only defined once: ```terraform module "git_clone" { @@ -104,7 +85,6 @@ resource "coder_devcontainer" "project" { count = data.coder_workspace.me.start_count agent_id = coder_agent.main.id workspace_folder = "/home/coder/${module.git_clone[0].folder_name}" - depends_on = [module.git_clone] } ``` @@ -162,165 +142,11 @@ resource "coder_devcontainer" "backend" { } ``` -## Complete Template Examples - -You can test the Coder dev container integration and features with these starter templates. - -
Docker-based template (privileged) - -This version uses a Docker-in-Docker image, so it works even if the host doesn’t expose a Docker socket. - -```terraform -terraform { - required_providers { - coder = { - source = "coder/coder" - } - docker = { - source = "kreuzwerker/docker" - } - } -} - -provider "coder" {} - -data "coder_workspace" "me" {} -data "coder_workspace_owner" "me" {} - -resource "docker_container" "workspace" { - count = data.coder_workspace.me.start_count - image = "codercom/enterprise-base:ubuntu" # includes Coder agent - name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" - - # share host Docker - volumes { - host_path = "/var/run/docker.sock" - container_path = "/var/run/docker.sock" - } - - must_run = true # keep container alive - - env = [ - "CODER_AGENT_TOKEN=${coder_agent.main.token}", - "CODER_AGENT_URL=${data.coder_workspace.me.access_url}" # lets built-in entrypoint phone home - ] -} - -resource "coder_agent" "main" { - os = "linux" - arch = "amd64" -} - -# install node -module "nodejs" { - count = data.coder_workspace.me.start_count - source = "dev.registry.coder.com/modules/nodejs/coder" - agent_id = coder_agent.main.id -} - -module "devcontainers_cli" { - count = data.coder_workspace.me.start_count - source = "dev.registry.coder.com/modules/devcontainers-cli/coder" - agent_id = coder_agent.main.id - depends_on = [module.nodejs] # npm first -} - -# clone a repo -module "git_clone" { - count = data.coder_workspace.me.start_count - source = "dev.registry.coder.com/modules/git-clone/coder" - agent_id = coder_agent.main.id - url = "https://github.com/devcontainers/template-starter.git" - base_dir = "/home/coder/project" -} - -# launch the Dev Container -resource "coder_devcontainer" "project" { - count = data.coder_workspace.me.start_count - agent_id = coder_agent.main.id - workspace_folder = "/home/coder/project/${module.git_clone[0].folder_name}" - depends_on = [module.git_clone] -} -``` - -
- -
Kubernetes-based template (Sysbox runtime) - -```terraform -terraform { - required_providers { - coder = { source = "coder/coder" } - kubernetes = { source = "hashicorp/kubernetes" } - } -} - -data "coder_workspace" "me" {} -data "coder_workspace_owner" "me" {} - -resource "coder_agent" "main" { - os = "linux" - arch = "amd64" - - startup_script_behavior = "blocking" - startup_script = "sudo service docker start" -} - -module "devcontainers_cli" { - count = data.coder_workspace.me.start_count - source = "dev.registry.coder.com/modules/devcontainers-cli/coder" - agent_id = coder_agent.main.id -} - -module "git_clone" { - count = data.coder_workspace.me.start_count - source = "dev.registry.coder.com/modules/git-clone/coder" - agent_id = coder_agent.main.id - url = "https://github.com/coder/coder.git" - base_dir = "/home/coder/project" -} - -resource "coder_devcontainer" "project" { - count = data.coder_workspace.me.start_count - agent_id = coder_agent.main.id - workspace_folder = "/home/coder/project/${module.git_clone[0].folder_name}" - depends_on = [module.git_clone] -} - -resource "kubernetes_pod" "workspace" { - count = data.coder_workspace.me.start_count - - metadata { - name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" - namespace = "coder-workspaces" - } - - spec { - container { - name = "main" - image = "codercom/enterprise-base:ubuntu" - - security_context { privileged = true } # or use Sysbox / rootless - env { name = "CODER_AGENT_TOKEN" value = coder_agent.main.token } - } - } -} -``` - -
- ## Troubleshoot Common Issues ### Disable dev containers integration -To disable the dev containers integration in your workspace, add the `CODER_AGENT_DEVCONTAINERS_ENABLE` environment variable to your existing `coder_agent` block: - -```terraform -env = { - CODER_AGENT_DEVCONTAINERS_ENABLE = "false" - # existing variables ... -} -``` +To disable the dev containers integration in your workspace, set the `CODER_AGENT_DEVCONTAINERS_ENABLE= "false"` environment variable. ### Dev container does not start From 30372fca3e752369b1dfd3ed895130ade8872989 Mon Sep 17 00:00:00 2001 From: Edward Angert Date: Wed, 2 Jul 2025 13:00:59 -0400 Subject: [PATCH 28/36] Apply suggestions from code review Co-authored-by: Danielle Maywood --- .../templates/extending-templates/advanced-dev-containers.md | 2 +- .../user-guides/devcontainers/troubleshooting-dev-containers.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/admin/templates/extending-templates/advanced-dev-containers.md b/docs/admin/templates/extending-templates/advanced-dev-containers.md index 54ff99133620b..4c31e863aa92a 100644 --- a/docs/admin/templates/extending-templates/advanced-dev-containers.md +++ b/docs/admin/templates/extending-templates/advanced-dev-containers.md @@ -196,5 +196,5 @@ resource "docker_container" "workspace" { ## Troubleshooting 1. Run `docker ps` inside the workspace to ensure Docker is available. -1. Check `/tmp/startup.log` for agent logs. +1. Check `/tmp/coder-agent.log` for agent logs. 1. Verify the workspace image includes Node/npm or add the `nodejs` module before the `devcontainers_cli` module. diff --git a/docs/user-guides/devcontainers/troubleshooting-dev-containers.md b/docs/user-guides/devcontainers/troubleshooting-dev-containers.md index 4d186e7b77f3a..0912b134efd3c 100644 --- a/docs/user-guides/devcontainers/troubleshooting-dev-containers.md +++ b/docs/user-guides/devcontainers/troubleshooting-dev-containers.md @@ -22,7 +22,7 @@ If your dev container fails to start: ## Rebuild prompt does not appear -1. Confirm that you saved `devcontainer.json` (or changes to `devcontainer.local.json`) in the correct repo path detected by Coder. +1. Confirm that you saved `devcontainer.json` in the correct repo path detected by Coder. 1. Run `coder devcontainer rebuild` manually. 1. Check agent logs for `devcontainer build` errors. From f7c52729a6947e4108fcb2dae9b52f42aa1f949e Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Wed, 2 Jul 2025 20:24:39 +0000 Subject: [PATCH 29/36] edit dev-containers-envbuilder --- .../dev-containers-envbuilder.md | 31 +++++-------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/docs/admin/templates/extending-templates/dev-containers-envbuilder.md b/docs/admin/templates/extending-templates/dev-containers-envbuilder.md index c52d9f7fbd198..898f0d8d4975b 100644 --- a/docs/admin/templates/extending-templates/dev-containers-envbuilder.md +++ b/docs/admin/templates/extending-templates/dev-containers-envbuilder.md @@ -19,28 +19,6 @@ Use this page to decide which path fits your project or platform needs. | Templates | Standard `devcontainer.json` | Terraform + Envbuilder blocks | | Suitable for CI / AI agents | Yes. Deterministic, composable | Less ideal. No isolated container | -## When To Choose the Dev Containers Integration - -Choose the new integration if: - -- Your workspace image can run Docker (DinD, Sysbox, or a mounted Docker socket). -- You need multiple Dev Containers (like `frontend`, `backend`, `db`) in a single workspace. -- Developers should own their environment and rebuild on demand. -- You rely on features such as automatic port forwarding, full SSH into containers, or change-detection prompts. - -[Dev Container integration](./devcontainers.md) documentation. - -## When To Choose Envbuilder - -Envbuilder remains a solid choice when: - -- Docker isn’t available or allowed inside the workspace image. -- The platform team wants tight control over container contents via Terraform. -- A single layered environment is sufficient (no need for separate sub-containers). -- You already have Envbuilder templates in production and they meet current needs. - -[Envbuilder Dev Container](../managing-templates/devcontainers/add-devcontainer.md#envbuilder-terraform-provider) documentation. - ## How To Migrate From Envbuilder to the Dev Containers Integration 1. Ensure the workspace image can run Docker and has sufficient resources: @@ -51,13 +29,20 @@ Envbuilder remains a solid choice when: 1. Remove any Envbuilder blocks that reference `coder_dev_envbuilder` from the template. 1. Add (or keep) a standard `.devcontainer/` folder with `devcontainer.json` in the repository. -1. Add the `devcontainers-cli` module: +1. Follow the [dev containers documentation](./devcontainers.md) for the full list of steps and options. + + At minimum, add the `devcontainers-cli` module and `coder_devcontainer` resource: ```terraform module "devcontainers_cli" { source = "dev.registry.coder.com/modules/devcontainers-cli/coder" agent_id = coder_agent.main.id } + resource "coder_devcontainer" "project" { # `project` in this example is how users will connect to the dev container: `ssh://project..me.coder` + count = data.coder_workspace.me.start_count + agent_id = coder_agent.main.id + workspace_folder = "/home/coder/project" + } ``` 1. Start a new workspace. From 9eb367d39ffeec68337d10339ea50b05234d9da7 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Wed, 2 Jul 2025 20:43:18 +0000 Subject: [PATCH 30/36] edit devcontainers doc --- docs/admin/templates/extending-templates/devcontainers.md | 1 - docs/user-guides/devcontainers/index.md | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/admin/templates/extending-templates/devcontainers.md b/docs/admin/templates/extending-templates/devcontainers.md index 9309b6bc12cff..21ba2a527a912 100644 --- a/docs/admin/templates/extending-templates/devcontainers.md +++ b/docs/admin/templates/extending-templates/devcontainers.md @@ -122,7 +122,6 @@ For more advanced use cases, consult the [advanced dev containers doc](./advance Coder names dev container agents in this order: 1. `customizations.coder.name` in `devcontainer.json` -1. Resource name used in terraform (`resource "coder_devcontainer" "name"`) 1. Project directory name (name of folder containing `devcontainer.json` or `.devcontainer` folder) 1. If project directory name is already taken, the name is expanded to include the parent folder (`/home/coder/some/project` -> `project` (taken) -> `some-project`) diff --git a/docs/user-guides/devcontainers/index.md b/docs/user-guides/devcontainers/index.md index 0934f8b5e7cb0..34a64a8707485 100644 --- a/docs/user-guides/devcontainers/index.md +++ b/docs/user-guides/devcontainers/index.md @@ -36,11 +36,11 @@ When a workspace with the dev containers integration starts: - Automatic discovery of `.devcontainer` configs. - Change detection with rebuild prompts. -- Rebuild containers with one click from the Coder dashboard or from the CLI. +- Rebuild containers with one click from the Coder dashboard. ### Connectivity -- Full SSH access directly into dev containers (`coder ssh .`). +- Full SSH access directly into dev containers (`coder ssh ..me.coder`). - Automatic port forwarding. ## Comparison with Envbuilder-based Dev Containers @@ -59,7 +59,7 @@ Visit [Choose an approach to Dev Containers](../../admin/templates/extending-tem ## Known Limitations -Currently, dev containers are not compatible with the [prebuilt workspaces](../../admin/templates/extending-templates/prebuilt-workspaces.md). +Currently, dev containers are not compatible with [prebuilt workspaces](../../admin/templates/extending-templates/prebuilt-workspaces.md). If your template allows for prebuilt workspaces, do not select a prebuilt workspace if you plan to use a dev container. From abd91a2726f475bb8a04b83c07e9a05ff8ab7b3b Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Wed, 2 Jul 2025 21:15:44 +0000 Subject: [PATCH 31/36] changes from code review --- .../devcontainers/troubleshooting-dev-containers.md | 2 -- docs/user-guides/devcontainers/working-with-dev-containers.md | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/user-guides/devcontainers/troubleshooting-dev-containers.md b/docs/user-guides/devcontainers/troubleshooting-dev-containers.md index 0912b134efd3c..8e9b869330d4e 100644 --- a/docs/user-guides/devcontainers/troubleshooting-dev-containers.md +++ b/docs/user-guides/devcontainers/troubleshooting-dev-containers.md @@ -12,7 +12,6 @@ If your dev container fails to start: - `/tmp/coder-agent.log` - `/tmp/coder-startup-script.log` - `/tmp/coder-script-[script_id].log` - - `/tmp/devcontainer-build.log` 1. Verify that Docker is running in your workspace. 1. Ensure the `devcontainer.json` file is valid. @@ -23,7 +22,6 @@ If your dev container fails to start: ## Rebuild prompt does not appear 1. Confirm that you saved `devcontainer.json` in the correct repo path detected by Coder. -1. Run `coder devcontainer rebuild` manually. 1. Check agent logs for `devcontainer build` errors. ## Known Limitations diff --git a/docs/user-guides/devcontainers/working-with-dev-containers.md b/docs/user-guides/devcontainers/working-with-dev-containers.md index 95f26084cd714..de2551768fd1d 100644 --- a/docs/user-guides/devcontainers/working-with-dev-containers.md +++ b/docs/user-guides/devcontainers/working-with-dev-containers.md @@ -49,8 +49,7 @@ work. ## Port Forwarding -Coder automatically forwards any port declared in `appPort`, `forwardPorts`, -or exposed by `docker-compose.yml`. +Coder automatically forwards any port declared in `appPort` or `forwardPorts`. Use the dashboard to open a forwarded port, or the CLI: ```console From 032edc3b33d74ae9d22c7e9804b193296fbe6a69 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:09:04 +0000 Subject: [PATCH 32/36] edits --- .../extending-templates/devcontainers.md | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/docs/admin/templates/extending-templates/devcontainers.md b/docs/admin/templates/extending-templates/devcontainers.md index 21ba2a527a912..3f6fc2c852767 100644 --- a/docs/admin/templates/extending-templates/devcontainers.md +++ b/docs/admin/templates/extending-templates/devcontainers.md @@ -123,7 +123,9 @@ Coder names dev container agents in this order: 1. `customizations.coder.name` in `devcontainer.json` 1. Project directory name (name of folder containing `devcontainer.json` or `.devcontainer` folder) -1. If project directory name is already taken, the name is expanded to include the parent folder (`/home/coder/some/project` -> `project` (taken) -> `some-project`) +1. If the project directory name is already taken, the name is expanded to include the parent folder. + + For example, if the path is `/home/coder/some/project` and `project` is taken, then the agent is `some-project`. ### Multiple dev containers @@ -141,6 +143,67 @@ resource "coder_devcontainer" "backend" { } ``` +## Example Docker Dev Container + +
Expand for the full file: + +```terraform +terraform { + required_providers { + coder = { source = "coder/coder" } + docker = { source = "kreuzwerker/docker" } + } +} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +resource "coder_agent" "main" { + os = "linux" + arch = "amd64" + + startup_script_behavior = "blocking" + startup_script = "sudo service docker start" + shutdown_script = "sudo service docker stop" +} + +module "devcontainers-cli" { + count = data.coder_workspace.me.start_count + source = "dev.registry.coder.com/modules/devcontainers-cli/coder" + agent_id = coder_agent.main.id +} + +module "git_clone" { + count = data.coder_workspace.me.start_count + source = "dev.registry.coder.com/modules/git-clone/coder" + agent_id = coder_agent.main.id + url = "https://github.com/coder/coder.git" + base_dir = "/home/coder" +} + +resource "coder_devcontainer" "project" { + count = data.coder_workspace.me.start_count + agent_id = coder_agent.main.id + workspace_folder = "/home/coder/${module.git_clone[0].folder_name}" +} + +resource "docker_container" "workspace" { + count = data.coder_workspace.me.start_count + image = "codercom/enterprise-node:ubuntu" + name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" + + runtime = "sysbox-runc" + + entrypoint = ["sh", "-c", coder_agent.main.init_script] + + env = [ + "CODER_AGENT_TOKEN=${coder_agent.main.token}", + "CODER_AGENT_URL=${data.coder_workspace.me.access_url}", + "CODER_AGENT_DEVCONTAINERS_ENABLE=true" + ] +} +``` + ## Troubleshoot Common Issues ### Disable dev containers integration From 67702ae55f4d5b7d85a96deb9e0f10a455ad1a1e Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Thu, 3 Jul 2025 20:17:56 +0000 Subject: [PATCH 33/36] tested configs; remove untested --- .../advanced-dev-containers.md | 159 ++++-------------- 1 file changed, 35 insertions(+), 124 deletions(-) diff --git a/docs/admin/templates/extending-templates/advanced-dev-containers.md b/docs/admin/templates/extending-templates/advanced-dev-containers.md index 4c31e863aa92a..b71646c61b15e 100644 --- a/docs/admin/templates/extending-templates/advanced-dev-containers.md +++ b/docs/admin/templates/extending-templates/advanced-dev-containers.md @@ -7,14 +7,13 @@ user-controlled startup, repository selection, and infrastructure tuning. Run independent dev containers in the same workspace so each component appears as its own agent. -In this example, there are three: `frontend`, `backend`, and a `database`: +In this example, there are two: `frontend` and `backend`: ```terraform # Clone each repo module "git_clone_frontend" { count = data.coder_workspace.me.start_count - source = "registry.coder.com/modules/git-clone/coder" - version = "~> 1.0" + source = "dev.registry.coder.com/modules/git-clone/coder" agent_id = coder_agent.main.id url = "https://github.com/your-org/frontend.git" @@ -23,24 +22,13 @@ module "git_clone_frontend" { module "git_clone_backend" { count = data.coder_workspace.me.start_count - source = "registry.coder.com/modules/git-clone/coder" - version = "~> 1.0" + source = "dev.registry.coder.com/modules/git-clone/coder" agent_id = coder_agent.main.id url = "https://github.com/your-org/backend.git" base_dir = "/home/coder/backend" } -module "git_clone_database" { - count = data.coder_workspace.me.start_count - source = "registry.coder.com/modules/git-clone/coder" - version = "~> 1.0" - - agent_id = coder_agent.main.id - url = "https://github.com/your-org/database.git" - base_dir = "/home/coder/database" -} - # Dev container resources resource "coder_devcontainer" "frontend" { count = data.coder_workspace.me.start_count @@ -54,11 +42,6 @@ resource "coder_devcontainer" "backend" { workspace_folder = "/home/coder/backend/${module.git_clone_backend[0].folder_name}" } -resource "coder_devcontainer" "database" { - count = data.coder_workspace.me.start_count - agent_id = coder_agent.main.id - workspace_folder = "/home/coder/database/${module.git_clone_database[0].folder_name}" -} ``` Each dev container appears as a separate agent, so developers can connect to any @@ -89,112 +72,40 @@ resource "coder_devcontainer" "frontend" { Prompt users to pick a repository or team at workspace creation time and clone the selected repo(s) automatically into the workspace: -### Dropdown selector - -```terraform -data "coder_parameter" "project" { - name = "Project" - description = "Choose a project" - type = "string" - mutable = true - order = 1 - - option { name = "E-commerce FE" value = "https://github.com/org/ecom-fe.git" icon = "/icon/react.svg" } - option { name = "Payment API" value = "https://github.com/org/pay.git" icon = "/icon/nodejs.svg" } -} - -module "git_clone_selected" { - count = data.coder_workspace.me.start_count - source = "registry.coder.com/modules/git-clone/coder" - version = "~> 1.0" - - agent_id = coder_agent.main.id - url = data.coder_parameter.project.value - base_dir = "/home/coder/project" -} -``` - -### Team-based selection - -```terraform -data "coder_parameter" "team" { - name = "Team" - type = "string" - mutable = true - order = 1 - - option { name = "Frontend" value = "frontend" icon = "/icon/react.svg" } - option { name = "Backend" value = "backend" icon = "/icon/nodejs.svg" } -} - -locals { - repos = { - frontend = ["https://github.com/your-org/web.git"] - backend = ["https://github.com/your-org/api.git"] - } -} - -module "git_clone_team" { - count = length(local.repos[data.coder_parameter.team.value]) * data.coder_workspace.me.start_count - source = "registry.coder.com/modules/git-clone/coder" - version = "~> 1.0" - - agent_id = coder_agent.main.id - url = local.repos[data.coder_parameter.team.value][count.index] - base_dir = "/home/coder/${replace(basename(url), \".git\", \"\")}" -} -``` - -## Infrastructure Tuning - -Adjust workspace infrastructure to set memory/CPU limits, attach a custom Docker network, -or add persistent volumes—to improve performance and isolation for dev containers: - -### Resource limits - -```terraform -resource "docker_container" "workspace" { - count = data.coder_workspace.me.start_count - image = "codercom/enterprise-node:ubuntu" - - resources { - memory = 4096 # MiB - cpus = 2 - memory_swap = 8192 - } -} -``` - -### Custom network - -```terraform -resource "docker_network" "dev" { - name = "coder-${data.coder_workspace.me.id}-dev" -} - -resource "docker_container" "workspace" { - networks_advanced { name = docker_network.dev.name } -} -``` - -### Volume caching - -```terraform -resource "docker_volume" "node_modules" { - name = "coder-${data.coder_workspace.me.id}-node-modules" - lifecycle { ignore_changes = all } -} - -resource "docker_container" "workspace" { - volumes { - container_path = "/home/coder/project/node_modules" - volume_name = docker_volume.node_modules.name - } -} -``` +1. Add a parameter to the template: + + ```terraform + data "coder_parameter" "project" { + name = "project" + display_name = "Choose a project" + type = "string" + default = "https://github.com/coder/coder.git" + + option { + name = "coder/coder" + value = "https://github.com/coder/coder.git" + } + option { + name = "Dev Container template" + value = "https://github.com/devcontainers/template-starter.git" + } + } + ``` + +1. Change the `git_clone` module to accept the `value` as the `url`: + + ```terraform + module "git_clone" { + count = data.coder_workspace.me.start_count + source = "dev.registry.coder.com/modules/git-clone/coder" + agent_id = coder_agent.main.id + url = data.coder_parameter.project.value + base_dir = "/home/coder" + } + ``` ## Troubleshooting 1. Run `docker ps` inside the workspace to ensure Docker is available. 1. Check `/tmp/coder-agent.log` for agent logs. -1. Verify the workspace image includes Node/npm or add the `nodejs` module before the `devcontainers_cli` module. +1. Verify that the workspace image includes Node/npm. From 11891da1496d40410171ce88093abb4ee7e2b9e0 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Wed, 9 Jul 2025 05:24:38 +0000 Subject: [PATCH 34/36] consistency and words --- .../advanced-dev-containers.md | 17 +++++++------ .../extending-templates/devcontainers.md | 24 ++++++++++--------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/docs/admin/templates/extending-templates/advanced-dev-containers.md b/docs/admin/templates/extending-templates/advanced-dev-containers.md index b71646c61b15e..269dd3477aa58 100644 --- a/docs/admin/templates/extending-templates/advanced-dev-containers.md +++ b/docs/admin/templates/extending-templates/advanced-dev-containers.md @@ -1,6 +1,6 @@ # Advanced Dev Container Configuration -This page extends [devcontainers.md](./devcontainers.md) with patterns for multiple dev containers, +This page extends the [devcontainers documentation](./devcontainers.md) with patterns for multiple dev containers, user-controlled startup, repository selection, and infrastructure tuning. ## Run Multiple Dev Containers @@ -11,7 +11,7 @@ In this example, there are two: `frontend` and `backend`: ```terraform # Clone each repo -module "git_clone_frontend" { +module "git-clone-frontend" { count = data.coder_workspace.me.start_count source = "dev.registry.coder.com/modules/git-clone/coder" @@ -20,7 +20,7 @@ module "git_clone_frontend" { base_dir = "/home/coder/frontend" } -module "git_clone_backend" { +module "git-clone-backend" { count = data.coder_workspace.me.start_count source = "dev.registry.coder.com/modules/git-clone/coder" @@ -33,15 +33,14 @@ module "git_clone_backend" { resource "coder_devcontainer" "frontend" { count = data.coder_workspace.me.start_count agent_id = coder_agent.main.id - workspace_folder = "/home/coder/frontend/${module.git_clone_frontend[0].folder_name}" + workspace_folder = "/home/coder/frontend/${module.git-clone-frontend[0].folder_name}" } resource "coder_devcontainer" "backend" { count = data.coder_workspace.me.start_count agent_id = coder_agent.main.id - workspace_folder = "/home/coder/backend/${module.git_clone_backend[0].folder_name}" + workspace_folder = "/home/coder/backend/${module.git-clone-backend[0].folder_name}" } - ``` Each dev container appears as a separate agent, so developers can connect to any @@ -64,7 +63,7 @@ data "coder_parameter" "enable_frontend" { resource "coder_devcontainer" "frontend" { count = data.coder_parameter.enable_frontend.value ? data.coder_workspace.me.start_count : 0 agent_id = coder_agent.main.id - workspace_folder = "/home/coder/frontend/${module.git_clone_frontend[0].folder_name}" + workspace_folder = "/home/coder/frontend/${module.git-clone-frontend[0].folder_name}" } ``` @@ -92,10 +91,10 @@ Prompt users to pick a repository or team at workspace creation time and clone t } ``` -1. Change the `git_clone` module to accept the `value` as the `url`: +1. Change the `git-clone` module to accept the `value` as the `url`: ```terraform - module "git_clone" { + module "git-clone" { count = data.coder_workspace.me.start_count source = "dev.registry.coder.com/modules/git-clone/coder" agent_id = coder_agent.main.id diff --git a/docs/admin/templates/extending-templates/devcontainers.md b/docs/admin/templates/extending-templates/devcontainers.md index 3f6fc2c852767..b5735e29ecc35 100644 --- a/docs/admin/templates/extending-templates/devcontainers.md +++ b/docs/admin/templates/extending-templates/devcontainers.md @@ -9,11 +9,13 @@ modules and configurations outlined in this doc. ## Why Use Dev Containers -Dev containers improve consistency across environments by letting developers define their development setup. -When integrated with Coder templates, they provide: +Dev containers improve consistency across environments by letting developers define their development setup within +the code repository. + +When integrated with Coder templates, dev containers provide: - **Project-specific environments**: Each repository can define its own tools, extensions, and configuration. -- **Zero setup time**: Developers get fully configured environments without manual installation. +- **Zero setup time**: Developers start workspace with fully configured environments without additional installation. - **Consistency across teams**: Everyone works in identical environments regardless of their local machine. - **Version control**: Development environment changes are tracked alongside code changes. @@ -55,7 +57,7 @@ RUN npm install -g @devcontainers/cli ## Define the Dev Container Resource -If you don't use [`git_clone`](#clone-the-repository), point the resource at the folder that contains `devcontainer.json`: +If you don't use [`git-clone`](#clone-the-repository), point the resource at the folder that contains `devcontainer.json`: ```terraform resource "coder_devcontainer" "project" { # `project` in this example is how users will connect to the dev container: `ssh://project..me.coder` @@ -69,11 +71,11 @@ resource "coder_devcontainer" "project" { # `project` in this example is how use This step is optional, but it ensures that the project is present before the dev container starts. -Note that if you use the `git_clone` module, update or replace the `coder_devcontainer` resource -to point to `/home/coder/project/${module.git_clone[0].folder_name}` so that it is only defined once: +Note that if you use the `git-clone` module, update or replace the `coder_devcontainer` resource +to point to `/home/coder/project/${module.git-clone[0].folder_name}` so that it is only defined once: ```terraform -module "git_clone" { +module "git-clone" { count = data.coder_workspace.me.start_count source = "dev.registry.coder.com/modules/git-clone/coder" agent_id = coder_agent.main.id @@ -84,7 +86,7 @@ module "git_clone" { resource "coder_devcontainer" "project" { count = data.coder_workspace.me.start_count agent_id = coder_agent.main.id - workspace_folder = "/home/coder/${module.git_clone[0].folder_name}" + workspace_folder = "/home/coder/${module.git-clone[0].folder_name}" } ``` @@ -143,7 +145,7 @@ resource "coder_devcontainer" "backend" { } ``` -## Example Docker Dev Container +## Example Docker Dev Container Template
Expand for the full file: @@ -173,7 +175,7 @@ module "devcontainers-cli" { agent_id = coder_agent.main.id } -module "git_clone" { +module "git-clone" { count = data.coder_workspace.me.start_count source = "dev.registry.coder.com/modules/git-clone/coder" agent_id = coder_agent.main.id @@ -184,7 +186,7 @@ module "git_clone" { resource "coder_devcontainer" "project" { count = data.coder_workspace.me.start_count agent_id = coder_agent.main.id - workspace_folder = "/home/coder/${module.git_clone[0].folder_name}" + workspace_folder = "/home/coder/${module.git-clone[0].folder_name}" } resource "docker_container" "workspace" { From 0b5cd2487fb8fceca84e2fbc8221041df8d063c6 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Wed, 9 Jul 2025 05:34:32 +0000 Subject: [PATCH 35/36] don't encourage envbuilder --- .../devcontainers/add-devcontainer.md | 2 ++ .../devcontainer-releases-known-issues.md | 4 ++++ .../managing-templates/devcontainers/index.md | 4 ++-- docs/user-guides/devcontainers/index.md | 14 -------------- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/docs/admin/templates/managing-templates/devcontainers/add-devcontainer.md b/docs/admin/templates/managing-templates/devcontainers/add-devcontainer.md index 83bb8cfecf680..85e5906d8152a 100644 --- a/docs/admin/templates/managing-templates/devcontainers/add-devcontainer.md +++ b/docs/admin/templates/managing-templates/devcontainers/add-devcontainer.md @@ -6,6 +6,8 @@ This allows the template to prompt for the developer for their dev container rep [parameter](../../extending-templates/parameters.md) when they create their workspace. Envbuilder clones the repo and builds a container from the `devcontainer.json` specified in the repo. +This is a legacy implementation. + For the Docker-based Dev Containers integration, follow the [Configure a template for dev containers](../../extending-templates/devcontainers.md) documentation instead. You can create template files through the Coder dashboard, CLI, or you can choose a template from the diff --git a/docs/admin/templates/managing-templates/devcontainers/devcontainer-releases-known-issues.md b/docs/admin/templates/managing-templates/devcontainers/devcontainer-releases-known-issues.md index 6824d409dfe29..6d1cffa182ce0 100644 --- a/docs/admin/templates/managing-templates/devcontainers/devcontainer-releases-known-issues.md +++ b/docs/admin/templates/managing-templates/devcontainers/devcontainer-releases-known-issues.md @@ -1,5 +1,9 @@ # Envbuilder Dev Container Releases and Known Issues +Envbuilder is a legacy implementation of dev containers. + +For the Docker-based Dev Containers integration, follow the [Configure a template for dev containers](../../extending-templates/devcontainers.md) documentation instead. + ## Release Channels Envbuilder provides two release channels: diff --git a/docs/admin/templates/managing-templates/devcontainers/index.md b/docs/admin/templates/managing-templates/devcontainers/index.md index 9b1c00a58701e..4737ef2a30614 100644 --- a/docs/admin/templates/managing-templates/devcontainers/index.md +++ b/docs/admin/templates/managing-templates/devcontainers/index.md @@ -14,9 +14,9 @@ pre-approved by platform teams in registries like workflows, reduces the need for tickets and approvals, and promotes greater independence for developers. -This doc explains how to use Envbuilder to integrate dev containers in a template. +Envbuilder is a legacy implementation of dev containers. -For the Docker-based Dev Containers integration, follow the [Configure a template for dev containers](../../extending-templates/devcontainers.md) documentation. +For the Docker-based Dev Containers integration, follow the [Configure a template for dev containers](../../extending-templates/devcontainers.md) documentation instead. ## Prerequisites diff --git a/docs/user-guides/devcontainers/index.md b/docs/user-guides/devcontainers/index.md index 34a64a8707485..e6bc7ad95d8c8 100644 --- a/docs/user-guides/devcontainers/index.md +++ b/docs/user-guides/devcontainers/index.md @@ -43,20 +43,6 @@ When a workspace with the dev containers integration starts: - Full SSH access directly into dev containers (`coder ssh ..me.coder`). - Automatic port forwarding. -## Comparison with Envbuilder-based Dev Containers - -| Feature | Dev Containers | Envbuilder Dev Containers | -|----------------|----------------------------------------|----------------------------------------------| -| Implementation | Direct `@devcontainers/cli` and Docker | Coder's Envbuilder | -| Target users | Individual developers | Platform teams and administrators | -| Configuration | Standard `devcontainer.json` | Terraform templates with Envbuilder | -| Management | User-controlled | Admin-controlled | -| Requirements | Docker access in workspace | Compatible with more restricted environments | - -Choose the appropriate solution based on your team's needs and infrastructure constraints. - -Visit [Choose an approach to Dev Containers](../../admin/templates/extending-templates/dev-containers-envbuilder.md) for a more in-depth comparison. - ## Known Limitations Currently, dev containers are not compatible with [prebuilt workspaces](../../admin/templates/extending-templates/prebuilt-workspaces.md). From 6485b8fc7b5382065d7d3985effcf5353f04ffb4 Mon Sep 17 00:00:00 2001 From: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Date: Wed, 9 Jul 2025 05:35:55 +0000 Subject: [PATCH 36/36] example edit --- docs/user-guides/devcontainers/working-with-dev-containers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user-guides/devcontainers/working-with-dev-containers.md b/docs/user-guides/devcontainers/working-with-dev-containers.md index de2551768fd1d..1377b6a0757d8 100644 --- a/docs/user-guides/devcontainers/working-with-dev-containers.md +++ b/docs/user-guides/devcontainers/working-with-dev-containers.md @@ -41,7 +41,7 @@ You can open your dev container directly in VS Code by: 2. Using the Coder CLI with the container flag: ```console -coder open vscode --container keen_dijkstra my-workspace +coder open vscode --container my-container-name my-workspace ``` While optimized for VS Code, other IDEs with dev containers support may also pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy