diff --git a/coderd/templatebuilder/bases/docker/README.md b/coderd/templatebuilder/bases/docker/README.md index 6398547ef5f..60b95bfbbf9 100644 --- a/coderd/templatebuilder/bases/docker/README.md +++ b/coderd/templatebuilder/bases/docker/README.md @@ -17,6 +17,19 @@ Provision Docker containers as [Coder workspaces](https://coder.com/docs/user-gu ## Prerequisites +### Workspace image + +The container image determines what tools, languages, and runtimes are available in the workspace out of the box, so it has a major impact on the developer experience. + +Some options to consider: + +- [`codercom/example-base:ubuntu`](https://github.com/coder/images/tree/main/images/base) (default): minimal and lightweight, but may not include many tools developers expect by default +- [`codercom/example-universal:ubuntu`](https://github.com/coder/images/tree/main/images/universal): catch-all image with many languages and tools available, but larger and slower to pull + +More language-specific images (Go, Java, Node.js, and more) are available in [coder/images](https://github.com/coder/images), and the [devcontainers/images](https://github.com/devcontainers/images) collection is another good source of ready-made development images. +You can also build your own image to pre-bake the exact tools your team needs. +Visit [Coder's image management docs](https://coder.com/docs/admin/templates/managing-templates/image-management) for additional guidance. + ### Infrastructure The VM you run Coder on must have a running Docker socket and the `coder` user must be added to the Docker group: diff --git a/coderd/templatebuilder/bases/docker/base.json b/coderd/templatebuilder/bases/docker/base.json index 09b8224c014..1ded9b022ae 100644 --- a/coderd/templatebuilder/bases/docker/base.json +++ b/coderd/templatebuilder/bases/docker/base.json @@ -3,6 +3,17 @@ "display_name": "Docker", "os": "linux", "default_context": { - "container_image": "codercom/enterprise-base:ubuntu" - } + "container_image": "codercom/example-base:ubuntu" + }, + "variables": [ + { + "name": "container_image", + "type": "string", + "description": "Container image for workspaces. The image determines which tools and languages are available in the workspace by default. See the template README for guidance on choosing an image.", + "default": "codercom/example-base:ubuntu", + "required": false, + "sensitive": false, + "computed": false + } + ] } diff --git a/coderd/templatebuilder/bases/docker/main.tf.tmpl b/coderd/templatebuilder/bases/docker/main.tf.tmpl index b044974892c..c462ef9094d 100644 --- a/coderd/templatebuilder/bases/docker/main.tf.tmpl +++ b/coderd/templatebuilder/bases/docker/main.tf.tmpl @@ -166,7 +166,7 @@ resource "docker_container" "workspace" { {{- if .ImageOptions }} image = data.coder_parameter.container_image.value {{- else }} - image = "{{ .ContainerImage }}" + image = {{ .Variables.container_image }} {{- end }} # Uses lower() to avoid Docker restriction on container names. name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" diff --git a/coderd/templatebuilder/bases/kubernetes/README.md b/coderd/templatebuilder/bases/kubernetes/README.md index 26e7e07e381..f97f78c7d43 100644 --- a/coderd/templatebuilder/bases/kubernetes/README.md +++ b/coderd/templatebuilder/bases/kubernetes/README.md @@ -21,7 +21,18 @@ Provision Kubernetes Pods as [Coder workspaces](https://coder.com/docs/user-guid **Cluster**: This template requires an existing Kubernetes cluster -**Container Image**: This template uses the [codercom/enterprise-base:ubuntu image](https://github.com/coder/enterprise-images/tree/main/images/base) with some dev tools preinstalled. To add additional tools, extend this image or build it yourself. +### Workspace image + +The container image determines what tools, languages, and runtimes are available in the workspace out of the box, so it has a major impact on the developer experience. + +Some options to consider: + +- [`codercom/example-base:ubuntu`](https://github.com/coder/images/tree/main/images/base) (default): minimal and lightweight, but may not include many tools developers expect by default +- [`codercom/example-universal:ubuntu`](https://github.com/coder/images/tree/main/images/universal): catch-all image with many languages and tools available, but larger and slower to pull + +More language-specific images (Go, Java, Node.js, and more) are available in [coder/images](https://github.com/coder/images), and the [devcontainers/images](https://github.com/devcontainers/images) collection is another good source of ready-made development images. +You can also build your own image to pre-bake the exact tools your team needs. +Visit [Coder's image management docs](https://coder.com/docs/admin/templates/managing-templates/image-management) for additional guidance. ### Authentication diff --git a/coderd/templatebuilder/bases/kubernetes/base.json b/coderd/templatebuilder/bases/kubernetes/base.json index a3b04acbaa9..d6c4583b26f 100644 --- a/coderd/templatebuilder/bases/kubernetes/base.json +++ b/coderd/templatebuilder/bases/kubernetes/base.json @@ -3,9 +3,18 @@ "display_name": "Kubernetes", "os": "linux", "default_context": { - "container_image": "codercom/enterprise-base:ubuntu" + "container_image": "codercom/example-base:ubuntu" }, "variables": [ + { + "name": "container_image", + "type": "string", + "description": "Container image for workspaces. The image determines which tools and languages are available in the workspace by default. See the template README for guidance on choosing an image.", + "default": "codercom/example-base:ubuntu", + "required": false, + "sensitive": false, + "computed": false + }, { "name": "use_kubeconfig", "type": "bool", diff --git a/coderd/templatebuilder/bases/kubernetes/main.tf.tmpl b/coderd/templatebuilder/bases/kubernetes/main.tf.tmpl index b50495ffb35..d32c8e0bd90 100644 --- a/coderd/templatebuilder/bases/kubernetes/main.tf.tmpl +++ b/coderd/templatebuilder/bases/kubernetes/main.tf.tmpl @@ -260,7 +260,7 @@ resource "kubernetes_deployment_v1" "main" { {{- if .ImageOptions }} image = data.coder_parameter.container_image.value {{- else }} - image = "{{ .ContainerImage }}" + image = {{ .Variables.container_image }} {{- end }} image_pull_policy = "Always" command = ["sh", "-c", coder_agent.main.init_script] diff --git a/coderd/templatebuilder/bases_test.go b/coderd/templatebuilder/bases_test.go index cb2109331c1..f01f6f48a04 100644 --- a/coderd/templatebuilder/bases_test.go +++ b/coderd/templatebuilder/bases_test.go @@ -65,14 +65,14 @@ func TestDefaultBaseRenderContext(t *testing.T) { t.Run("Docker", func(t *testing.T) { t.Parallel() rc := templatebuilder.DefaultBaseRenderContext("docker") - require.Equal(t, "codercom/enterprise-base:ubuntu", rc.ContainerImage) + require.Equal(t, "codercom/example-base:ubuntu", rc.ContainerImage) require.Nil(t, rc.ImageOptions) }) t.Run("Kubernetes", func(t *testing.T) { t.Parallel() rc := templatebuilder.DefaultBaseRenderContext("kubernetes") - require.Equal(t, "codercom/enterprise-base:ubuntu", rc.ContainerImage) + require.Equal(t, "codercom/example-base:ubuntu", rc.ContainerImage) require.Nil(t, rc.ImageOptions) }) diff --git a/coderd/templatebuilder/compose_test.go b/coderd/templatebuilder/compose_test.go index b15692036fd..dca7877f826 100644 --- a/coderd/templatebuilder/compose_test.go +++ b/coderd/templatebuilder/compose_test.go @@ -290,6 +290,60 @@ func TestCompose(t *testing.T) { require.Contains(t, err.Error(), "interpolation") }) + t.Run("DockerDefaultContainerImage", func(t *testing.T) { + t.Parallel() + result, err := templatebuilder.Compose(templatebuilder.ComposeRequest{ + BaseTemplateID: "docker", + RegistryURL: "https://registry.coder.com", + }) + require.NoError(t, err) + require.Contains(t, string(result.MainTF), `"codercom/example-base:ubuntu"`) + }) + + t.Run("DockerCustomContainerImage", func(t *testing.T) { + t.Parallel() + result, err := templatebuilder.Compose(templatebuilder.ComposeRequest{ + BaseTemplateID: "docker", + RegistryURL: "https://registry.coder.com", + BaseVariableValues: map[string]string{ + "container_image": "myregistry/myimage:v2", + }, + }) + require.NoError(t, err) + mainTF := string(result.MainTF) + require.Contains(t, mainTF, `"myregistry/myimage:v2"`) + require.NotContains(t, mainTF, `codercom/example-base:ubuntu`) + }) + + t.Run("KubernetesDefaultContainerImage", func(t *testing.T) { + t.Parallel() + result, err := templatebuilder.Compose(templatebuilder.ComposeRequest{ + BaseTemplateID: "kubernetes", + RegistryURL: "https://registry.coder.com", + BaseVariableValues: map[string]string{ + "namespace": "default", + }, + }) + require.NoError(t, err) + require.Contains(t, string(result.MainTF), `"codercom/example-base:ubuntu"`) + }) + + t.Run("KubernetesCustomContainerImage", func(t *testing.T) { + t.Parallel() + result, err := templatebuilder.Compose(templatebuilder.ComposeRequest{ + BaseTemplateID: "kubernetes", + RegistryURL: "https://registry.coder.com", + BaseVariableValues: map[string]string{ + "namespace": "default", + "container_image": "custom/workspace:latest", + }, + }) + require.NoError(t, err) + mainTF := string(result.MainTF) + require.Contains(t, mainTF, `"custom/workspace:latest"`) + require.NotContains(t, mainTF, `codercom/example-base:ubuntu`) + }) + t.Run("MissingRequiredVariable", func(t *testing.T) { t.Parallel() // git-clone has a required "url" variable with no default. diff --git a/coderd/templatebuilder/testdata/docker.tf.golden b/coderd/templatebuilder/testdata/docker.tf.golden index 11d9ded4f64..9efa2ee27a7 100644 --- a/coderd/templatebuilder/testdata/docker.tf.golden +++ b/coderd/templatebuilder/testdata/docker.tf.golden @@ -150,7 +150,7 @@ resource "docker_volume" "home_volume" { resource "docker_container" "workspace" { count = data.coder_workspace.me.start_count - image = "codercom/enterprise-base:ubuntu" + image = "codercom/example-base:ubuntu" # 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:~$ diff --git a/coderd/templatebuilder/testdata/kubernetes.tf.golden b/coderd/templatebuilder/testdata/kubernetes.tf.golden index 876f3c1dfc2..7bcb33e298a 100644 --- a/coderd/templatebuilder/testdata/kubernetes.tf.golden +++ b/coderd/templatebuilder/testdata/kubernetes.tf.golden @@ -244,7 +244,7 @@ resource "kubernetes_deployment_v1" "main" { container { name = "dev" - image = "codercom/enterprise-base:ubuntu" + image = "codercom/example-base:ubuntu" image_pull_policy = "Always" command = ["sh", "-c", coder_agent.main.init_script] security_context { diff --git a/coderd/templatebuilder_handler_test.go b/coderd/templatebuilder_handler_test.go index baa89e80aeb..3629bb2d27b 100644 --- a/coderd/templatebuilder_handler_test.go +++ b/coderd/templatebuilder_handler_test.go @@ -45,13 +45,14 @@ func TestTemplateBuilderBases(t *testing.T) { { id: "docker", expectedOS: "linux", - hasVariables: false, + hasVariables: true, + expectedVars: []string{"container_image"}, }, { id: "kubernetes", expectedOS: "linux", hasVariables: true, - expectedVars: []string{"namespace", "use_kubeconfig"}, + expectedVars: []string{"container_image", "namespace", "use_kubeconfig"}, }, { id: "aws-linux", diff --git a/docs/admin/templates/managing-templates/image-management.md b/docs/admin/templates/managing-templates/image-management.md index 0a7cca2d623..ac7b1e2dcae 100644 --- a/docs/admin/templates/managing-templates/image-management.md +++ b/docs/admin/templates/managing-templates/image-management.md @@ -1,24 +1,27 @@ # Image Management While Coder provides example -[base container images](https://github.com/coder/enterprise-images) for -workspaces, it's often best to create custom images that matches the needs of -your users. This document serves a guide to operational maturity with some best -practices around managing workspaces images for Coder. +[container images](https://github.com/coder/images) for +workspaces, it's often best to create custom images that match the needs of +your users. This document serves as a guide to operational maturity with some +best practices around managing workspace images for Coder. -1. Create a minimal base image -2. Create golden image(s) with standard tooling -3. Allow developers to bring their own images and customizations with Dev - Containers +After following this tutorial, you'll accomplish the following: + +1. Create a minimal base image. +2. Create golden images with standard tooling. +3. Create project-specific images for common use cases. +4. Let developers customize their own environment. An image is just one of the many properties defined within the template. Templates can pull images from a public image registry (e.g. Docker Hub) or an -internal one, thanks to Terraform. +internal one, thanks to Terraform. Each image reference below can be dropped +directly into a template's image variable or parameter. ## Create a minimal base image While you may not use this directly in Coder templates, it's useful to have a -minimal base image is a small image that contains only the necessary +minimal base image as a small image that contains only the necessary dependencies to work in your network and work with Coder. Here are some things to consider: @@ -31,17 +34,18 @@ to consider: `docker`, `bash`, `jq`, and/or internal tooling - Consider creating (and starting the container with) a non-root user -See Coder's -[example base image](https://github.com/coder/enterprise-images/tree/main/images/minimal) -for reference. +Examples: + +- [`codercom/example-minimal:ubuntu`](https://github.com/coder/images/tree/main/images/minimal): only the necessary dependencies for a Coder workspace to bootstrap +- [`codercom/example-base:ubuntu`](https://github.com/coder/images/tree/main/images/base): a slightly more padded starting point with common utilities preinstalled -## Create general-purpose golden image(s) with standard tooling +## Create golden images with standard tooling -It's often practical to have a few golden images that contain standard tooling -for developers. These images should contain a number of languages (e.g. Python, -Java, TypeScript), IDEs (VS Code, JetBrains, PyCharm), and other tools (e.g. -`docker`). Unlike project-specific images (which are also important), general -purpose images are great for: +Building on the base image, it's often practical to have a few golden images +that contain standard tooling for developers. These images should contain a +number of languages (e.g. Python, Java, TypeScript), IDEs (VS Code, JetBrains, +PyCharm), and other tools (e.g. `docker`). Unlike project-specific images +(which are also important), general purpose images are great for: - **Scripting:** Developers may just want to hop in a Coder workspace to run basic scripts or queries. @@ -61,14 +65,37 @@ most cases) with a well-defined scope. Examples: -- [Universal Dev Containers Image](https://github.com/devcontainers/images/tree/main/src/universal) +- [`codercom/example-universal:ubuntu`](https://github.com/coder/images/tree/main/images/universal): a catch-all image with many languages and tools preinstalled. Runs as the `coder` user, so it works with Coder templates out of the box. +- [`mcr.microsoft.com/devcontainers/universal`](https://github.com/devcontainers/images/tree/main/src/universal): the Universal Dev Containers image -## Allow developers to bring their own images and customizations with Dev Containers +## Create project-specific images for common use cases -While golden images are great for general use cases, developers will often need -specific tooling for their projects. The [Dev Container](https://containers.dev) -specification allows developers to define their projects dependencies within a -`devcontainer.json` in their Git repository. +Beyond golden images, create images scoped to a specific project, language, or +use case (e.g., a Go backend, a Node.js frontend, or a data science stack). +These images stay smaller and faster to pull than one larger image that tries to install all dependencies. + +Examples: -- [Configure a template for Dev Containers](../../integrations/devcontainers/integration.md) (recommended) -- [Learn about Envbuilder](../../integrations/devcontainers/envbuilder/index.md) (alternative for environments without Docker) +- [`codercom/example-golang:ubuntu`](https://github.com/coder/images/tree/main/images/golang), + [`codercom/example-java:ubuntu`](https://github.com/coder/images/tree/main/images/java), + [`codercom/example-node:ubuntu`](https://github.com/coder/images/tree/main/images/node): + Coder's example language-specific images, all running as the `coder` user. + Refer to [coder/images](https://github.com/coder/images) for the full list. +- [`codercom/oss-dogfood:latest`](https://github.com/coder/coder/tree/main/dogfood): + the image Coder's own engineers use to develop Coder. A good reference for a + project-specific image tailored to a specific team or monorepo setup. + +## Let developers customize their own environment + +Even with well-scoped images, developers will often need tooling that is +specific to their project or personal workflow. Instead of maintaining an +image for every combination, developers can layer their own customizations +on top of a smaller image: + +- [Dev Containers](https://containers.dev): developers define their project's + dependencies in a `devcontainer.json` in their Git repository, and Coder + builds the environment on top of your base image. Visit + [configure a template for Dev Containers](../../integrations/devcontainers/integration.md). +- [mise](https://mise.jdx.dev/): developers install and pin language runtimes + and CLI tools at workspace startup without rebuilding the image. Visit the + [install command-line tools guide](../../../get-started/customize-your-template/install-command-line-tools.md).