mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
feat(examples/templates): add docker-devcontainer
template and rename envbuilder template (#18741)
This change adds a new `docker-devcontainer` template which allows you to provision a workspace running in Docker, that also creates workspaces via Docker running inside (DinD). - **chore(examples/templates): rename `docker-devcontainer` to `docker-envbuilder`** - **feat(examples/templates): add `docker-devcontainer` example template**
This commit is contained in:
committed by
GitHub
parent
8b6d70bf1f
commit
7d412c2272
@ -6,7 +6,7 @@ USAGE:
|
||||
Get started with a templated template.
|
||||
|
||||
OPTIONS:
|
||||
--id aws-devcontainer|aws-linux|aws-windows|azure-linux|digitalocean-linux|docker|docker-devcontainer|gcp-devcontainer|gcp-linux|gcp-vm-container|gcp-windows|kubernetes|kubernetes-devcontainer|nomad-docker|scratch
|
||||
--id aws-devcontainer|aws-linux|aws-windows|azure-linux|digitalocean-linux|docker|docker-devcontainer|docker-envbuilder|gcp-devcontainer|gcp-linux|gcp-vm-container|gcp-windows|kubernetes|kubernetes-devcontainer|nomad-docker|scratch
|
||||
Specify a given example template by ID.
|
||||
|
||||
———
|
||||
|
4
docs/reference/cli/templates_init.md
generated
4
docs/reference/cli/templates_init.md
generated
@ -14,7 +14,7 @@ coder templates init [flags] [directory]
|
||||
### --id
|
||||
|
||||
| | |
|
||||
|------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Type | <code>aws-devcontainer\|aws-linux\|aws-windows\|azure-linux\|digitalocean-linux\|docker\|docker-devcontainer\|gcp-devcontainer\|gcp-linux\|gcp-vm-container\|gcp-windows\|kubernetes\|kubernetes-devcontainer\|nomad-docker\|scratch</code> |
|
||||
|------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Type | <code>aws-devcontainer\|aws-linux\|aws-windows\|azure-linux\|digitalocean-linux\|docker\|docker-devcontainer\|docker-envbuilder\|gcp-devcontainer\|gcp-linux\|gcp-vm-container\|gcp-windows\|kubernetes\|kubernetes-devcontainer\|nomad-docker\|scratch</code> |
|
||||
|
||||
Specify a given example template by ID.
|
||||
|
@ -83,15 +83,29 @@
|
||||
{
|
||||
"id": "docker-devcontainer",
|
||||
"url": "",
|
||||
"name": "Docker (Devcontainer)",
|
||||
"name": "Docker-in-Docker Dev Containers",
|
||||
"description": "Provision Docker containers as Coder workspaces running Dev Containers via Docker-in-Docker.",
|
||||
"icon": "/icon/docker.png",
|
||||
"tags": [
|
||||
"docker",
|
||||
"container",
|
||||
"devcontainer"
|
||||
],
|
||||
"markdown": "\n# Remote Development on Dev Containers\n\nProvision Docker containers as [Coder workspaces](https://coder.com/docs/workspaces) running [Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers) via Docker-in-Docker.\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n## Prerequisites\n\n### Infrastructure\n\nThe VM you run Coder on must have a running Docker socket and the `coder` user must be added to the Docker group:\n\n```sh\n# Add coder user to Docker group\nsudo adduser coder docker\n\n# Restart Coder server\nsudo systemctl restart coder\n\n# Test Docker\nsudo -u coder docker ps\n```\n\n## Architecture\n\nThis example uses the `codercom/enterprise-node:ubuntu` Docker image as a base image for the workspace. It includes necessary tools like Docker and Node.js, which are required for running Dev Containers via the `@devcontainers/cli` tool.\n\nThis template provisions the following resources:\n\n- Docker image (built by Docker socket and kept locally)\n- Docker container (ephemeral)\n- Docker volume (persistent on `/home/coder`)\n- Docker volume (persistent on `/var/lib/docker`)\n\nThis means, when the workspace restarts, any tools or files outside of the home directory or docker library are not persisted.\n\nFor devcontainers running inside the workspace, data persistence is dependent on each projects `devcontainer.json` configuration.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n"
|
||||
},
|
||||
{
|
||||
"id": "docker-envbuilder",
|
||||
"url": "",
|
||||
"name": "Docker (Envbuilder)",
|
||||
"description": "Provision envbuilder containers as Coder workspaces",
|
||||
"icon": "/icon/docker.png",
|
||||
"tags": [
|
||||
"container",
|
||||
"docker",
|
||||
"devcontainer"
|
||||
"devcontainer",
|
||||
"envbuilder"
|
||||
],
|
||||
"markdown": "\n# Remote Development on Docker Containers (with Devcontainers)\n\nProvision Devcontainers as [Coder workspaces](https://coder.com/docs/workspaces) in Docker with this example template.\n\n## Prerequisites\n\n### Infrastructure\n\nCoder must have access to a running Docker socket, and the `coder` user must be a member of the `docker` group:\n\n```shell\n# Add coder user to Docker group\nsudo usermod -aG docker coder\n\n# Restart Coder server\nsudo systemctl restart coder\n\n# Test Docker\nsudo -u coder docker ps\n```\n\n## Architecture\n\nCoder supports Devcontainers via [envbuilder](https://github.com/coder/envbuilder), an open source project. Read more about this in [Coder's documentation](https://coder.com/docs/templates/dev-containers).\n\nThis template provisions the following resources:\n\n- Envbuilder cached image (conditional, persistent) using [`terraform-provider-envbuilder`](https://github.com/coder/terraform-provider-envbuilder)\n- Docker image (persistent) using [`envbuilder`](https://github.com/coder/envbuilder)\n- Docker container (ephemeral)\n- Docker volume (persistent on `/workspaces`)\n\nThe Git repository is cloned inside the `/workspaces` volume if not present.\nAny local changes to the Devcontainer files inside the volume will be applied when you restart the workspace.\nKeep in mind that any tools or files outside of `/workspaces` or not added as part of the Devcontainer specification are not persisted.\nEdit the `devcontainer.json` instead!\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## Docker-in-Docker\n\nSee the [Envbuilder documentation](https://github.com/coder/envbuilder/blob/main/docs/docker.md) for information on running Docker containers inside a devcontainer built by Envbuilder.\n\n## Caching\n\nTo speed up your builds, you can use a container registry as a cache.\nWhen creating the template, set the parameter `cache_repo` to a valid Docker repository.\n\nFor example, you can run a local registry:\n\n```shell\ndocker run --detach \\\n --volume registry-cache:/var/lib/registry \\\n --publish 5000:5000 \\\n --name registry-cache \\\n --net=host \\\n registry:2\n```\n\nThen, when creating the template, enter `localhost:5000/devcontainer-cache` for the parameter `cache_repo`.\n\nSee the [Envbuilder Terraform Provider Examples](https://github.com/coder/terraform-provider-envbuilder/blob/main/examples/resources/envbuilder_cached_image/envbuilder_cached_image_resource.tf/) for a more complete example of how the provider works.\n\n\u003e [!NOTE]\n\u003e We recommend using a registry cache with authentication enabled.\n\u003e To allow Envbuilder to authenticate with the registry cache, specify the variable `cache_repo_docker_config_path`\n\u003e with the path to a Docker config `.json` on disk containing valid credentials for the registry.\n"
|
||||
"markdown": "\n# Remote Development on Docker Containers (with Envbuilder)\n\nProvision Envbuilder containers based on `devcontainer.json` as [Coder workspaces](https://coder.com/docs/workspaces) in Docker with this example template.\n\n## Prerequisites\n\n### Infrastructure\n\nCoder must have access to a running Docker socket, and the `coder` user must be a member of the `docker` group:\n\n```shell\n# Add coder user to Docker group\nsudo usermod -aG docker coder\n\n# Restart Coder server\nsudo systemctl restart coder\n\n# Test Docker\nsudo -u coder docker ps\n```\n\n## Architecture\n\nCoder supports Envbuilder containers based on `devcontainer.json` via [envbuilder](https://github.com/coder/envbuilder), an open source project. Read more about this in [Coder's documentation](https://coder.com/docs/templates/dev-containers).\n\nThis template provisions the following resources:\n\n- Envbuilder cached image (conditional, persistent) using [`terraform-provider-envbuilder`](https://github.com/coder/terraform-provider-envbuilder)\n- Docker image (persistent) using [`envbuilder`](https://github.com/coder/envbuilder)\n- Docker container (ephemeral)\n- Docker volume (persistent on `/workspaces`)\n\nThe Git repository is cloned inside the `/workspaces` volume if not present.\nAny local changes to the Devcontainer files inside the volume will be applied when you restart the workspace.\nKeep in mind that any tools or files outside of `/workspaces` or not added as part of the Devcontainer specification are not persisted.\nEdit the `devcontainer.json` instead!\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## Docker-in-Docker\n\nSee the [Envbuilder documentation](https://github.com/coder/envbuilder/blob/main/docs/docker.md) for information on running Docker containers inside an Envbuilder container.\n\n## Caching\n\nTo speed up your builds, you can use a container registry as a cache.\nWhen creating the template, set the parameter `cache_repo` to a valid Docker repository.\n\nFor example, you can run a local registry:\n\n```shell\ndocker run --detach \\\n --volume registry-cache:/var/lib/registry \\\n --publish 5000:5000 \\\n --name registry-cache \\\n --net=host \\\n registry:2\n```\n\nThen, when creating the template, enter `localhost:5000/envbuilder-cache` for the parameter `cache_repo`.\n\nSee the [Envbuilder Terraform Provider Examples](https://github.com/coder/terraform-provider-envbuilder/blob/main/examples/resources/envbuilder_cached_image/envbuilder_cached_image_resource.tf/) for a more complete example of how the provider works.\n\n\u003e [!NOTE]\n\u003e We recommend using a registry cache with authentication enabled.\n\u003e To allow Envbuilder to authenticate with the registry cache, specify the variable `cache_repo_docker_config_path`\n\u003e with the path to a Docker config `.json` on disk containing valid credentials for the registry.\n"
|
||||
},
|
||||
{
|
||||
"id": "gcp-devcontainer",
|
||||
|
@ -31,6 +31,7 @@ var (
|
||||
//go:embed templates/digitalocean-linux
|
||||
//go:embed templates/docker
|
||||
//go:embed templates/docker-devcontainer
|
||||
//go:embed templates/docker-envbuilder
|
||||
//go:embed templates/gcp-devcontainer
|
||||
//go:embed templates/gcp-linux
|
||||
//go:embed templates/gcp-vm-container
|
||||
|
@ -1,25 +1,27 @@
|
||||
---
|
||||
display_name: Docker (Devcontainer)
|
||||
description: Provision envbuilder containers as Coder workspaces
|
||||
display_name: Docker-in-Docker Dev Containers
|
||||
description: Provision Docker containers as Coder workspaces running Dev Containers via Docker-in-Docker.
|
||||
icon: ../../../site/static/icon/docker.png
|
||||
maintainer_github: coder
|
||||
verified: true
|
||||
tags: [container, docker, devcontainer]
|
||||
tags: [docker, container, devcontainer]
|
||||
---
|
||||
|
||||
# Remote Development on Docker Containers (with Devcontainers)
|
||||
# Remote Development on Dev Containers
|
||||
|
||||
Provision Devcontainers as [Coder workspaces](https://coder.com/docs/workspaces) in Docker with this example template.
|
||||
Provision Docker containers as [Coder workspaces](https://coder.com/docs/workspaces) running [Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers) via Docker-in-Docker.
|
||||
|
||||
<!-- TODO: Add screenshot -->
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Infrastructure
|
||||
|
||||
Coder must have access to a running Docker socket, and the `coder` user must be a member of the `docker` group:
|
||||
The VM you run Coder on must have a running Docker socket and the `coder` user must be added to the Docker group:
|
||||
|
||||
```shell
|
||||
```sh
|
||||
# Add coder user to Docker group
|
||||
sudo usermod -aG docker coder
|
||||
sudo adduser coder docker
|
||||
|
||||
# Restart Coder server
|
||||
sudo systemctl restart coder
|
||||
@ -30,48 +32,18 @@ sudo -u coder docker ps
|
||||
|
||||
## Architecture
|
||||
|
||||
Coder supports Devcontainers via [envbuilder](https://github.com/coder/envbuilder), an open source project. Read more about this in [Coder's documentation](https://coder.com/docs/templates/dev-containers).
|
||||
This example uses the `codercom/enterprise-node:ubuntu` Docker image as a base image for the workspace. It includes necessary tools like Docker and Node.js, which are required for running Dev Containers via the `@devcontainers/cli` tool.
|
||||
|
||||
This template provisions the following resources:
|
||||
|
||||
- Envbuilder cached image (conditional, persistent) using [`terraform-provider-envbuilder`](https://github.com/coder/terraform-provider-envbuilder)
|
||||
- Docker image (persistent) using [`envbuilder`](https://github.com/coder/envbuilder)
|
||||
- Docker image (built by Docker socket and kept locally)
|
||||
- Docker container (ephemeral)
|
||||
- Docker volume (persistent on `/workspaces`)
|
||||
- Docker volume (persistent on `/home/coder`)
|
||||
- Docker volume (persistent on `/var/lib/docker`)
|
||||
|
||||
The Git repository is cloned inside the `/workspaces` volume if not present.
|
||||
Any local changes to the Devcontainer files inside the volume will be applied when you restart the workspace.
|
||||
Keep in mind that any tools or files outside of `/workspaces` or not added as part of the Devcontainer specification are not persisted.
|
||||
Edit the `devcontainer.json` instead!
|
||||
This means, when the workspace restarts, any tools or files outside of the home directory or docker library are not persisted.
|
||||
|
||||
For devcontainers running inside the workspace, data persistence is dependent on each projects `devcontainer.json` configuration.
|
||||
|
||||
> **Note**
|
||||
> This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.
|
||||
|
||||
## Docker-in-Docker
|
||||
|
||||
See the [Envbuilder documentation](https://github.com/coder/envbuilder/blob/main/docs/docker.md) for information on running Docker containers inside a devcontainer built by Envbuilder.
|
||||
|
||||
## Caching
|
||||
|
||||
To speed up your builds, you can use a container registry as a cache.
|
||||
When creating the template, set the parameter `cache_repo` to a valid Docker repository.
|
||||
|
||||
For example, you can run a local registry:
|
||||
|
||||
```shell
|
||||
docker run --detach \
|
||||
--volume registry-cache:/var/lib/registry \
|
||||
--publish 5000:5000 \
|
||||
--name registry-cache \
|
||||
--net=host \
|
||||
registry:2
|
||||
```
|
||||
|
||||
Then, when creating the template, enter `localhost:5000/devcontainer-cache` for the parameter `cache_repo`.
|
||||
|
||||
See the [Envbuilder Terraform Provider Examples](https://github.com/coder/terraform-provider-envbuilder/blob/main/examples/resources/envbuilder_cached_image/envbuilder_cached_image_resource.tf/) for a more complete example of how the provider works.
|
||||
|
||||
> [!NOTE]
|
||||
> We recommend using a registry cache with authentication enabled.
|
||||
> To allow Envbuilder to authenticate with the registry cache, specify the variable `cache_repo_docker_config_path`
|
||||
> with the path to a Docker config `.json` on disk containing valid credentials for the registry.
|
||||
|
@ -2,257 +2,86 @@ terraform {
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "~> 2.0"
|
||||
}
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
}
|
||||
envbuilder = {
|
||||
source = "coder/envbuilder"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
username = data.coder_workspace_owner.me.name
|
||||
|
||||
# Use a workspace image that supports rootless Docker
|
||||
# (Docker-in-Docker) and Node.js.
|
||||
workspace_image = "codercom/enterprise-node:ubuntu"
|
||||
}
|
||||
|
||||
variable "docker_socket" {
|
||||
default = ""
|
||||
description = "(Optional) Docker socket URI"
|
||||
type = string
|
||||
}
|
||||
|
||||
provider "coder" {}
|
||||
data "coder_parameter" "repo_url" {
|
||||
type = "string"
|
||||
name = "repo_url"
|
||||
display_name = "Git Repository"
|
||||
description = "Enter the URL of the Git repository to clone into your workspace. This repository should contain a devcontainer.json file to configure your development environment."
|
||||
default = "https://github.com/coder/coder"
|
||||
mutable = true
|
||||
}
|
||||
|
||||
provider "docker" {
|
||||
# Defaulting to null if the variable is an empty string lets us have an optional variable without having to set our own default
|
||||
host = var.docker_socket != "" ? var.docker_socket : null
|
||||
}
|
||||
provider "envbuilder" {}
|
||||
|
||||
data "coder_provisioner" "me" {}
|
||||
data "coder_workspace" "me" {}
|
||||
data "coder_workspace_owner" "me" {}
|
||||
|
||||
data "coder_parameter" "repo" {
|
||||
description = "Select a repository to automatically clone and start working with a devcontainer."
|
||||
display_name = "Repository (auto)"
|
||||
mutable = true
|
||||
name = "repo"
|
||||
option {
|
||||
name = "vercel/next.js"
|
||||
description = "The React Framework"
|
||||
value = "https://github.com/vercel/next.js"
|
||||
}
|
||||
option {
|
||||
name = "home-assistant/core"
|
||||
description = "🏡 Open source home automation that puts local control and privacy first."
|
||||
value = "https://github.com/home-assistant/core"
|
||||
}
|
||||
option {
|
||||
name = "discourse/discourse"
|
||||
description = "A platform for community discussion. Free, open, simple."
|
||||
value = "https://github.com/discourse/discourse"
|
||||
}
|
||||
option {
|
||||
name = "denoland/deno"
|
||||
description = "A modern runtime for JavaScript and TypeScript."
|
||||
value = "https://github.com/denoland/deno"
|
||||
}
|
||||
option {
|
||||
name = "microsoft/vscode"
|
||||
icon = "/icon/code.svg"
|
||||
description = "Code editing. Redefined."
|
||||
value = "https://github.com/microsoft/vscode"
|
||||
}
|
||||
option {
|
||||
name = "Custom"
|
||||
icon = "/emojis/1f5c3.png"
|
||||
description = "Specify a custom repo URL below"
|
||||
value = "custom"
|
||||
}
|
||||
order = 1
|
||||
}
|
||||
|
||||
data "coder_parameter" "custom_repo_url" {
|
||||
default = ""
|
||||
description = "Optionally enter a custom repository URL, see [awesome-devcontainers](https://github.com/manekinekko/awesome-devcontainers)."
|
||||
display_name = "Repository URL (custom)"
|
||||
name = "custom_repo_url"
|
||||
mutable = true
|
||||
order = 2
|
||||
}
|
||||
|
||||
data "coder_parameter" "fallback_image" {
|
||||
default = "codercom/enterprise-base:ubuntu"
|
||||
description = "This image runs if the devcontainer fails to build."
|
||||
display_name = "Fallback Image"
|
||||
mutable = true
|
||||
name = "fallback_image"
|
||||
order = 3
|
||||
}
|
||||
|
||||
data "coder_parameter" "devcontainer_builder" {
|
||||
description = <<-EOF
|
||||
Image that will build the devcontainer.
|
||||
We highly recommend using a specific release as the `:latest` tag will change.
|
||||
Find the latest version of Envbuilder here: https://github.com/coder/envbuilder/pkgs/container/envbuilder
|
||||
EOF
|
||||
display_name = "Devcontainer Builder"
|
||||
mutable = true
|
||||
name = "devcontainer_builder"
|
||||
default = "ghcr.io/coder/envbuilder:latest"
|
||||
order = 4
|
||||
}
|
||||
|
||||
variable "cache_repo" {
|
||||
default = ""
|
||||
description = "(Optional) Use a container registry as a cache to speed up builds."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "insecure_cache_repo" {
|
||||
default = false
|
||||
description = "Enable this option if your cache registry does not serve HTTPS."
|
||||
type = bool
|
||||
}
|
||||
|
||||
variable "cache_repo_docker_config_path" {
|
||||
default = ""
|
||||
description = "(Optional) Path to a docker config.json containing credentials to the provided cache repo, if required."
|
||||
sensitive = true
|
||||
type = string
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
|
||||
devcontainer_builder_image = data.coder_parameter.devcontainer_builder.value
|
||||
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
|
||||
repo_url = data.coder_parameter.repo.value == "custom" ? data.coder_parameter.custom_repo_url.value : data.coder_parameter.repo.value
|
||||
# The envbuilder provider requires a key-value map of environment variables.
|
||||
envbuilder_env = {
|
||||
# ENVBUILDER_GIT_URL and ENVBUILDER_CACHE_REPO will be overridden by the provider
|
||||
# if the cache repo is enabled.
|
||||
"ENVBUILDER_GIT_URL" : local.repo_url,
|
||||
"ENVBUILDER_CACHE_REPO" : var.cache_repo,
|
||||
"CODER_AGENT_TOKEN" : coder_agent.main.token,
|
||||
# Use the docker gateway if the access URL is 127.0.0.1
|
||||
"CODER_AGENT_URL" : replace(data.coder_workspace.me.access_url, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal"),
|
||||
# Use the docker gateway if the access URL is 127.0.0.1
|
||||
"ENVBUILDER_INIT_SCRIPT" : replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal"),
|
||||
"ENVBUILDER_FALLBACK_IMAGE" : data.coder_parameter.fallback_image.value,
|
||||
"ENVBUILDER_DOCKER_CONFIG_BASE64" : try(data.local_sensitive_file.cache_repo_dockerconfigjson[0].content_base64, ""),
|
||||
"ENVBUILDER_PUSH_IMAGE" : var.cache_repo == "" ? "" : "true",
|
||||
"ENVBUILDER_INSECURE" : "${var.insecure_cache_repo}",
|
||||
}
|
||||
# Convert the above map to the format expected by the docker provider.
|
||||
docker_env = [
|
||||
for k, v in local.envbuilder_env : "${k}=${v}"
|
||||
]
|
||||
}
|
||||
|
||||
data "local_sensitive_file" "cache_repo_dockerconfigjson" {
|
||||
count = var.cache_repo_docker_config_path == "" ? 0 : 1
|
||||
filename = var.cache_repo_docker_config_path
|
||||
}
|
||||
|
||||
resource "docker_image" "devcontainer_builder_image" {
|
||||
name = local.devcontainer_builder_image
|
||||
keep_locally = true
|
||||
}
|
||||
|
||||
resource "docker_volume" "workspaces" {
|
||||
name = "coder-${data.coder_workspace.me.id}"
|
||||
# Protect the volume from being deleted due to changes in attributes.
|
||||
lifecycle {
|
||||
ignore_changes = all
|
||||
}
|
||||
# Add labels in Docker to keep track of orphan resources.
|
||||
labels {
|
||||
label = "coder.owner"
|
||||
value = data.coder_workspace_owner.me.name
|
||||
}
|
||||
labels {
|
||||
label = "coder.owner_id"
|
||||
value = data.coder_workspace_owner.me.id
|
||||
}
|
||||
labels {
|
||||
label = "coder.workspace_id"
|
||||
value = data.coder_workspace.me.id
|
||||
}
|
||||
# This field becomes outdated if the workspace is renamed but can
|
||||
# be useful for debugging or cleaning out dangling volumes.
|
||||
labels {
|
||||
label = "coder.workspace_name_at_creation"
|
||||
value = data.coder_workspace.me.name
|
||||
}
|
||||
}
|
||||
|
||||
# Check for the presence of a prebuilt image in the cache repo
|
||||
# that we can use instead.
|
||||
resource "envbuilder_cached_image" "cached" {
|
||||
count = var.cache_repo == "" ? 0 : data.coder_workspace.me.start_count
|
||||
builder_image = local.devcontainer_builder_image
|
||||
git_url = local.repo_url
|
||||
cache_repo = var.cache_repo
|
||||
extra_env = local.envbuilder_env
|
||||
insecure = var.insecure_cache_repo
|
||||
}
|
||||
|
||||
resource "docker_container" "workspace" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
image = var.cache_repo == "" ? local.devcontainer_builder_image : envbuilder_cached_image.cached.0.image
|
||||
# Uses lower() to avoid Docker restriction on container names.
|
||||
name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
|
||||
# Hostname makes the shell more user friendly: coder@my-workspace:~$
|
||||
hostname = data.coder_workspace.me.name
|
||||
# Use the environment specified by the envbuilder provider, if available.
|
||||
env = var.cache_repo == "" ? local.docker_env : envbuilder_cached_image.cached.0.env
|
||||
# network_mode = "host" # Uncomment if testing with a registry running on `localhost`.
|
||||
host {
|
||||
host = "host.docker.internal"
|
||||
ip = "host-gateway"
|
||||
}
|
||||
volumes {
|
||||
container_path = "/workspaces"
|
||||
volume_name = docker_volume.workspaces.name
|
||||
read_only = false
|
||||
}
|
||||
# Add labels in Docker to keep track of orphan resources.
|
||||
labels {
|
||||
label = "coder.owner"
|
||||
value = data.coder_workspace_owner.me.name
|
||||
}
|
||||
labels {
|
||||
label = "coder.owner_id"
|
||||
value = data.coder_workspace_owner.me.id
|
||||
}
|
||||
labels {
|
||||
label = "coder.workspace_id"
|
||||
value = data.coder_workspace.me.id
|
||||
}
|
||||
labels {
|
||||
label = "coder.workspace_name"
|
||||
value = data.coder_workspace.me.name
|
||||
}
|
||||
}
|
||||
|
||||
resource "coder_agent" "main" {
|
||||
arch = data.coder_provisioner.me.arch
|
||||
os = "linux"
|
||||
startup_script = <<-EOT
|
||||
set -e
|
||||
|
||||
# Add any commands that should be executed at workspace startup (e.g install requirements, start a program, etc) here
|
||||
# Prepare user home with default files on first start.
|
||||
if [ ! -f ~/.init_done ]; then
|
||||
cp -rT /etc/skel ~
|
||||
touch ~/.init_done
|
||||
fi
|
||||
|
||||
# Add any commands that should be executed at workspace startup
|
||||
# (e.g. install requirements, start a program, etc) here.
|
||||
EOT
|
||||
shutdown_script = <<-EOT
|
||||
set -e
|
||||
|
||||
# Clean up the docker volume from unused resources to keep storage
|
||||
# usage low.
|
||||
#
|
||||
# WARNING! This will remove:
|
||||
# - all stopped containers
|
||||
# - all networks not used by at least one container
|
||||
# - all images without at least one container associated to them
|
||||
# - all build cache
|
||||
docker system prune -a -f
|
||||
|
||||
# Stop the Docker service.
|
||||
sudo service docker stop
|
||||
EOT
|
||||
dir = "/workspaces"
|
||||
|
||||
# These environment variables allow you to make Git commits right away after creating a
|
||||
# workspace. Note that they take precedence over configuration defined in ~/.gitconfig!
|
||||
# You can remove this block if you'd prefer to configure Git manually or using
|
||||
# dotfiles. (see docs/dotfiles.md)
|
||||
env = {
|
||||
GIT_AUTHOR_NAME = local.git_author_name
|
||||
GIT_AUTHOR_EMAIL = local.git_author_email
|
||||
GIT_COMMITTER_NAME = local.git_author_name
|
||||
GIT_COMMITTER_EMAIL = local.git_author_email
|
||||
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}"
|
||||
}
|
||||
|
||||
# The following metadata blocks are optional. They are used to display
|
||||
@ -279,7 +108,7 @@ resource "coder_agent" "main" {
|
||||
metadata {
|
||||
display_name = "Home Disk"
|
||||
key = "3_home_disk"
|
||||
script = "coder stat disk --path $HOME"
|
||||
script = "coder stat disk --path $${HOME}"
|
||||
interval = 60
|
||||
timeout = 1
|
||||
}
|
||||
@ -322,51 +151,159 @@ resource "coder_agent" "main" {
|
||||
}
|
||||
}
|
||||
|
||||
# See https://registry.coder.com/modules/coder/code-server
|
||||
module "code-server" {
|
||||
resource "coder_script" "init_docker_in_docker" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/code-server/coder"
|
||||
|
||||
# This ensures that the latest non-breaking version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production.
|
||||
version = "~> 1.0"
|
||||
|
||||
agent_id = coder_agent.main.id
|
||||
order = 1
|
||||
display_name = "Initialize Docker-in-Docker"
|
||||
run_on_start = true
|
||||
icon = "/icon/docker.svg"
|
||||
script = file("${path.module}/scripts/init-docker-in-docker.sh")
|
||||
}
|
||||
|
||||
# See https://registry.coder.com/modules/coder/jetbrains-gateway
|
||||
module "jetbrains_gateway" {
|
||||
# See https://registry.coder.com/modules/coder/devcontainers-cli
|
||||
module "devcontainers-cli" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains-gateway/coder"
|
||||
|
||||
# JetBrains IDEs to make available for the user to select
|
||||
jetbrains_ides = ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD", "RR"]
|
||||
default = "IU"
|
||||
|
||||
# Default folder to open when starting a JetBrains IDE
|
||||
folder = "/workspaces"
|
||||
|
||||
# This ensures that the latest non-breaking version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production.
|
||||
version = "~> 1.0"
|
||||
|
||||
source = "registry.coder.com/coder/devcontainers-cli/coder"
|
||||
agent_id = coder_agent.main.id
|
||||
agent_name = "main"
|
||||
order = 2
|
||||
|
||||
# This ensures that the latest non-breaking version of the module gets
|
||||
# downloaded, you can also pin the module version to prevent breaking
|
||||
# changes in production.
|
||||
version = "~> 1.0"
|
||||
}
|
||||
|
||||
resource "coder_metadata" "container_info" {
|
||||
# See https://registry.coder.com/modules/coder/git-clone
|
||||
module "git-clone" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
resource_id = coder_agent.main.id
|
||||
item {
|
||||
key = "workspace image"
|
||||
value = var.cache_repo == "" ? local.devcontainer_builder_image : envbuilder_cached_image.cached.0.image
|
||||
source = "registry.coder.com/coder/git-clone/coder"
|
||||
agent_id = coder_agent.main.id
|
||||
url = data.coder_parameter.repo_url.value
|
||||
base_dir = "~"
|
||||
# This ensures that the latest non-breaking version of the module gets
|
||||
# downloaded, you can also pin the module version to prevent breaking
|
||||
# changes in production.
|
||||
version = "~> 1.0"
|
||||
}
|
||||
|
||||
# Automatically start the devcontainer for the workspace.
|
||||
resource "coder_devcontainer" "repo" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
agent_id = coder_agent.main.id
|
||||
workspace_folder = "~/${module.git-clone[0].folder_name}"
|
||||
}
|
||||
|
||||
resource "docker_volume" "home_volume" {
|
||||
name = "coder-${data.coder_workspace.me.id}-home"
|
||||
# Protect the volume from being deleted due to changes in attributes.
|
||||
lifecycle {
|
||||
ignore_changes = all
|
||||
}
|
||||
item {
|
||||
key = "git url"
|
||||
value = local.repo_url
|
||||
# Add labels in Docker to keep track of orphan resources.
|
||||
labels {
|
||||
label = "coder.owner"
|
||||
value = data.coder_workspace_owner.me.name
|
||||
}
|
||||
item {
|
||||
key = "cache repo"
|
||||
value = var.cache_repo == "" ? "not enabled" : var.cache_repo
|
||||
labels {
|
||||
label = "coder.owner_id"
|
||||
value = data.coder_workspace_owner.me.id
|
||||
}
|
||||
labels {
|
||||
label = "coder.workspace_id"
|
||||
value = data.coder_workspace.me.id
|
||||
}
|
||||
# This field becomes outdated if the workspace is renamed but can
|
||||
# be useful for debugging or cleaning out dangling volumes.
|
||||
labels {
|
||||
label = "coder.workspace_name_at_creation"
|
||||
value = data.coder_workspace.me.name
|
||||
}
|
||||
}
|
||||
|
||||
resource "docker_volume" "docker_volume" {
|
||||
name = "coder-${data.coder_workspace.me.id}-docker"
|
||||
# Protect the volume from being deleted due to changes in attributes.
|
||||
lifecycle {
|
||||
ignore_changes = all
|
||||
}
|
||||
# Add labels in Docker to keep track of orphan resources.
|
||||
labels {
|
||||
label = "coder.owner"
|
||||
value = data.coder_workspace_owner.me.name
|
||||
}
|
||||
labels {
|
||||
label = "coder.owner_id"
|
||||
value = data.coder_workspace_owner.me.id
|
||||
}
|
||||
labels {
|
||||
label = "coder.workspace_id"
|
||||
value = data.coder_workspace.me.id
|
||||
}
|
||||
# This field becomes outdated if the workspace is renamed but can
|
||||
# be useful for debugging or cleaning out dangling volumes.
|
||||
labels {
|
||||
label = "coder.workspace_name_at_creation"
|
||||
value = data.coder_workspace.me.name
|
||||
}
|
||||
}
|
||||
|
||||
resource "docker_container" "workspace" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
image = local.workspace_image
|
||||
|
||||
# NOTE: The `privileged` mode is one way to run Docker-in-Docker,
|
||||
# which is required for the devcontainer to work. If this is not
|
||||
# desired, you can remove this line. However, you will need to ensure
|
||||
# that the devcontainer can run Docker commands in some other way.
|
||||
# Mounting the host Docker socket is strongly discouraged because
|
||||
# workspaces will then compete for control of the devcontainers.
|
||||
# For more information, see:
|
||||
# https://coder.com/docs/admin/templates/extending-templates/docker-in-workspaces
|
||||
privileged = true
|
||||
|
||||
# Uses lower() to avoid Docker restriction on container names.
|
||||
name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
|
||||
# Hostname makes the shell more user friendly: coder@my-workspace:~$
|
||||
hostname = data.coder_workspace.me.name
|
||||
# Use the docker gateway if the access URL is 127.0.0.1
|
||||
command = ["sh", "-c", replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal")]
|
||||
env = [
|
||||
"CODER_AGENT_TOKEN=${coder_agent.main.token}"
|
||||
]
|
||||
host {
|
||||
host = "host.docker.internal"
|
||||
ip = "host-gateway"
|
||||
}
|
||||
|
||||
# Workspace home volume persists user data across workspace restarts.
|
||||
volumes {
|
||||
container_path = "/home/coder"
|
||||
volume_name = docker_volume.home_volume.name
|
||||
read_only = false
|
||||
}
|
||||
|
||||
# Workspace docker volume persists Docker data across workspace
|
||||
# restarts, allowing the devcontainer cache to be reused.
|
||||
volumes {
|
||||
container_path = "/var/lib/docker"
|
||||
volume_name = docker_volume.docker_volume.name
|
||||
read_only = false
|
||||
}
|
||||
|
||||
# Add labels in Docker to keep track of orphan resources.
|
||||
labels {
|
||||
label = "coder.owner"
|
||||
value = data.coder_workspace_owner.me.name
|
||||
}
|
||||
labels {
|
||||
label = "coder.owner_id"
|
||||
value = data.coder_workspace_owner.me.id
|
||||
}
|
||||
labels {
|
||||
label = "coder.workspace_id"
|
||||
value = data.coder_workspace.me.id
|
||||
}
|
||||
labels {
|
||||
label = "coder.workspace_name"
|
||||
value = data.coder_workspace.me.name
|
||||
}
|
||||
}
|
||||
|
101
examples/templates/docker-devcontainer/scripts/init-docker-in-docker.sh
Executable file
101
examples/templates/docker-devcontainer/scripts/init-docker-in-docker.sh
Executable file
@ -0,0 +1,101 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Docker-in-Docker setup for Coder dev containers using host.docker.internal
|
||||
# URLs. When Docker runs inside a container, the "docker0" bridge interface
|
||||
# can interfere with host.docker.internal DNS resolution, breaking
|
||||
# connectivity to the Coder server.
|
||||
|
||||
if [ "${CODER_AGENT_URL#*host.docker.internal}" = "$CODER_AGENT_URL" ]; then
|
||||
# External access URL detected, no networking workarounds needed.
|
||||
sudo service docker start
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# host.docker.internal URL detected. Docker's default bridge network creates
|
||||
# a "docker0" interface that can shadow the host.docker.internal hostname
|
||||
# resolution. This typically happens when Docker starts inside a devcontainer,
|
||||
# as the inner Docker daemon creates its own bridge network that conflicts
|
||||
# with the outer one.
|
||||
|
||||
# Enable IP forwarding to allow packets to route between the host network and
|
||||
# the devcontainer networks. Without this, traffic cannot flow properly
|
||||
# between the different Docker bridge networks.
|
||||
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
|
||||
sudo iptables -t nat -A POSTROUTING -j MASQUERADE
|
||||
|
||||
# Set up port forwarding to the host Docker gateway (typically 172.17.0.1).
|
||||
# We resolve host.docker.internal to get the actual IP and create NAT rules
|
||||
# to forward traffic from this workspace to the host.
|
||||
host_ip=$(getent hosts host.docker.internal | awk '{print $1}')
|
||||
|
||||
echo "Host IP for host.docker.internal: $host_ip"
|
||||
|
||||
# Extract the port from CODER_AGENT_URL. The URL format is typically
|
||||
# http://host.docker.internal:port/.
|
||||
port="${CODER_AGENT_URL##*:}"
|
||||
port="${port%%/*}"
|
||||
case "$port" in
|
||||
[0-9]*)
|
||||
# Specific port found, forward it to the host gateway.
|
||||
sudo iptables -t nat -A PREROUTING -p tcp --dport "$port" -j DNAT --to-destination "$host_ip:$port"
|
||||
echo "Forwarded port $port to $host_ip"
|
||||
;;
|
||||
*)
|
||||
# No specific port or non-numeric port, forward standard web ports.
|
||||
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination "$host_ip:80"
|
||||
sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination "$host_ip:443"
|
||||
echo "Forwarded default ports 80/443 to $host_ip"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Start Docker service, which creates the "docker0" interface if it doesn't
|
||||
# exist. We need the interface to extract the second IP address for DNS
|
||||
# resolution.
|
||||
sudo service docker start
|
||||
|
||||
# Configure DNS resolution to avoid requiring devcontainer project modifications.
|
||||
# While devcontainers can use the "--add-host" flag, it requires explicit
|
||||
# definition in devcontainer.json. Using a DNS server instead means every
|
||||
# devcontainer project doesn't need to accommodate this.
|
||||
|
||||
# Wait for the workspace to acquire its Docker bridge IP address. The
|
||||
# "hostname -I" command returns multiple IPs: the first is typically the host
|
||||
# Docker bridge (172.17.0.0/16 range) and the second is the workspace Docker
|
||||
# bridge (172.18.0.0/16). We need the second IP because that's where
|
||||
# devcontainers will be able to reach us.
|
||||
dns_ip=
|
||||
while [ -z "$dns_ip" ]; do
|
||||
dns_ip=$(hostname -I | awk '{print $2}')
|
||||
if [ -z "$dns_ip" ]; then
|
||||
echo "Waiting for hostname -I to return a valid second IP address..."
|
||||
sleep 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Using DNS IP: $dns_ip"
|
||||
|
||||
# Install dnsmasq to provide custom DNS resolution. This lightweight DNS
|
||||
# server allows us to override specific hostname lookups without affecting
|
||||
# other DNS queries.
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y dnsmasq
|
||||
|
||||
# Configure dnsmasq to resolve host.docker.internal to this workspace's IP.
|
||||
# This ensures devcontainers can find the Coder server even when the "docker0"
|
||||
# interface would normally shadow the hostname resolution.
|
||||
echo "no-hosts" | sudo tee /etc/dnsmasq.conf
|
||||
echo "address=/host.docker.internal/$dns_ip" | sudo tee -a /etc/dnsmasq.conf
|
||||
echo "resolv-file=/etc/resolv.conf" | sudo tee -a /etc/dnsmasq.conf
|
||||
echo "no-dhcp-interface=" | sudo tee -a /etc/dnsmasq.conf
|
||||
echo "bind-interfaces" | sudo tee -a /etc/dnsmasq.conf
|
||||
echo "listen-address=127.0.0.1,$dns_ip" | sudo tee -a /etc/dnsmasq.conf
|
||||
|
||||
sudo service dnsmasq restart
|
||||
|
||||
# Configure Docker daemon to use our custom DNS server. This is the critical
|
||||
# piece that ensures all containers (including devcontainers) use our dnsmasq
|
||||
# server for hostname resolution, allowing them to properly resolve
|
||||
# host.docker.internal.
|
||||
echo "{\"dns\": [\"$dns_ip\"]}" | sudo tee /etc/docker/daemon.json
|
||||
sudo service docker restart
|
77
examples/templates/docker-envbuilder/README.md
Normal file
77
examples/templates/docker-envbuilder/README.md
Normal file
@ -0,0 +1,77 @@
|
||||
---
|
||||
display_name: Docker (Envbuilder)
|
||||
description: Provision envbuilder containers as Coder workspaces
|
||||
icon: ../../../site/static/icon/docker.png
|
||||
maintainer_github: coder
|
||||
verified: true
|
||||
tags: [container, docker, devcontainer, envbuilder]
|
||||
---
|
||||
|
||||
# Remote Development on Docker Containers (with Envbuilder)
|
||||
|
||||
Provision Envbuilder containers based on `devcontainer.json` as [Coder workspaces](https://coder.com/docs/workspaces) in Docker with this example template.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Infrastructure
|
||||
|
||||
Coder must have access to a running Docker socket, and the `coder` user must be a member of the `docker` group:
|
||||
|
||||
```shell
|
||||
# Add coder user to Docker group
|
||||
sudo usermod -aG docker coder
|
||||
|
||||
# Restart Coder server
|
||||
sudo systemctl restart coder
|
||||
|
||||
# Test Docker
|
||||
sudo -u coder docker ps
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
Coder supports Envbuilder containers based on `devcontainer.json` via [envbuilder](https://github.com/coder/envbuilder), an open source project. Read more about this in [Coder's documentation](https://coder.com/docs/templates/dev-containers).
|
||||
|
||||
This template provisions the following resources:
|
||||
|
||||
- Envbuilder cached image (conditional, persistent) using [`terraform-provider-envbuilder`](https://github.com/coder/terraform-provider-envbuilder)
|
||||
- Docker image (persistent) using [`envbuilder`](https://github.com/coder/envbuilder)
|
||||
- Docker container (ephemeral)
|
||||
- Docker volume (persistent on `/workspaces`)
|
||||
|
||||
The Git repository is cloned inside the `/workspaces` volume if not present.
|
||||
Any local changes to the Devcontainer files inside the volume will be applied when you restart the workspace.
|
||||
Keep in mind that any tools or files outside of `/workspaces` or not added as part of the Devcontainer specification are not persisted.
|
||||
Edit the `devcontainer.json` instead!
|
||||
|
||||
> **Note**
|
||||
> This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.
|
||||
|
||||
## Docker-in-Docker
|
||||
|
||||
See the [Envbuilder documentation](https://github.com/coder/envbuilder/blob/main/docs/docker.md) for information on running Docker containers inside an Envbuilder container.
|
||||
|
||||
## Caching
|
||||
|
||||
To speed up your builds, you can use a container registry as a cache.
|
||||
When creating the template, set the parameter `cache_repo` to a valid Docker repository.
|
||||
|
||||
For example, you can run a local registry:
|
||||
|
||||
```shell
|
||||
docker run --detach \
|
||||
--volume registry-cache:/var/lib/registry \
|
||||
--publish 5000:5000 \
|
||||
--name registry-cache \
|
||||
--net=host \
|
||||
registry:2
|
||||
```
|
||||
|
||||
Then, when creating the template, enter `localhost:5000/envbuilder-cache` for the parameter `cache_repo`.
|
||||
|
||||
See the [Envbuilder Terraform Provider Examples](https://github.com/coder/terraform-provider-envbuilder/blob/main/examples/resources/envbuilder_cached_image/envbuilder_cached_image_resource.tf/) for a more complete example of how the provider works.
|
||||
|
||||
> [!NOTE]
|
||||
> We recommend using a registry cache with authentication enabled.
|
||||
> To allow Envbuilder to authenticate with the registry cache, specify the variable `cache_repo_docker_config_path`
|
||||
> with the path to a Docker config `.json` on disk containing valid credentials for the registry.
|
372
examples/templates/docker-envbuilder/main.tf
Normal file
372
examples/templates/docker-envbuilder/main.tf
Normal file
@ -0,0 +1,372 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "~> 2.0"
|
||||
}
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
}
|
||||
envbuilder = {
|
||||
source = "coder/envbuilder"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "docker_socket" {
|
||||
default = ""
|
||||
description = "(Optional) Docker socket URI"
|
||||
type = string
|
||||
}
|
||||
|
||||
provider "coder" {}
|
||||
provider "docker" {
|
||||
# Defaulting to null if the variable is an empty string lets us have an optional variable without having to set our own default
|
||||
host = var.docker_socket != "" ? var.docker_socket : null
|
||||
}
|
||||
provider "envbuilder" {}
|
||||
|
||||
data "coder_provisioner" "me" {}
|
||||
data "coder_workspace" "me" {}
|
||||
data "coder_workspace_owner" "me" {}
|
||||
|
||||
data "coder_parameter" "repo" {
|
||||
description = "Select a repository to automatically clone and start working with a devcontainer."
|
||||
display_name = "Repository (auto)"
|
||||
mutable = true
|
||||
name = "repo"
|
||||
option {
|
||||
name = "vercel/next.js"
|
||||
description = "The React Framework"
|
||||
value = "https://github.com/vercel/next.js"
|
||||
}
|
||||
option {
|
||||
name = "home-assistant/core"
|
||||
description = "🏡 Open source home automation that puts local control and privacy first."
|
||||
value = "https://github.com/home-assistant/core"
|
||||
}
|
||||
option {
|
||||
name = "discourse/discourse"
|
||||
description = "A platform for community discussion. Free, open, simple."
|
||||
value = "https://github.com/discourse/discourse"
|
||||
}
|
||||
option {
|
||||
name = "denoland/deno"
|
||||
description = "A modern runtime for JavaScript and TypeScript."
|
||||
value = "https://github.com/denoland/deno"
|
||||
}
|
||||
option {
|
||||
name = "microsoft/vscode"
|
||||
icon = "/icon/code.svg"
|
||||
description = "Code editing. Redefined."
|
||||
value = "https://github.com/microsoft/vscode"
|
||||
}
|
||||
option {
|
||||
name = "Custom"
|
||||
icon = "/emojis/1f5c3.png"
|
||||
description = "Specify a custom repo URL below"
|
||||
value = "custom"
|
||||
}
|
||||
order = 1
|
||||
}
|
||||
|
||||
data "coder_parameter" "custom_repo_url" {
|
||||
default = ""
|
||||
description = "Optionally enter a custom repository URL, see [awesome-devcontainers](https://github.com/manekinekko/awesome-devcontainers)."
|
||||
display_name = "Repository URL (custom)"
|
||||
name = "custom_repo_url"
|
||||
mutable = true
|
||||
order = 2
|
||||
}
|
||||
|
||||
data "coder_parameter" "fallback_image" {
|
||||
default = "codercom/enterprise-base:ubuntu"
|
||||
description = "This image runs if the devcontainer fails to build."
|
||||
display_name = "Fallback Image"
|
||||
mutable = true
|
||||
name = "fallback_image"
|
||||
order = 3
|
||||
}
|
||||
|
||||
data "coder_parameter" "devcontainer_builder" {
|
||||
description = <<-EOF
|
||||
Image that will build the devcontainer.
|
||||
We highly recommend using a specific release as the `:latest` tag will change.
|
||||
Find the latest version of Envbuilder here: https://github.com/coder/envbuilder/pkgs/container/envbuilder
|
||||
EOF
|
||||
display_name = "Devcontainer Builder"
|
||||
mutable = true
|
||||
name = "devcontainer_builder"
|
||||
default = "ghcr.io/coder/envbuilder:latest"
|
||||
order = 4
|
||||
}
|
||||
|
||||
variable "cache_repo" {
|
||||
default = ""
|
||||
description = "(Optional) Use a container registry as a cache to speed up builds."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "insecure_cache_repo" {
|
||||
default = false
|
||||
description = "Enable this option if your cache registry does not serve HTTPS."
|
||||
type = bool
|
||||
}
|
||||
|
||||
variable "cache_repo_docker_config_path" {
|
||||
default = ""
|
||||
description = "(Optional) Path to a docker config.json containing credentials to the provided cache repo, if required."
|
||||
sensitive = true
|
||||
type = string
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
|
||||
devcontainer_builder_image = data.coder_parameter.devcontainer_builder.value
|
||||
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
|
||||
repo_url = data.coder_parameter.repo.value == "custom" ? data.coder_parameter.custom_repo_url.value : data.coder_parameter.repo.value
|
||||
# The envbuilder provider requires a key-value map of environment variables.
|
||||
envbuilder_env = {
|
||||
# ENVBUILDER_GIT_URL and ENVBUILDER_CACHE_REPO will be overridden by the provider
|
||||
# if the cache repo is enabled.
|
||||
"ENVBUILDER_GIT_URL" : local.repo_url,
|
||||
"ENVBUILDER_CACHE_REPO" : var.cache_repo,
|
||||
"CODER_AGENT_TOKEN" : coder_agent.main.token,
|
||||
# Use the docker gateway if the access URL is 127.0.0.1
|
||||
"CODER_AGENT_URL" : replace(data.coder_workspace.me.access_url, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal"),
|
||||
# Use the docker gateway if the access URL is 127.0.0.1
|
||||
"ENVBUILDER_INIT_SCRIPT" : replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal"),
|
||||
"ENVBUILDER_FALLBACK_IMAGE" : data.coder_parameter.fallback_image.value,
|
||||
"ENVBUILDER_DOCKER_CONFIG_BASE64" : try(data.local_sensitive_file.cache_repo_dockerconfigjson[0].content_base64, ""),
|
||||
"ENVBUILDER_PUSH_IMAGE" : var.cache_repo == "" ? "" : "true",
|
||||
"ENVBUILDER_INSECURE" : "${var.insecure_cache_repo}",
|
||||
}
|
||||
# Convert the above map to the format expected by the docker provider.
|
||||
docker_env = [
|
||||
for k, v in local.envbuilder_env : "${k}=${v}"
|
||||
]
|
||||
}
|
||||
|
||||
data "local_sensitive_file" "cache_repo_dockerconfigjson" {
|
||||
count = var.cache_repo_docker_config_path == "" ? 0 : 1
|
||||
filename = var.cache_repo_docker_config_path
|
||||
}
|
||||
|
||||
resource "docker_image" "devcontainer_builder_image" {
|
||||
name = local.devcontainer_builder_image
|
||||
keep_locally = true
|
||||
}
|
||||
|
||||
resource "docker_volume" "workspaces" {
|
||||
name = "coder-${data.coder_workspace.me.id}"
|
||||
# Protect the volume from being deleted due to changes in attributes.
|
||||
lifecycle {
|
||||
ignore_changes = all
|
||||
}
|
||||
# Add labels in Docker to keep track of orphan resources.
|
||||
labels {
|
||||
label = "coder.owner"
|
||||
value = data.coder_workspace_owner.me.name
|
||||
}
|
||||
labels {
|
||||
label = "coder.owner_id"
|
||||
value = data.coder_workspace_owner.me.id
|
||||
}
|
||||
labels {
|
||||
label = "coder.workspace_id"
|
||||
value = data.coder_workspace.me.id
|
||||
}
|
||||
# This field becomes outdated if the workspace is renamed but can
|
||||
# be useful for debugging or cleaning out dangling volumes.
|
||||
labels {
|
||||
label = "coder.workspace_name_at_creation"
|
||||
value = data.coder_workspace.me.name
|
||||
}
|
||||
}
|
||||
|
||||
# Check for the presence of a prebuilt image in the cache repo
|
||||
# that we can use instead.
|
||||
resource "envbuilder_cached_image" "cached" {
|
||||
count = var.cache_repo == "" ? 0 : data.coder_workspace.me.start_count
|
||||
builder_image = local.devcontainer_builder_image
|
||||
git_url = local.repo_url
|
||||
cache_repo = var.cache_repo
|
||||
extra_env = local.envbuilder_env
|
||||
insecure = var.insecure_cache_repo
|
||||
}
|
||||
|
||||
resource "docker_container" "workspace" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
image = var.cache_repo == "" ? local.devcontainer_builder_image : envbuilder_cached_image.cached.0.image
|
||||
# Uses lower() to avoid Docker restriction on container names.
|
||||
name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
|
||||
# Hostname makes the shell more user friendly: coder@my-workspace:~$
|
||||
hostname = data.coder_workspace.me.name
|
||||
# Use the environment specified by the envbuilder provider, if available.
|
||||
env = var.cache_repo == "" ? local.docker_env : envbuilder_cached_image.cached.0.env
|
||||
# network_mode = "host" # Uncomment if testing with a registry running on `localhost`.
|
||||
host {
|
||||
host = "host.docker.internal"
|
||||
ip = "host-gateway"
|
||||
}
|
||||
volumes {
|
||||
container_path = "/workspaces"
|
||||
volume_name = docker_volume.workspaces.name
|
||||
read_only = false
|
||||
}
|
||||
# Add labels in Docker to keep track of orphan resources.
|
||||
labels {
|
||||
label = "coder.owner"
|
||||
value = data.coder_workspace_owner.me.name
|
||||
}
|
||||
labels {
|
||||
label = "coder.owner_id"
|
||||
value = data.coder_workspace_owner.me.id
|
||||
}
|
||||
labels {
|
||||
label = "coder.workspace_id"
|
||||
value = data.coder_workspace.me.id
|
||||
}
|
||||
labels {
|
||||
label = "coder.workspace_name"
|
||||
value = data.coder_workspace.me.name
|
||||
}
|
||||
}
|
||||
|
||||
resource "coder_agent" "main" {
|
||||
arch = data.coder_provisioner.me.arch
|
||||
os = "linux"
|
||||
startup_script = <<-EOT
|
||||
set -e
|
||||
|
||||
# Add any commands that should be executed at workspace startup (e.g install requirements, start a program, etc) here
|
||||
EOT
|
||||
dir = "/workspaces"
|
||||
|
||||
# These environment variables allow you to make Git commits right away after creating a
|
||||
# workspace. Note that they take precedence over configuration defined in ~/.gitconfig!
|
||||
# You can remove this block if you'd prefer to configure Git manually or using
|
||||
# dotfiles. (see docs/dotfiles.md)
|
||||
env = {
|
||||
GIT_AUTHOR_NAME = local.git_author_name
|
||||
GIT_AUTHOR_EMAIL = local.git_author_email
|
||||
GIT_COMMITTER_NAME = local.git_author_name
|
||||
GIT_COMMITTER_EMAIL = local.git_author_email
|
||||
}
|
||||
|
||||
# The following metadata blocks are optional. They are used to display
|
||||
# information about your workspace in the dashboard. You can remove them
|
||||
# if you don't want to display any information.
|
||||
# For basic resources, you can use the `coder stat` command.
|
||||
# If you need more control, you can write your own script.
|
||||
metadata {
|
||||
display_name = "CPU Usage"
|
||||
key = "0_cpu_usage"
|
||||
script = "coder stat cpu"
|
||||
interval = 10
|
||||
timeout = 1
|
||||
}
|
||||
|
||||
metadata {
|
||||
display_name = "RAM Usage"
|
||||
key = "1_ram_usage"
|
||||
script = "coder stat mem"
|
||||
interval = 10
|
||||
timeout = 1
|
||||
}
|
||||
|
||||
metadata {
|
||||
display_name = "Home Disk"
|
||||
key = "3_home_disk"
|
||||
script = "coder stat disk --path $HOME"
|
||||
interval = 60
|
||||
timeout = 1
|
||||
}
|
||||
|
||||
metadata {
|
||||
display_name = "CPU Usage (Host)"
|
||||
key = "4_cpu_usage_host"
|
||||
script = "coder stat cpu --host"
|
||||
interval = 10
|
||||
timeout = 1
|
||||
}
|
||||
|
||||
metadata {
|
||||
display_name = "Memory Usage (Host)"
|
||||
key = "5_mem_usage_host"
|
||||
script = "coder stat mem --host"
|
||||
interval = 10
|
||||
timeout = 1
|
||||
}
|
||||
|
||||
metadata {
|
||||
display_name = "Load Average (Host)"
|
||||
key = "6_load_host"
|
||||
# get load avg scaled by number of cores
|
||||
script = <<EOT
|
||||
echo "`cat /proc/loadavg | awk '{ print $1 }'` `nproc`" | awk '{ printf "%0.2f", $1/$2 }'
|
||||
EOT
|
||||
interval = 60
|
||||
timeout = 1
|
||||
}
|
||||
|
||||
metadata {
|
||||
display_name = "Swap Usage (Host)"
|
||||
key = "7_swap_host"
|
||||
script = <<EOT
|
||||
free -b | awk '/^Swap/ { printf("%.1f/%.1f", $3/1024.0/1024.0/1024.0, $2/1024.0/1024.0/1024.0) }'
|
||||
EOT
|
||||
interval = 10
|
||||
timeout = 1
|
||||
}
|
||||
}
|
||||
|
||||
# See https://registry.coder.com/modules/coder/code-server
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/code-server/coder"
|
||||
|
||||
# This ensures that the latest non-breaking version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production.
|
||||
version = "~> 1.0"
|
||||
|
||||
agent_id = coder_agent.main.id
|
||||
order = 1
|
||||
}
|
||||
|
||||
# See https://registry.coder.com/modules/coder/jetbrains-gateway
|
||||
module "jetbrains_gateway" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains-gateway/coder"
|
||||
|
||||
# JetBrains IDEs to make available for the user to select
|
||||
jetbrains_ides = ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD", "RR"]
|
||||
default = "IU"
|
||||
|
||||
# Default folder to open when starting a JetBrains IDE
|
||||
folder = "/workspaces"
|
||||
|
||||
# This ensures that the latest non-breaking version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production.
|
||||
version = "~> 1.0"
|
||||
|
||||
agent_id = coder_agent.main.id
|
||||
agent_name = "main"
|
||||
order = 2
|
||||
}
|
||||
|
||||
resource "coder_metadata" "container_info" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
resource_id = coder_agent.main.id
|
||||
item {
|
||||
key = "workspace image"
|
||||
value = var.cache_repo == "" ? local.devcontainer_builder_image : envbuilder_cached_image.cached.0.image
|
||||
}
|
||||
item {
|
||||
key = "git url"
|
||||
value = local.repo_url
|
||||
}
|
||||
item {
|
||||
key = "cache repo"
|
||||
value = var.cache_repo == "" ? "not enabled" : var.cache_repo
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user