diff --git a/docs/get-started/customize-your-template/add-a-language.md b/docs/get-started/customize-your-template/add-a-language.md new file mode 100644 index 0000000000000..019aaad85a316 --- /dev/null +++ b/docs/get-started/customize-your-template/add-a-language.md @@ -0,0 +1,866 @@ +# Add a programming language to your template + +Now that you've finished [Launch your first workspace](../index.md), you can add another language toolchain to every workspace you create. + +The Quickstart template installs a language only when the workspace owner selects it from the **Programming Languages** parameter. +In this guide, you add Ruby as an option. + +> [!NOTE] +> This guide assumes your Quickstart template is open for editing. +> If it's not, you can edit the template from the web by finding the template, selecting the three dots menu, and selecting **Edit files**. +> Refer to [Customize workspace startup](./index.md#open-the-template-for-editing) for more information. + +## What you'll do + +- ✅ Add a Ruby option to the **Programming Languages** parameter. +- ✅ Publish the change and apply it to your running workspace. +- ✅ Install the Ruby toolchain so selecting Ruby works. + +## Parameters in brief + +A parameter is a question Coder asks when someone creates a workspace. +Each parameter comes from a `coder_parameter` [data source](https://developer.hashicorp.com/terraform/language/data-sources) in the template. +The **Programming Languages** parameter is a multi-select list, and each language the reader can choose is an `option` block: + +```tf +data "coder_parameter" "languages" { + name = "languages" + display_name = "Programming Languages" + description = "Select the languages to pre-install in your workspace" + type = "list(string)" + form_type = "multi-select" + default = jsonencode(["python"]) + mutable = true + icon = "/icon/code.svg" + order = 1 + + option { + name = "Python" + value = "python" + icon = "/icon/python.svg" + } + # ...more options +} +``` + +This produces the following dropdown parameter: + +![Programming Language dropdown parameter from the Quickstart template](../../images/screenshots/quickstart-language-param.png)_Programming Language dropdown parameter from the Quickstart template_ + +Adding an `option` adds a choice to that list. + +## Step 1: Add the Ruby option + +In `main.tf`, find the `data "coder_parameter" "languages"` block. +Add a Ruby `option` after the last existing option, before the parameter's closing brace: + +```tf + option { + name = "Ruby" + value = "ruby" + icon = "/icon/ruby.svg" + } +``` + +> [!IMPORTANT] +> The `option` block must sit at the same indentation as the other `option` blocks inside the parameter. +> Coder reads the parameter's choices from these blocks, so a misplaced `option` doesn't appear in the form. + +The `icon` value points at an icon bundled with your Coder deployment. +To browse the full set and copy a path, open `https:///icons`. + +Now publish the change as a new template version: + +
+ +### UI + +In the web editor, make the edit above in `main.tf`. +Select **Build**, wait for the build to pass, then select **Publish**. + +### CLI + +Make the edit in `~/coder-quickstart/main.tf`, then publish a new version: + +```sh +coder templates push -d ~/coder-quickstart -y quickstart +``` + +
+ +## Step 2: Add Ruby to your workspace + +Your workspace from [Launch your first workspace](../index.md) is still on the old template version. +Update it to the version you just published, and add Ruby to its **Programming Languages** selection: + +
+ +### UI + +1. From your workspace, open **Workspace settings** (top right), then select **Parameters**. +2. Add **Ruby** to the **Programming Languages** parameter. +3. Apply the change. Coder updates the workspace to the new template version and restarts it with Ruby selected. + +### CLI + +Update the workspace and re-select its parameters. +Replace `` with your workspace's name (run `coder list` to see it): + +```sh +coder update --always-prompt +``` + +When prompted for **Programming Languages**, add **Ruby**, then let the workspace rebuild. + +
+ +Adding the Ruby option to the parameter shows the following: + +![Programming Language dropdown parameter from the Quickstart template with Ruby](../../images/screenshots/quickstart-language-param-ruby.png)_Programming Language dropdown parameter from the Quickstart template with Ruby_ + +## Step 3: Check whether Ruby is installed + +When the workspace restarts, open a terminal and ask for the Ruby version: + +```sh +ruby --version +``` + +The command fails: + +```text +ruby: command not found +``` + +You added the option, selected it, and rebuilt the workspace, but Ruby isn't there. +Adding the `option` only changed the form. +It added Ruby to the list of choices, but nothing in the template acts on that choice yet. +A separate startup script installs each selected toolchain, and you haven't taught it about Ruby. + +## Step 4: Install Ruby when the workspace starts + +The template installs each selected language from `install-languages.sh.tftpl`, a startup script that runs when the workspace boots. +Open that file and add a branch that installs Ruby when the reader selects it: + +```sh +if echo "$LANGUAGES" | grep -q "ruby"; then + if command -v ruby >/dev/null 2>&1; then + echo "Ruby: $(ruby --version | head -1)" + else + echo "Installing Ruby toolchain..." + apt_update + sudo apt-get install -y -qq ruby-full + echo "Installed Ruby: $(ruby --version | head -1)" + fi +fi +``` + +The script installs Ruby with `apt-get`, the package manager built into the workspace image. + +> [!WARNING] +> Use the package manager the workspace image provides, not a personal one. +> If you replace the `apt-get` line with `brew install ruby`, the build fails: the `codercom/enterprise-base:ubuntu` image doesn't include Homebrew, so the workspace logs `brew: command not found` and Ruby never installs. +> To install a personal tool like a Homebrew formula in your own workspace, refer to [Install your own command-line tools](./install-command-line-tools.md). + +Publish the change, then update your workspace again: + +
+ +### UI + +1. In the web editor, make the edit above in `install-languages.sh.tftpl`. +2. Select **Build**, wait for the build to pass, then select **Publish**. +3. On your workspace's home tab, select **Update and restart**. + +### CLI + +Make the edit in `~/coder-quickstart/install-languages.sh.tftpl`, then publish and update: + +```sh +coder templates push -d ~/coder-quickstart -y quickstart +coder update +``` + +
+ +Ruby is already selected, so you don't change parameters this time. +When the workspace restarts, open a terminal and check again: + +```sh +ruby --version +``` + +This time the workspace reports a Ruby version. + +## Step 5: Add a Ruby preset + +Steps 1 through 4 make Ruby selectable as a parameter and install it at startup. +The Quickstart template also ships presets: named bundles of parameter values that appear on the workspace creation form: + +![Default presets with the Quickstart template](../../images/screenshots/quickstart-default-presets.png)_Default presets with the Quickstart template_ + +The shipped presets cover combinations like Go and Python, but none selects Ruby, so add one to keep the parameter and preset choices in sync. + +In `main.tf`, find the `# --- Presets ---` section and add a Ruby preset next to the existing ones: + +```tf +data "coder_workspace_preset" "ruby_dev" { + name = "Ruby Development" + icon = "/icon/ruby.svg" + parameters = { + languages = jsonencode(["ruby"]) + ides = jsonencode(["code-server", "jetbrains"]) + jetbrains_ides = jsonencode(["RM"]) + git_repo = "" + } +} +``` + +This preset adds JetBrains RubyMine as a default option for the language, but you can customize the preferred IDEs in this default based on your needs. + +Publish the template again. +The next time you create a workspace, **Ruby Development** appears in the preset list and preselects Ruby: + +![Updated presets with the Quickstart template](../../images/screenshots/quickstart-updated-presets.png)_Updated presets with the Quickstart template_ + +Expanding the preset parameters after selecting **Ruby Development** shows the parameter values Coder will use to create the workspace: + +![Expanded parameter values for the Ruby Development preset](../../images/screenshots/quickstart-ruby-preset.png)_Expanded parameter values for the Ruby Development preset_ + +## What just happened + +You changed two different things to add one language: + +- The `coder_parameter` `option` block added Ruby to the workspace creation form. +- The startup script installed the Ruby toolchain when a workspace owner selected Ruby. + +A parameter collects a choice. +A startup script acts on it. +A new language needs both. + +You also added support for a new preset, so workspace users can launch a workspace from this template quickly, without having to fill out the parameters manually. + +
+How does Coder install the language you pick? + +The selection travels through the template in four steps: + +1. The `option` block in `data "coder_parameter" "languages"` (`main.tf`) adds `ruby` to the values the form accepts. +2. When the workspace builds, `local.languages` decodes the selection in `main.tf`: + + ```tf + languages = jsondecode(data.coder_parameter.languages.value) + ``` + +3. `coder_script.install_languages` renders the startup script with that list and runs it on the agent (also in `main.tf`): + + ```tf + script = templatefile("${path.module}/install-languages.sh.tftpl", { + LANGUAGES = join(",", local.languages) + }) + ``` + +4. Inside the rendered script, the `ruby` branch matches and installs the toolchain: + + ```sh + if echo "$LANGUAGES" | grep -q "ruby"; then + ``` + +The first three steps ran as soon as you added the option, which is why Ruby appeared in the form. +Step 4 is the part you were missing in Step 3, so the script had nothing to do for `ruby`. + +
+ +
+Why a .tftpl file instead of a plain script? + +The install script needs to know which languages the workspace owner selected, and only Terraform has that value when the workspace builds. +`templatefile()` renders `install-languages.sh.tftpl` and replaces `${LANGUAGES}` with `join(",", local.languages)`, producing a finished script with the selection baked in. +A static `.sh` file couldn't receive that value, so the `.tftpl` is the bridge between the parameter and the shell script. + +
+ +## Final code + +
+ +The complete template files + +Your template files after this guide's changes, starting from the Quickstart template: + +
+ +### main.tf + +```tf +terraform { + required_providers { + coder = { + source = "coder/coder" + } + docker = { + source = "kreuzwerker/docker" + } + external = { + source = "hashicorp/external" + } + } +} + +variable "docker_socket" { + default = "" + description = "(Optional) Docker socket URI" + type = string +} + +provider "docker" { + host = var.docker_socket != "" ? var.docker_socket : null +} + +data "coder_provisioner" "me" {} +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +# --- Parameters --- + +data "coder_parameter" "languages" { + name = "languages" + display_name = "Programming Languages" + description = "Select the languages to pre-install in your workspace" + type = "list(string)" + form_type = "multi-select" + default = jsonencode(["python"]) + mutable = true + icon = "/icon/code.svg" + order = 1 + + option { + name = "Python" + value = "python" + icon = "/icon/python.svg" + } + option { + name = "Node.js" + value = "nodejs" + icon = "/icon/nodejs.svg" + } + option { + name = "Go" + value = "go" + icon = "/icon/go.svg" + } + option { + name = "Rust" + value = "rust" + icon = "/icon/rust.svg" + } + option { + name = "Java" + value = "java" + icon = "/icon/java.svg" + } + option { + name = "C/C++" + value = "cpp" + icon = "/icon/cpp.svg" + } + option { + name = "Ruby" + value = "ruby" + icon = "/icon/ruby.svg" + } +} + +data "coder_parameter" "ides" { + name = "ides" + display_name = "IDEs & Editors" + description = "Select the development environments for your workspace" + type = "list(string)" + form_type = "multi-select" + default = jsonencode(["code-server"]) + mutable = true + icon = "/icon/code.svg" + order = 2 + + option { + name = "VS Code (Browser)" + value = "code-server" + icon = "/icon/code.svg" + } + option { + name = "Cursor" + value = "cursor" + icon = "/icon/cursor.svg" + } + option { + name = "JetBrains IDEs" + value = "jetbrains" + icon = "/icon/jetbrains.svg" + } + option { + name = "Zed" + value = "zed" + icon = "/icon/zed.svg" + } + option { + name = "Windsurf" + value = "windsurf" + icon = "/icon/windsurf.svg" + } +} + +# Shown only when "JetBrains IDEs" is selected in the IDEs parameter. +# Pre-selects IDEs that match the chosen languages. +data "coder_parameter" "jetbrains_ides" { + count = contains(local.ides, "jetbrains") ? 1 : 0 + name = "jetbrains_ides" + display_name = "JetBrains IDEs" + description = "Select the JetBrains IDEs to install" + type = "list(string)" + form_type = "multi-select" + default = jsonencode(local.jetbrains_ides_from_languages) + mutable = true + icon = "/icon/jetbrains.svg" + order = 3 + + option { + name = "IntelliJ IDEA" + value = "IU" + icon = "/icon/intellij.svg" + } + option { + name = "PyCharm" + value = "PY" + icon = "/icon/pycharm.svg" + } + option { + name = "GoLand" + value = "GO" + icon = "/icon/goland.svg" + } + option { + name = "WebStorm" + value = "WS" + icon = "/icon/webstorm.svg" + } + option { + name = "RustRover" + value = "RR" + icon = "/icon/rustrover.svg" + } + option { + name = "CLion" + value = "CL" + icon = "/icon/clion.svg" + } + option { + name = "PhpStorm" + value = "PS" + icon = "/icon/phpstorm.svg" + } + option { + name = "RubyMine" + value = "RM" + icon = "/icon/rubymine.svg" + } + option { + name = "Rider" + value = "RD" + icon = "/icon/rider.svg" + } +} + +data "coder_parameter" "git_repo" { + name = "git_repo" + display_name = "Git Repository (Optional)" + description = "URL of a Git repository to clone into your workspace (leave empty to skip)" + type = "string" + default = "" + mutable = true + icon = "/icon/git.svg" + order = 4 +} + +# --- Locals --- + +locals { + username = data.coder_workspace_owner.me.name + languages = jsondecode(data.coder_parameter.languages.value) + ides = jsondecode(data.coder_parameter.ides.value) + + # Map selected languages to the relevant JetBrains IDE product codes. + # Used as the default for the JetBrains IDE selector parameter. + jetbrains_by_language = { + python = ["PY"] + go = ["GO"] + java = ["IU"] + nodejs = ["WS"] + rust = ["RR"] + cpp = ["CL"] + } + jetbrains_ides_from_languages = distinct(flatten([ + for lang in local.languages : lookup(local.jetbrains_by_language, lang, []) + ])) + + # The actual JetBrains IDEs to install, from the user's selection + # in the conditional JetBrains parameter (or empty if not shown). + jetbrains_selected = contains(local.ides, "jetbrains") ? jsondecode(data.coder_parameter.jetbrains_ides[0].value) : [] +} + +# --- Agent --- + +resource "coder_agent" "main" { + arch = data.coder_provisioner.me.arch + os = "linux" + startup_script = <<-EOT + set -e + if [ ! -f ~/.init_done ]; then + cp -rT /etc/skel ~ + touch ~/.init_done + fi + EOT + + 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}" + } + + 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 + } +} + +# --- Language installation --- +# All languages install in a single script to avoid apt-get lock +# conflicts (coder_script resources run in parallel). + +resource "coder_script" "install_languages" { + count = length(local.languages) > 0 ? 1 : 0 + agent_id = coder_agent.main.id + display_name = "Install Languages" + icon = "/icon/code.svg" + run_on_start = true + start_blocks_login = true + script = templatefile("${path.module}/install-languages.sh.tftpl", { + LANGUAGES = join(",", local.languages) + }) +} + +# --- IDE modules --- + +module "code-server" { + count = data.coder_workspace.me.start_count * (contains(local.ides, "code-server") ? 1 : 0) + source = "registry.coder.com/coder/code-server/coder" + version = "~> 1.0" + agent_id = coder_agent.main.id + order = 1 +} + +module "cursor" { + count = data.coder_workspace.me.start_count * (contains(local.ides, "cursor") ? 1 : 0) + source = "registry.coder.com/coder/cursor/coder" + version = "~> 1.0" + agent_id = coder_agent.main.id + folder = "/home/coder" + order = 3 +} + +# TODO: Re-add the coder/jetbrains module once Coder's dynamic +# parameter system respects module count for parameter visibility. +# The module's internal coder_parameter appears even when count = 0, +# creating a ghost parameter in the workspace creation form. +# module "jetbrains" { +# count = data.coder_workspace.me.start_count * (contains(local.ides, "jetbrains") && length(local.jetbrains_selected) > 0 ? 1 : 0) +# source = "registry.coder.com/coder/jetbrains/coder" +# version = "~> 1.0" +# agent_id = coder_agent.main.id +# folder = "/home/coder" +# default = toset(local.jetbrains_selected) +# } + +module "zed" { + count = data.coder_workspace.me.start_count * (contains(local.ides, "zed") ? 1 : 0) + source = "registry.coder.com/coder/zed/coder" + version = "~> 1.0" + agent_id = coder_agent.main.id + folder = "/home/coder" + order = 5 +} + +module "windsurf" { + count = data.coder_workspace.me.start_count * (contains(local.ides, "windsurf") ? 1 : 0) + source = "registry.coder.com/coder/windsurf/coder" + version = "~> 1.0" + agent_id = coder_agent.main.id + folder = "/home/coder" + order = 6 +} + +# --- Git clone --- + +module "git-clone" { + count = data.coder_workspace.me.start_count * (data.coder_parameter.git_repo.value != "" ? 1 : 0) + source = "registry.coder.com/coder/git-clone/coder" + version = "~> 2.0" + agent_id = coder_agent.main.id + url = data.coder_parameter.git_repo.value +} + +# --- Presets --- + +data "coder_workspace_preset" "web_dev" { + name = "Web Development" + icon = "/icon/nodejs.svg" + parameters = { + languages = jsonencode(["python", "nodejs"]) + ides = jsonencode(["code-server"]) + git_repo = "" + } +} + +data "coder_workspace_preset" "backend_go" { + name = "Backend (Go)" + icon = "/icon/go.svg" + parameters = { + languages = jsonencode(["go"]) + ides = jsonencode(["code-server", "jetbrains"]) + jetbrains_ides = jsonencode(["GO"]) + git_repo = "" + } +} + +data "coder_workspace_preset" "data_science" { + name = "Data Science" + icon = "/icon/python.svg" + parameters = { + languages = jsonencode(["python"]) + ides = jsonencode(["code-server"]) + git_repo = "" + } +} + +data "coder_workspace_preset" "full_stack" { + name = "Full Stack" + icon = "/icon/code.svg" + parameters = { + languages = jsonencode(["python", "nodejs", "go"]) + ides = jsonencode(["code-server", "cursor"]) + git_repo = "" + } +} + +data "coder_workspace_preset" "ruby_dev" { + name = "Ruby Development" + icon = "/icon/ruby.svg" + parameters = { + languages = jsonencode(["ruby"]) + ides = jsonencode(["code-server", "jetbrains"]) + jetbrains_ides = jsonencode(["RM"]) + git_repo = "" + } +} + +# --- Docker resources --- + +resource "docker_volume" "home_volume" { + name = "coder-${data.coder_workspace.me.id}-home" + lifecycle { + ignore_changes = all + } + labels { + label = "coder.owner" + value = data.coder_workspace_owner.me.name + } + labels { + label = "coder.owner_id" + value = data.coder_workspace_owner.me.id + } + labels { + label = "coder.workspace_id" + value = data.coder_workspace.me.id + } + labels { + label = "coder.workspace_name_at_creation" + value = data.coder_workspace.me.name + } + depends_on = [] +} + +resource "docker_container" "workspace" { + count = data.coder_workspace.me.start_count + image = "codercom/enterprise-base:ubuntu" + name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" + hostname = data.coder_workspace.me.name + entrypoint = [ + "sh", "-c", + replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal"), + ] + env = ["CODER_AGENT_TOKEN=${coder_agent.main.token}"] + host { + host = "host.docker.internal" + ip = "host-gateway" + } + volumes { + container_path = "/home/coder" + volume_name = docker_volume.home_volume.name + read_only = false + } + labels { + label = "coder.owner" + value = data.coder_workspace_owner.me.name + } + labels { + label = "coder.owner_id" + value = data.coder_workspace_owner.me.id + } + labels { + label = "coder.workspace_id" + value = data.coder_workspace.me.id + } + labels { + label = "coder.workspace_name" + value = data.coder_workspace.me.name + } + depends_on = [] +} +``` + +### install-languages.sh.tftpl + +```sh +#!/bin/bash +set -e + +LANGUAGES="${LANGUAGES}" +APT_UPDATED=false + +apt_update() { + if [ "$APT_UPDATED" = "false" ]; then + sudo apt-get update -qq + APT_UPDATED=true + fi +} + +if echo "$LANGUAGES" | grep -q "python"; then + if command -v python3 >/dev/null 2>&1; then + echo "Python: $(python3 --version)" + else + echo "Installing Python..." + apt_update + sudo apt-get install -y -qq python3 python3-pip python3-venv + echo "Installed Python: $(python3 --version)" + fi +fi + +if echo "$LANGUAGES" | grep -q "nodejs"; then + if command -v node >/dev/null 2>&1; then + echo "Node.js: $(node --version)" + else + echo "Installing Node.js 22..." + curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - + sudo apt-get install -y -qq nodejs + echo "Installed Node.js: $(node --version)" + fi +fi + +if echo "$LANGUAGES" | grep -q "go"; then + if command -v /usr/local/go/bin/go >/dev/null 2>&1; then + echo "Go: $(/usr/local/go/bin/go version)" + else + echo "Installing Go..." + ARCH=$(uname -m) + case $ARCH in + x86_64) GOARCH="amd64" ;; + aarch64) GOARCH="arm64" ;; + *) echo "Unsupported architecture: $ARCH"; exit 1 ;; + esac + GO_VERSION=$(curl -fsSL "https://go.dev/VERSION?m=text" | head -1) + curl -fsSL "https://go.dev/dl/$${GO_VERSION}.linux-$${GOARCH}.tar.gz" | sudo tar -C /usr/local -xz + echo 'export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin' | sudo tee /etc/profile.d/go.sh >/dev/null + echo "Installed Go: $(/usr/local/go/bin/go version)" + fi +fi + +if echo "$LANGUAGES" | grep -q "rust"; then + if command -v rustc >/dev/null 2>&1 || [ -f "$HOME/.cargo/bin/rustc" ]; then + RUSTC=$${HOME}/.cargo/bin/rustc + command -v rustc >/dev/null 2>&1 && RUSTC=rustc + echo "Rust: $($RUSTC --version)" + else + echo "Installing Rust..." + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + echo "Installed Rust: $($HOME/.cargo/bin/rustc --version)" + fi +fi + +if echo "$LANGUAGES" | grep -q "java"; then + if command -v java >/dev/null 2>&1; then + echo "Java: $(java --version 2>&1 | head -1)" + else + echo "Installing Java (OpenJDK 21)..." + apt_update + sudo apt-get install -y -qq openjdk-21-jdk + echo "Installed Java: $(java --version 2>&1 | head -1)" + fi +fi + +if echo "$LANGUAGES" | grep -q "cpp"; then + if command -v gcc >/dev/null 2>&1; then + echo "C/C++: $(gcc --version | head -1)" + else + echo "Installing C/C++ toolchain..." + apt_update + sudo apt-get install -y -qq gcc g++ make cmake + echo "Installed C/C++: $(gcc --version | head -1)" + fi +fi + +if echo "$LANGUAGES" | grep -q "ruby"; then + if command -v ruby >/dev/null 2>&1; then + echo "Ruby: $(ruby --version | head -1)" + else + echo "Installing Ruby toolchain..." + apt_update + sudo apt-get install -y -qq ruby-full + echo "Installed Ruby: $(ruby --version | head -1)" + fi +fi + +echo "Language setup complete." +``` + +
+ +
+ +## What's next? + +Now that you added a language, you can [install your own command-line tools](./install-command-line-tools.md). + +## Learn more + +- [Parameters](../../admin/templates/extending-templates/parameters.md) in the Coder documentation +- [Terraform data sources](https://developer.hashicorp.com/terraform/language/data-sources) +- [Terraform types](https://developer.hashicorp.com/terraform/language/expressions/types) for parameter values diff --git a/docs/get-started/customize-your-template/authenticate-to-github.md b/docs/get-started/customize-your-template/authenticate-to-github.md new file mode 100644 index 0000000000000..d783a0a11616c --- /dev/null +++ b/docs/get-started/customize-your-template/authenticate-to-github.md @@ -0,0 +1,605 @@ +# Clone private repositories + +Now that you've finished [Launch your first workspace](../index.md), you can let your workspaces clone private GitHub repositories without a manual login each time. + +The Quickstart template already clones **public** repositories through its **Git Repository (Optional)** parameter with no extra setup. +A **private** repository needs authentication, which is what you add here. +In this guide, you add the `coder_external_auth` data source, connect your workspace to GitHub, and clone a private repository with no manual login. +This guide uses GitHub, and the same external-auth pattern works for other providers. + +> [!NOTE] +> This guide assumes your Quickstart template is open for editing. +> If it's not, you can edit the template from the web by finding the template, selecting the three dots menu, and selecting **Edit files**. +> Refer to [Customize workspace startup](./index.md#open-the-template-for-editing) for more information. + +## What you'll do + +- ✅ Add the `coder_external_auth` data source to the template. +- ✅ Connect your workspace to GitHub with the **Login with GitHub** button. +- ✅ Clone a private repository without a manual login. + +## Data sources for their side effect + +You already met one [data source](https://developer.hashicorp.com/terraform/language/data-sources) in [Add a programming language](./add-a-language.md): `coder_parameter` reads a choice from the workspace owner. + +`coder_external_auth` is a data source you add for its side effect rather than its value. +Its presence tells Coder that a workspace requires an authenticated session with a provider before it starts. +The `id` points at a provider configured on the Coder deployment, and the local Coder server you started in Part 1 already has a `github` provider available. + +> [!NOTE] +> External authentication lets a workspace sign in to an outside service, such as a Git provider, before it starts. +> A default Coder install includes a built-in GitHub app, so `id = "github"` works without extra setup. +> An admin can configure other providers (GitLab, Bitbucket, Azure DevOps, or generic OIDC) or replace the built-in GitHub app. +> To learn more, refer to [External authentication](../../admin/external-auth/index.md). + +## Step 1: Require GitHub authentication + +Add this `coder_external_auth` block to `main.tf`: + +```tf +data "coder_external_auth" "github" { + id = "github" +} +``` + +Then publish a new version of the template: + +
+ +### UI + +In the web editor, add the `coder_external_auth` block to `main.tf`. +Select **Build**, wait for the build to pass, then select **Publish**. + +### CLI + +Add the block to `~/coder-quickstart/main.tf`, then publish a new version: + +```sh +coder templates push -d ~/coder-quickstart -y quickstart +``` + +
+ +> [!WARNING] +> Adding this block makes GitHub authentication required: a workspace on this template can't start until you authenticate, with no option to skip. +> If you'd rather leave it optional, set `optional = true` in the `coder_external_auth` block so users can skip authentication when they create or update a workspace. + +## Step 2: Connect your workspace to GitHub + +Update the workspace you built in [Launch your first workspace](../index.md) to the version you just published. +Because the local Coder server is already connected to GitHub, you don't configure an OAuth app; you only authorize the workspace. + +
+ +### UI + +On your workspace, select **Update**. +The update form shows a **Login with GitHub** button: select it, follow the prompts to authorize Coder, then select **Update and restart**. +The following screenshot shows the **External Authentication** section of the workspace creation screen before connecting GitHub: + +![External Authentication with GitHub before logging in](../../images/screenshots/login-with-github-logged-out.png)_External Authentication with GitHub before logging in_ + +### CLI + +Update the workspace: + +```sh +coder update +``` + +If you aren't authenticated yet, Coder prints a URL to log in with GitHub; open it, authorize Coder, then run the command again. + +
+ +Once you authorize Coder, the workspace starts, and Coder stores and refreshes your GitHub token for later builds. +After authorizing Coder, Coder replaces the **Login with GitHub** button with an **Authenticated** message: + +![External Authentication with GitHub after logging in](../../images/screenshots/login-with-github-logged-in.png)_External Authentication with GitHub after logging in_ + +## Step 3: Clone a private repository + +Now that your workspace is connected to GitHub, point the **Git Repository (Optional)** parameter at a private repository. + +
+ +### UI + +On your workspace, set **Git Repository (Optional)** to your private repository URL in the update form, then select **Update and restart**. + +### CLI + +Update the workspace and re-select its parameters: + +```sh +coder update --always-prompt +``` + +When prompted for **Git Repository (Optional)**, enter your private repository URL, then let the workspace rebuild. + +
+ +On the next build, the workspace clones the private repository with no manual login, because Coder supplies your GitHub token to `git`. + +## What just happened + +You added one data source and connected your workspace to GitHub: + +- `coder_external_auth` declared that the workspace needs an authenticated GitHub session before it starts. +- Selecting **Login with GitHub** authorized Coder, and Coder now supplies a GitHub token to `git` for every build. + +A `coder_parameter` reads an answer from the workspace owner. +A `coder_external_auth` data source reaches outside the template for a prerequisite the deployment provides. +Both are data sources, and neither creates infrastructure. + +## Final code + +
+ +The complete main.tf + +Your `main.tf` after this guide's changes, starting from the Quickstart template: + +```tf +terraform { + required_providers { + coder = { + source = "coder/coder" + } + docker = { + source = "kreuzwerker/docker" + } + external = { + source = "hashicorp/external" + } + } +} + +variable "docker_socket" { + default = "" + description = "(Optional) Docker socket URI" + type = string +} + +provider "docker" { + host = var.docker_socket != "" ? var.docker_socket : null +} + +data "coder_provisioner" "me" {} +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +# --- Parameters --- + +data "coder_parameter" "languages" { + name = "languages" + display_name = "Programming Languages" + description = "Select the languages to pre-install in your workspace" + type = "list(string)" + form_type = "multi-select" + default = jsonencode(["python"]) + mutable = true + icon = "/icon/code.svg" + order = 1 + + option { + name = "Python" + value = "python" + icon = "/icon/python.svg" + } + option { + name = "Node.js" + value = "nodejs" + icon = "/icon/nodejs.svg" + } + option { + name = "Go" + value = "go" + icon = "/icon/go.svg" + } + option { + name = "Rust" + value = "rust" + icon = "/icon/rust.svg" + } + option { + name = "Java" + value = "java" + icon = "/icon/java.svg" + } + option { + name = "C/C++" + value = "cpp" + icon = "/icon/cpp.svg" + } +} + +data "coder_parameter" "ides" { + name = "ides" + display_name = "IDEs & Editors" + description = "Select the development environments for your workspace" + type = "list(string)" + form_type = "multi-select" + default = jsonencode(["code-server"]) + mutable = true + icon = "/icon/code.svg" + order = 2 + + option { + name = "VS Code (Browser)" + value = "code-server" + icon = "/icon/code.svg" + } + option { + name = "Cursor" + value = "cursor" + icon = "/icon/cursor.svg" + } + option { + name = "JetBrains IDEs" + value = "jetbrains" + icon = "/icon/jetbrains.svg" + } + option { + name = "Zed" + value = "zed" + icon = "/icon/zed.svg" + } + option { + name = "Windsurf" + value = "windsurf" + icon = "/icon/windsurf.svg" + } +} + +# Shown only when "JetBrains IDEs" is selected in the IDEs parameter. +# Pre-selects IDEs that match the chosen languages. +data "coder_parameter" "jetbrains_ides" { + count = contains(local.ides, "jetbrains") ? 1 : 0 + name = "jetbrains_ides" + display_name = "JetBrains IDEs" + description = "Select the JetBrains IDEs to install" + type = "list(string)" + form_type = "multi-select" + default = jsonencode(local.jetbrains_ides_from_languages) + mutable = true + icon = "/icon/jetbrains.svg" + order = 3 + + option { + name = "IntelliJ IDEA" + value = "IU" + icon = "/icon/intellij.svg" + } + option { + name = "PyCharm" + value = "PY" + icon = "/icon/pycharm.svg" + } + option { + name = "GoLand" + value = "GO" + icon = "/icon/goland.svg" + } + option { + name = "WebStorm" + value = "WS" + icon = "/icon/webstorm.svg" + } + option { + name = "RustRover" + value = "RR" + icon = "/icon/rustrover.svg" + } + option { + name = "CLion" + value = "CL" + icon = "/icon/clion.svg" + } + option { + name = "PhpStorm" + value = "PS" + icon = "/icon/phpstorm.svg" + } + option { + name = "RubyMine" + value = "RM" + icon = "/icon/rubymine.svg" + } + option { + name = "Rider" + value = "RD" + icon = "/icon/rider.svg" + } +} + +data "coder_parameter" "git_repo" { + name = "git_repo" + display_name = "Git Repository (Optional)" + description = "URL of a Git repository to clone into your workspace (leave empty to skip)" + type = "string" + default = "" + mutable = true + icon = "/icon/git.svg" + order = 4 +} + +# --- External authentication --- + +data "coder_external_auth" "github" { + id = "github" +} + +# --- Locals --- + +locals { + username = data.coder_workspace_owner.me.name + languages = jsondecode(data.coder_parameter.languages.value) + ides = jsondecode(data.coder_parameter.ides.value) + + # Map selected languages to the relevant JetBrains IDE product codes. + # Used as the default for the JetBrains IDE selector parameter. + jetbrains_by_language = { + python = ["PY"] + go = ["GO"] + java = ["IU"] + nodejs = ["WS"] + rust = ["RR"] + cpp = ["CL"] + } + jetbrains_ides_from_languages = distinct(flatten([ + for lang in local.languages : lookup(local.jetbrains_by_language, lang, []) + ])) + + # The actual JetBrains IDEs to install, from the user's selection + # in the conditional JetBrains parameter (or empty if not shown). + jetbrains_selected = contains(local.ides, "jetbrains") ? jsondecode(data.coder_parameter.jetbrains_ides[0].value) : [] +} + +# --- Agent --- + +resource "coder_agent" "main" { + arch = data.coder_provisioner.me.arch + os = "linux" + startup_script = <<-EOT + set -e + if [ ! -f ~/.init_done ]; then + cp -rT /etc/skel ~ + touch ~/.init_done + fi + EOT + + 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}" + } + + 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 + } +} + +# --- Language installation --- +# All languages install in a single script to avoid apt-get lock +# conflicts (coder_script resources run in parallel). + +resource "coder_script" "install_languages" { + count = length(local.languages) > 0 ? 1 : 0 + agent_id = coder_agent.main.id + display_name = "Install Languages" + icon = "/icon/code.svg" + run_on_start = true + start_blocks_login = true + script = templatefile("${path.module}/install-languages.sh.tftpl", { + LANGUAGES = join(",", local.languages) + }) +} + +# --- IDE modules --- + +module "code-server" { + count = data.coder_workspace.me.start_count * (contains(local.ides, "code-server") ? 1 : 0) + source = "registry.coder.com/coder/code-server/coder" + version = "~> 1.0" + agent_id = coder_agent.main.id + order = 1 +} + +module "cursor" { + count = data.coder_workspace.me.start_count * (contains(local.ides, "cursor") ? 1 : 0) + source = "registry.coder.com/coder/cursor/coder" + version = "~> 1.0" + agent_id = coder_agent.main.id + folder = "/home/coder" + order = 3 +} + +# TODO: Re-add the coder/jetbrains module once Coder's dynamic +# parameter system respects module count for parameter visibility. +# The module's internal coder_parameter appears even when count = 0, +# creating a ghost parameter in the workspace creation form. +# module "jetbrains" { +# count = data.coder_workspace.me.start_count * (contains(local.ides, "jetbrains") && length(local.jetbrains_selected) > 0 ? 1 : 0) +# source = "registry.coder.com/coder/jetbrains/coder" +# version = "~> 1.0" +# agent_id = coder_agent.main.id +# folder = "/home/coder" +# default = toset(local.jetbrains_selected) +# } + +module "zed" { + count = data.coder_workspace.me.start_count * (contains(local.ides, "zed") ? 1 : 0) + source = "registry.coder.com/coder/zed/coder" + version = "~> 1.0" + agent_id = coder_agent.main.id + folder = "/home/coder" + order = 5 +} + +module "windsurf" { + count = data.coder_workspace.me.start_count * (contains(local.ides, "windsurf") ? 1 : 0) + source = "registry.coder.com/coder/windsurf/coder" + version = "~> 1.0" + agent_id = coder_agent.main.id + folder = "/home/coder" + order = 6 +} + +# --- Git clone --- + +module "git-clone" { + count = data.coder_workspace.me.start_count * (data.coder_parameter.git_repo.value != "" ? 1 : 0) + source = "registry.coder.com/coder/git-clone/coder" + version = "~> 2.0" + agent_id = coder_agent.main.id + url = data.coder_parameter.git_repo.value +} + +# --- Presets --- + +data "coder_workspace_preset" "web_dev" { + name = "Web Development" + icon = "/icon/nodejs.svg" + parameters = { + languages = jsonencode(["python", "nodejs"]) + ides = jsonencode(["code-server"]) + git_repo = "" + } +} + +data "coder_workspace_preset" "backend_go" { + name = "Backend (Go)" + icon = "/icon/go.svg" + parameters = { + languages = jsonencode(["go"]) + ides = jsonencode(["code-server", "jetbrains"]) + jetbrains_ides = jsonencode(["GO"]) + git_repo = "" + } +} + +data "coder_workspace_preset" "data_science" { + name = "Data Science" + icon = "/icon/python.svg" + parameters = { + languages = jsonencode(["python"]) + ides = jsonencode(["code-server"]) + git_repo = "" + } +} + +data "coder_workspace_preset" "full_stack" { + name = "Full Stack" + icon = "/icon/code.svg" + parameters = { + languages = jsonencode(["python", "nodejs", "go"]) + ides = jsonencode(["code-server", "cursor"]) + git_repo = "" + } +} + +# --- Docker resources --- + +resource "docker_volume" "home_volume" { + name = "coder-${data.coder_workspace.me.id}-home" + lifecycle { + ignore_changes = all + } + labels { + label = "coder.owner" + value = data.coder_workspace_owner.me.name + } + labels { + label = "coder.owner_id" + value = data.coder_workspace_owner.me.id + } + labels { + label = "coder.workspace_id" + value = data.coder_workspace.me.id + } + labels { + label = "coder.workspace_name_at_creation" + value = data.coder_workspace.me.name + } + depends_on = [] +} + +resource "docker_container" "workspace" { + count = data.coder_workspace.me.start_count + image = "codercom/enterprise-base:ubuntu" + name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" + hostname = data.coder_workspace.me.name + entrypoint = [ + "sh", "-c", + replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal"), + ] + env = ["CODER_AGENT_TOKEN=${coder_agent.main.token}"] + host { + host = "host.docker.internal" + ip = "host-gateway" + } + volumes { + container_path = "/home/coder" + volume_name = docker_volume.home_volume.name + read_only = false + } + labels { + label = "coder.owner" + value = data.coder_workspace_owner.me.name + } + labels { + label = "coder.owner_id" + value = data.coder_workspace_owner.me.id + } + labels { + label = "coder.workspace_id" + value = data.coder_workspace.me.id + } + labels { + label = "coder.workspace_name" + value = data.coder_workspace.me.name + } + depends_on = [] +} +``` + +
+ +## What's next? + +You finished the Customize your template series, and your workspaces can now clone private repositories. + +This is the last guide in the series. To keep going, explore more of what Coder offers: + +- [Manage workspaces for your team](../../user-guides/workspace-management.md). +- [Try Coder Agents](../../ai-coder/agents/getting-started.md), the chat interface and API for delegating work to coding agents. + +Or revisit the [Customize your template overview](./index.md) for the full list of guides. + +## Learn more + +- [External authentication](../../admin/external-auth/index.md) in the Coder documentation +- [coder_external_auth data source](https://registry.terraform.io/providers/coder/coder/latest/docs/data-sources/external_auth) in the Terraform Registry +- [Terraform data sources](https://developer.hashicorp.com/terraform/language/data-sources) diff --git a/docs/get-started/customize-your-template/index.md b/docs/get-started/customize-your-template/index.md new file mode 100644 index 0000000000000..398e76c6338a1 --- /dev/null +++ b/docs/get-started/customize-your-template/index.md @@ -0,0 +1,112 @@ +# Customize your template + +In [Launch your first workspace](../index.md), you installed the `coder` CLI, started the Coder server, and built a workspace from the Quickstart template. +That template is a good starting point, but it has gaps: + +- It may not include every language in which you write code. +- It comes with a base toolset, but not the personal command-line tools you install on every machine. +- It clones public repositories, but it can't reach your private repositories without manual authentication. + +This part of the Quickstart closes those gaps one at a time. +Most of these guides edit the same template and introduce one Terraform building block. +One guide instead works inside a running workspace. +Each guide ties its change to something you can observe. + +## What you'll do + +Work through the guides in order, or pick the one that solves your problem: + +- [Add a programming language](./add-a-language.md): expose a new language through a parameter and install it at startup. +- [Install your own command-line tools](./install-command-line-tools.md): install personal command-line tools and make them persist. +- [Clone private repositories](./authenticate-to-github.md): authenticate to GitHub with an external-auth data source. + +Each guide takes about 10 minutes. + +## Templates in brief + +A template is a [Terraform](https://developer.hashicorp.com/terraform) blueprint for a workspace. +It consists of one or more Terraform files (`*.tf`), and it can include templated scripts (`*.tftpl`), a `README`, and any other supporting file. + +Every Coder template declares the `coder` provider inside a `required_providers` block: + +```tf +terraform { + required_providers { + coder = { + source = "coder/coder" + } + } +} +``` + +A template is built from a few kinds of Terraform block: + +- A [provider](https://developer.hashicorp.com/terraform/language/providers) is a plugin that connects Terraform to a platform or service. + The `coder` provider connects Terraform to Coder. +- A [resource](https://developer.hashicorp.com/terraform/language/resources) is any infrastructure object you want to manage with Terraform, such as a workspace agent or a cloud storage bucket. +- A [data source](https://developer.hashicorp.com/terraform/language/data-sources) reads data from a provider without creating or changing infrastructure. + Parameters and external auth are data sources. +- A [module](https://developer.hashicorp.com/terraform/language/modules) is a reusable bundle of resources, data sources, providers, and other Terraform blocks you pull in by reference. + Modules reduce the need to define the same Terraform code multiple times. + You can pull modules from the [Coder Registry](https://registry.coder.com). + +To go deeper on any of these, visit the [Terraform documentation](https://developer.hashicorp.com/terraform). + +## Open the template for editing + +Most guides in this part edit the Quickstart template. +Open it for editing now, either in the Coder web editor or in your local editor with the `coder` CLI. + +
+ +### UI + +1. Log in to Coder and select **Templates**. +2. Find the **Coder Quickstart** template you created and open it. +3. Select the three-dots menu next to **Create Workspace**, then select **Edit files**. + +The template web editor opens: + +![Coder template web editor](../../images/screenshots/template-web-editor.png)_Coder template web editor_ + +### CLI + +Pull the template to your local filesystem so you can edit it in your own editor. +With the server running in your existing terminal, open a second terminal and log in: + +```sh +coder login +``` + +`coder login` opens a browser page that generates a session token. +Copy the token, return to the terminal, and paste it. +Then pull the template: + +```sh +coder templates pull quickstart ~/coder-quickstart +``` + +Open the `~/coder-quickstart` folder in your preferred editor. + +
+ +The template has 3 files: + +- `install-languages.sh.tftpl`, a startup script that installs the selected languages. +- `main.tf`, the Terraform that defines the workspace. +- `README.md`. + +The guides in this part edit `main.tf` and `install-languages.sh.tftpl`. + +Each guide publishes a new template version after you edit these files. +The guides use the CLI command `coder templates push`, which reads from the `~/coder-quickstart` folder you pulled. +If you opened the template in the web editor instead, publish the new version from the editor each time a guide tells you to push. + +## What's next? + +Start with [Add a programming language](./add-a-language.md), then work through the rest of the series in order. + +## Learn more + +- [Extending templates](../../admin/templates/extending-templates/index.md) in the Coder documentation +- [Terraform documentation](https://developer.hashicorp.com/terraform) from HashiCorp diff --git a/docs/get-started/customize-your-template/install-command-line-tools.md b/docs/get-started/customize-your-template/install-command-line-tools.md new file mode 100644 index 0000000000000..132e4bb905296 --- /dev/null +++ b/docs/get-started/customize-your-template/install-command-line-tools.md @@ -0,0 +1,887 @@ +# Install your own command-line tools + +Now that you [launched your first workspace](../index.md), you can add your favorite command-line tools to every workspace. + +The Quickstart template installs system languages through the **Programming Languages** parameter, but it doesn't carry the small command-line tools you may often use, such as [`bat`](https://github.com/sharkdp/bat) or [`ripgrep`](https://github.com/BurntSushi/ripgrep). +You can install those yourself with a package manager like [Homebrew](https://brew.sh/) or [mise](https://mise.jdx.dev/). + +In this guide, you install both Homebrew and mise, install a tool with each, and learn which installs survive a workspace restart and why. +You then change the template so the Homebrew tools persist too, and finish by making your tools install in every new workspace automatically. + +> [!NOTE] +> This guide works inside a running workspace from the Quickstart template. +> Most of it runs in the workspace, but the last two steps edit the template so Homebrew persists and every new workspace ships with your tools. + +## What you'll do + +- ✅ Install command-line tools with [Homebrew](https://brew.sh/) and [mise](https://mise.jdx.dev/) into your workspace. +- ✅ Restart the workspace and see which tools persist. +- ✅ Learn why one persists and the other doesn't. +- ✅ Wire up Homebrew so its tools persist too. +- ✅ Preinstall your tools in every new workspace from the template. + +## What persists in a workspace + +A Quickstart workspace keeps your home directory, `/home/coder`, on a persistent volume. +Everything outside `/home/coder` comes from the workspace image, and Coder rebuilds it from that image every time the workspace starts. + +A tool survives a restart only when both of these are true: + +- The tool installs into `/home/coder`. +- Your shell finds the tool through a file in `/home/coder`, such as `.bashrc`. + +You'll install tools two ways and restart to see this rule decide which ones stay, change the template so Homebrew follows the rule too, then preinstall your tools in every new workspace. + +## Step 1: Install Homebrew and mise + +Open a terminal in your workspace. + +Install [Homebrew](https://brew.sh/) with its setup script: + +```sh +NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +``` + +Homebrew installs to `/home/linuxbrew/.linuxbrew`. +Add it to your shell so the `brew` command is available: + +```sh +echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> ~/.bashrc +eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" +``` + +Confirm Homebrew is available before you continue: + +```sh +brew --version +``` + +Install [mise](https://mise.jdx.dev/) with its setup script: + +```sh +curl -fsSL https://mise.run | sh +``` + +mise installs to `~/.local/bin/mise`, inside your home directory. +Activate it so every new shell loads it: + +```sh +echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc +``` + +Activation only takes effect in shells that start after this change, so open a new terminal (or run `source ~/.bashrc`) before you use mise. +Until you do, `mise doctor` reports that mise isn't activated, which is expected at this point. +For other shells, refer to [Activate mise](https://mise.jdx.dev/getting-started.html#activate-mise). + +Open a new terminal so both the Homebrew and mise changes take effect, then confirm each manager runs: + +```sh +brew --version +mise --version +``` + +> [!NOTE] +> If you manage `~/.bashrc` with [dotfiles](../../user-guides/workspace-dotfiles.md), add the `brew shellenv` and `mise activate` lines to the `.bashrc` in your dotfiles repository instead, so applying your dotfiles doesn't overwrite them. + +## Step 2: Install a tool with each manager + +Install [`ripgrep`](https://github.com/BurntSushi/ripgrep) with Homebrew: + +```sh +brew install ripgrep +``` + +Install [`bat`](https://github.com/sharkdp/bat) with mise: + +```sh +mise use -g bat +``` + +Confirm both tools run: + +```sh +rg --version +bat --version +``` + +Both work. +So far, the two package managers look interchangeable. + +## Step 3: Restart the workspace and compare + +Restart the workspace. +The restart rebuilds the container from the image and keeps only your home directory. + +
+ +### UI + +Open your workspace in the Coder dashboard and select **Restart**. +When it's back, reconnect by reopening the web terminal. + +### CLI + +From a terminal on your own machine, restart the workspace by name, then reconnect when it's back: + +```sh +coder restart +coder ssh +``` + +
+ +When you reconnect, your shell prints an error before you run anything: + +```text +bash: /home/linuxbrew/.linuxbrew/bin/brew: No such file or directory +``` + +That's the first sign something changed. +Your `.bashrc` still tries to load Homebrew, but the restart removed it. +Check each tool to see what survived. + +`bat`, installed with mise, still works: + +```sh +bat --version +``` + +```text +bat 0.26.1 +``` + +`rg`, installed with Homebrew, is gone: + +```sh +rg --version +``` + +```text +bash: rg: command not found +``` + +So is `brew` itself: + +```sh +brew --version +``` + +```text +bash: brew: command not found +``` + +mise installed `bat` under `/home/coder`, which persists, so `bat` survived. +Homebrew installed `ripgrep` to `/home/linuxbrew`, outside `/home/coder`, so the rebuild discarded Homebrew and every formula you installed with it. +The `brew shellenv` line stayed in your `.bashrc` because it lives in `/home/coder`, which is why your shell still tries to load the missing `brew` and prints the error above. + +## Step 4: Make Homebrew survive restarts + +To make Homebrew survive a restart, you'll edit the template and add a persistent volume. +The volume backs `/home/linuxbrew`, the prefix where Homebrew installs, so Homebrew and its formulae stay between restarts. + +> [!NOTE] +> This step assumes your Quickstart template is open for editing. +> If it's not, you can edit the template from the web by finding the template, selecting the three dots menu, and selecting **Edit files**. +> Refer to [Customize workspace startup](./index.md#open-the-template-for-editing) for more information. + +In `main.tf`, add a volume for Homebrew's directory next to the existing `home_volume`: + +```tf +resource "docker_volume" "homebrew_volume" { + name = "coder-${data.coder_workspace.me.id}-homebrew" + lifecycle { + ignore_changes = all + } +} +``` + +Then mount it in the `docker_container "workspace"` resource, alongside the block that mounts `/home/coder`: + +```tf + volumes { + container_path = "/home/linuxbrew" + volume_name = docker_volume.homebrew_volume.name + read_only = false + } +``` + +Publish the change and restart the workspace: + +
+ +### UI + +In the web editor, make the edits above in `main.tf`. +Select **Build**, wait for the build to pass, then select **Publish**. +On your workspace's home tab, select **Update and restart**. + +### CLI + +Make the edits in `~/coder-quickstart/main.tf`, then publish and update by name: + +```sh +coder templates push -d ~/coder-quickstart -y quickstart +coder update +``` + +
+ +The restart gives you a persistent but empty `/home/linuxbrew`. +Your earlier Homebrew install is gone, so install it once more. +This time it lands on the volume: + +```sh +NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +``` + +The `brew shellenv` line is still in your `.bashrc` from Step 1, so open a new terminal to load it, then reinstall `ripgrep`: + +```sh +brew install ripgrep +``` + +Restart the workspace once more and reconnect. +This time the startup error is gone, and both tools report their versions: + +```sh +rg --version +brew --version +``` + +Homebrew now persists because `/home/linuxbrew` lives on its own volume, the same way mise's tools persist because they live in `/home/coder`. + +## Step 5: Install your tools in every new workspace + +Steps 1 through 4 set up your tools in this workspace. +To give everyone who uses the template the same tools, install them from the template so every new workspace starts with them. + +The template already installs languages on every start with the `install_languages` script. +You'll add a second script that installs your command-line tools the same way. + +> [!NOTE] +> This step edits the template. +> If it isn't open for editing, refer to [Customize workspace startup](./index.md#open-the-template-for-editing). + +mise is the lighter choice here. +It installs prebuilt binaries into `/home/coder`, which already persists, so the first start stays quick and later starts reuse the tools. + +In `main.tf`, add a script next to the existing `install_languages` resource: + +```tf +resource "coder_script" "install_tools" { + agent_id = coder_agent.main.id + display_name = "Install Tools" + icon = "/icon/terminal.svg" + run_on_start = true + script = <<-EOT + #!/usr/bin/env bash + set -e + + # Install mise on first start. It lives in /home/coder, so later starts reuse it. + if [ ! -x "$HOME/.local/bin/mise" ]; then + curl -fsSL https://mise.run | sh + fi + export PATH="$HOME/.local/bin:$HOME/.local/share/mise/shims:$PATH" + + # Install the tools for everyone who uses the template, tracking the latest release. + mise use -g ripgrep@latest bat@latest + + # Load mise in new interactive shells so the tools are on PATH. + if ! grep -qs 'mise activate' "$HOME/.bashrc"; then + echo 'eval "$(mise activate bash)"' >> "$HOME/.bashrc" + fi + EOT +} +``` + +`mise use -g ripgrep@latest bat@latest` writes the tools to mise's global config at `~/.config/mise/config.toml` and installs them, so every workspace from the template resolves the same versions. + +Publish the change and apply it to your workspace: + +
+ +### UI + +In the web editor, add the `install_tools` resource to `main.tf`. +Select **Build**, wait for the build to pass, then select **Publish**. +On your workspace's home tab, select **Update and restart**. + +### CLI + +Add the resource in `~/coder-quickstart/main.tf`, then publish and update by name: + +```sh +coder templates push -d ~/coder-quickstart -y quickstart +coder update +``` + +
+ +When the workspace is back, confirm both tools run without installing anything by hand: + +```sh +rg --version +bat --version +``` + +The script runs on every start, so every new workspace from the template now ships with `ripgrep` and `bat`. + +
+ +Use Homebrew and a Brewfile instead + +If you'd rather manage these tools with Homebrew, install them from a [Brewfile](https://docs.brew.sh/Brew-Bundle-and-Brewfile) on every start. +This approach relies on the `/home/linuxbrew` volume you added in Step 4, so Homebrew and its formulae persist between restarts. + +Use this `install_tools` script in place of the mise version: + +```tf +resource "coder_script" "install_tools" { + agent_id = coder_agent.main.id + display_name = "Install Tools" + icon = "/icon/terminal.svg" + run_on_start = true + script = <<-EOT + #!/usr/bin/env bash + set -e + + # Install Homebrew on first start, while the volume is still empty. + if [ ! -x /home/linuxbrew/.linuxbrew/bin/brew ]; then + NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + fi + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + + # Write a Brewfile and install everything it lists. + printf 'brew "ripgrep"\nbrew "bat"\n' > "$HOME/Brewfile" + brew bundle install --file="$HOME/Brewfile" + + # Load Homebrew in new shells so the tools are on PATH. + if ! grep -qs 'brew shellenv' "$HOME/.bashrc"; then + echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> "$HOME/.bashrc" + fi + EOT +} +``` + +Homebrew installs more slowly than mise on a fresh volume because it downloads larger packages, which is why mise is the default here. + +
+ +## What just happened + +The two package managers behaved differently for one reason: where each one installs. + +- mise installs into `~/.local/share/mise`, inside your home directory, and activates from `~/.bashrc`. + Both are in `/home/coder`, so its tools persist with no template change. +- Homebrew installs to `/home/linuxbrew`, outside `/home/coder`, so its tools are discarded on every restart until you mount that path on a persistent volume. + +To keep a tool, choose the approach that matches who needs it: + +- For a tool that's yours alone, install it with mise. + It persists through restarts with no template change. +- To keep your Homebrew tools, mount `/home/linuxbrew` on a persistent volume, as you did in Step 4. + This is a template change, so it affects everyone who uses the template. +- To preinstall a tool in every new workspace, add it to the template's startup script, as you did in Step 5 with mise. + You can also install system packages with `apt-get`, as in [Add a programming language](./add-a-language.md), or bake the tool into the workspace image. + +The rule underneath all of these: a tool persists when it lives in a part of the workspace that persists. +Refer to [Resource persistence](../../admin/templates/extending-templates/resource-persistence.md) for how Coder decides what survives a restart. + +## Final code + +
+ +The complete main.tf + +Your `main.tf` after this guide's changes, starting from the Quickstart template: + +```tf +terraform { + required_providers { + coder = { + source = "coder/coder" + } + docker = { + source = "kreuzwerker/docker" + } + external = { + source = "hashicorp/external" + } + } +} + +variable "docker_socket" { + default = "" + description = "(Optional) Docker socket URI" + type = string +} + +provider "docker" { + host = var.docker_socket != "" ? var.docker_socket : null +} + +data "coder_provisioner" "me" {} +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +# --- Parameters --- + +data "coder_parameter" "languages" { + name = "languages" + display_name = "Programming Languages" + description = "Select the languages to pre-install in your workspace" + type = "list(string)" + form_type = "multi-select" + default = jsonencode(["python"]) + mutable = true + icon = "/icon/code.svg" + order = 1 + + option { + name = "Python" + value = "python" + icon = "/icon/python.svg" + } + option { + name = "Node.js" + value = "nodejs" + icon = "/icon/nodejs.svg" + } + option { + name = "Go" + value = "go" + icon = "/icon/go.svg" + } + option { + name = "Rust" + value = "rust" + icon = "/icon/rust.svg" + } + option { + name = "Java" + value = "java" + icon = "/icon/java.svg" + } + option { + name = "C/C++" + value = "cpp" + icon = "/icon/cpp.svg" + } +} + +data "coder_parameter" "ides" { + name = "ides" + display_name = "IDEs & Editors" + description = "Select the development environments for your workspace" + type = "list(string)" + form_type = "multi-select" + default = jsonencode(["code-server"]) + mutable = true + icon = "/icon/code.svg" + order = 2 + + option { + name = "VS Code (Browser)" + value = "code-server" + icon = "/icon/code.svg" + } + option { + name = "Cursor" + value = "cursor" + icon = "/icon/cursor.svg" + } + option { + name = "JetBrains IDEs" + value = "jetbrains" + icon = "/icon/jetbrains.svg" + } + option { + name = "Zed" + value = "zed" + icon = "/icon/zed.svg" + } + option { + name = "Windsurf" + value = "windsurf" + icon = "/icon/windsurf.svg" + } +} + +# Shown only when "JetBrains IDEs" is selected in the IDEs parameter. +# Pre-selects IDEs that match the chosen languages. +data "coder_parameter" "jetbrains_ides" { + count = contains(local.ides, "jetbrains") ? 1 : 0 + name = "jetbrains_ides" + display_name = "JetBrains IDEs" + description = "Select the JetBrains IDEs to install" + type = "list(string)" + form_type = "multi-select" + default = jsonencode(local.jetbrains_ides_from_languages) + mutable = true + icon = "/icon/jetbrains.svg" + order = 3 + + option { + name = "IntelliJ IDEA" + value = "IU" + icon = "/icon/intellij.svg" + } + option { + name = "PyCharm" + value = "PY" + icon = "/icon/pycharm.svg" + } + option { + name = "GoLand" + value = "GO" + icon = "/icon/goland.svg" + } + option { + name = "WebStorm" + value = "WS" + icon = "/icon/webstorm.svg" + } + option { + name = "RustRover" + value = "RR" + icon = "/icon/rustrover.svg" + } + option { + name = "CLion" + value = "CL" + icon = "/icon/clion.svg" + } + option { + name = "PhpStorm" + value = "PS" + icon = "/icon/phpstorm.svg" + } + option { + name = "RubyMine" + value = "RM" + icon = "/icon/rubymine.svg" + } + option { + name = "Rider" + value = "RD" + icon = "/icon/rider.svg" + } +} + +data "coder_parameter" "git_repo" { + name = "git_repo" + display_name = "Git Repository (Optional)" + description = "URL of a Git repository to clone into your workspace (leave empty to skip)" + type = "string" + default = "" + mutable = true + icon = "/icon/git.svg" + order = 4 +} + +# --- Locals --- + +locals { + username = data.coder_workspace_owner.me.name + languages = jsondecode(data.coder_parameter.languages.value) + ides = jsondecode(data.coder_parameter.ides.value) + + # Map selected languages to the relevant JetBrains IDE product codes. + # Used as the default for the JetBrains IDE selector parameter. + jetbrains_by_language = { + python = ["PY"] + go = ["GO"] + java = ["IU"] + nodejs = ["WS"] + rust = ["RR"] + cpp = ["CL"] + } + jetbrains_ides_from_languages = distinct(flatten([ + for lang in local.languages : lookup(local.jetbrains_by_language, lang, []) + ])) + + # The actual JetBrains IDEs to install, from the user's selection + # in the conditional JetBrains parameter (or empty if not shown). + jetbrains_selected = contains(local.ides, "jetbrains") ? jsondecode(data.coder_parameter.jetbrains_ides[0].value) : [] +} + +# --- Agent --- + +resource "coder_agent" "main" { + arch = data.coder_provisioner.me.arch + os = "linux" + startup_script = <<-EOT + set -e + if [ ! -f ~/.init_done ]; then + cp -rT /etc/skel ~ + touch ~/.init_done + fi + EOT + + 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}" + } + + 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 + } +} + +# --- Language installation --- +# All languages install in a single script to avoid apt-get lock +# conflicts (coder_script resources run in parallel). + +resource "coder_script" "install_languages" { + count = length(local.languages) > 0 ? 1 : 0 + agent_id = coder_agent.main.id + display_name = "Install Languages" + icon = "/icon/code.svg" + run_on_start = true + start_blocks_login = true + script = templatefile("${path.module}/install-languages.sh.tftpl", { + LANGUAGES = join(",", local.languages) + }) +} + +# --- Tool installation --- + +resource "coder_script" "install_tools" { + agent_id = coder_agent.main.id + display_name = "Install Tools" + icon = "/icon/terminal.svg" + run_on_start = true + script = <<-EOT + #!/usr/bin/env bash + set -e + + # Install mise on first start. It lives in /home/coder, so later starts reuse it. + if [ ! -x "$HOME/.local/bin/mise" ]; then + curl -fsSL https://mise.run | sh + fi + export PATH="$HOME/.local/bin:$HOME/.local/share/mise/shims:$PATH" + + # Install the tools for everyone who uses the template, tracking the latest release. + mise use -g ripgrep@latest bat@latest + + # Load mise in new interactive shells so the tools are on PATH. + if ! grep -qs 'mise activate' "$HOME/.bashrc"; then + echo 'eval "$(mise activate bash)"' >> "$HOME/.bashrc" + fi + EOT +} + +# --- IDE modules --- + +module "code-server" { + count = data.coder_workspace.me.start_count * (contains(local.ides, "code-server") ? 1 : 0) + source = "registry.coder.com/coder/code-server/coder" + version = "~> 1.0" + agent_id = coder_agent.main.id + order = 1 +} + +module "cursor" { + count = data.coder_workspace.me.start_count * (contains(local.ides, "cursor") ? 1 : 0) + source = "registry.coder.com/coder/cursor/coder" + version = "~> 1.0" + agent_id = coder_agent.main.id + folder = "/home/coder" + order = 3 +} + +# TODO: Re-add the coder/jetbrains module once Coder's dynamic +# parameter system respects module count for parameter visibility. +# The module's internal coder_parameter appears even when count = 0, +# creating a ghost parameter in the workspace creation form. +# module "jetbrains" { +# count = data.coder_workspace.me.start_count * (contains(local.ides, "jetbrains") && length(local.jetbrains_selected) > 0 ? 1 : 0) +# source = "registry.coder.com/coder/jetbrains/coder" +# version = "~> 1.0" +# agent_id = coder_agent.main.id +# folder = "/home/coder" +# default = toset(local.jetbrains_selected) +# } + +module "zed" { + count = data.coder_workspace.me.start_count * (contains(local.ides, "zed") ? 1 : 0) + source = "registry.coder.com/coder/zed/coder" + version = "~> 1.0" + agent_id = coder_agent.main.id + folder = "/home/coder" + order = 5 +} + +module "windsurf" { + count = data.coder_workspace.me.start_count * (contains(local.ides, "windsurf") ? 1 : 0) + source = "registry.coder.com/coder/windsurf/coder" + version = "~> 1.0" + agent_id = coder_agent.main.id + folder = "/home/coder" + order = 6 +} + +# --- Git clone --- + +module "git-clone" { + count = data.coder_workspace.me.start_count * (data.coder_parameter.git_repo.value != "" ? 1 : 0) + source = "registry.coder.com/coder/git-clone/coder" + version = "~> 2.0" + agent_id = coder_agent.main.id + url = data.coder_parameter.git_repo.value +} + +# --- Presets --- + +data "coder_workspace_preset" "web_dev" { + name = "Web Development" + icon = "/icon/nodejs.svg" + parameters = { + languages = jsonencode(["python", "nodejs"]) + ides = jsonencode(["code-server"]) + git_repo = "" + } +} + +data "coder_workspace_preset" "backend_go" { + name = "Backend (Go)" + icon = "/icon/go.svg" + parameters = { + languages = jsonencode(["go"]) + ides = jsonencode(["code-server", "jetbrains"]) + jetbrains_ides = jsonencode(["GO"]) + git_repo = "" + } +} + +data "coder_workspace_preset" "data_science" { + name = "Data Science" + icon = "/icon/python.svg" + parameters = { + languages = jsonencode(["python"]) + ides = jsonencode(["code-server"]) + git_repo = "" + } +} + +data "coder_workspace_preset" "full_stack" { + name = "Full Stack" + icon = "/icon/code.svg" + parameters = { + languages = jsonencode(["python", "nodejs", "go"]) + ides = jsonencode(["code-server", "cursor"]) + git_repo = "" + } +} + +# --- Docker resources --- + +resource "docker_volume" "home_volume" { + name = "coder-${data.coder_workspace.me.id}-home" + lifecycle { + ignore_changes = all + } + labels { + label = "coder.owner" + value = data.coder_workspace_owner.me.name + } + labels { + label = "coder.owner_id" + value = data.coder_workspace_owner.me.id + } + labels { + label = "coder.workspace_id" + value = data.coder_workspace.me.id + } + labels { + label = "coder.workspace_name_at_creation" + value = data.coder_workspace.me.name + } + depends_on = [] +} + +resource "docker_volume" "homebrew_volume" { + name = "coder-${data.coder_workspace.me.id}-homebrew" + lifecycle { + ignore_changes = all + } +} + +resource "docker_container" "workspace" { + count = data.coder_workspace.me.start_count + image = "codercom/enterprise-base:ubuntu" + name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" + hostname = data.coder_workspace.me.name + entrypoint = [ + "sh", "-c", + replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal"), + ] + env = ["CODER_AGENT_TOKEN=${coder_agent.main.token}"] + host { + host = "host.docker.internal" + ip = "host-gateway" + } + volumes { + container_path = "/home/coder" + volume_name = docker_volume.home_volume.name + read_only = false + } + volumes { + container_path = "/home/linuxbrew" + volume_name = docker_volume.homebrew_volume.name + read_only = false + } + labels { + label = "coder.owner" + value = data.coder_workspace_owner.me.name + } + labels { + label = "coder.owner_id" + value = data.coder_workspace_owner.me.id + } + labels { + label = "coder.workspace_id" + value = data.coder_workspace.me.id + } + labels { + label = "coder.workspace_name" + value = data.coder_workspace.me.name + } + depends_on = [] +} +``` + +
+ +## What's next? + +Now that you can install your own tools, [clone private repositories](./authenticate-to-github.md) so your workspaces can reach your private GitHub code. + +## Learn more + +- [Homebrew documentation](https://brew.sh/) for the package manager +- [mise documentation](https://mise.jdx.dev/) for the version manager +- [Resource persistence](../../admin/templates/extending-templates/resource-persistence.md) in the Coder documentation +- [Dotfiles](../../user-guides/workspace-dotfiles.md) in the Coder documentation diff --git a/docs/get-started/index.md b/docs/get-started/index.md index 5eebbf589dee2..48fb949c041f2 100644 --- a/docs/get-started/index.md +++ b/docs/get-started/index.md @@ -231,7 +231,7 @@ Templates define what's in your development environment. The following is a basi 4. Select **Save**. - ![Create template](../images/screenshots/create-template.png) + ![Create template](../images/screenshots/create-quickstart-template.png) **What just happened?** You defined a template, a reusable blueprint for dev environments, in your Coder deployment. @@ -264,6 +264,8 @@ Now it's time to launch a workspace. **Note:** If you use any of the JetBrains IDEs as your preferred IDE (such as PyCharm, GoLand, or RustRover), select **JetBrains IDEs** as the value. A new parameter will appear, with which you can choose your preferred JetBrains IDE. + ![Workspace creation screen](../images/screenshots/create-workspace.png)_Workspace creation screen_ + 4. Launch your workspace by selecting **Create workspace**. After a short wait (10-15 seconds on most modern computers), Coder will start your new workspace: @@ -305,7 +307,7 @@ workspace, you can clone it manually if you want: 4. You are now using VS Code in your Coder environment! -## Success! You're coding in Coder +## What's next? You now have: @@ -314,10 +316,9 @@ You now have: - A workspace running that environment. - IDE access to code remotely. -### What's next? +Now that you have your own workspace running, you can [customize your template](./customize-your-template/index.md) to fit your needs. -Now that you have your own workspace running, you can start exploring more -advanced capabilities that Coder offers. +## Learn more - [Try Coder Agents](../ai-coder/agents/getting-started.md), the chat interface and API for delegating development work to coding agents in your @@ -346,63 +347,63 @@ In that case, point Coder at the socket with the `DOCKER_HOST` environment varia
-#### macOS +#### Linux -1. If Colima is not installed, install it with [Homebrew](https://brew.sh): +1. Install Docker, if you haven't already: ```sh - brew install colima docker + curl -sSL https://get.docker.com | sh ``` -1. Start Colima to launch the Docker daemon: +1. Start the Docker daemon: ```sh - colima start + sudo systemctl start docker ``` -1. Verify that the daemon is reachable: +1. Assign your user to the `docker` group so Coder can access the daemon + without root: ```sh - docker ps + sudo usermod -aG docker $USER + newgrp docker ``` -1. If `docker ps` works but Coder still cannot connect, point `DOCKER_HOST` at the Colima socket, then restart the Coder server: +1. Confirm the group membership: - ```sh - export DOCKER_HOST="unix://${HOME}/.colima/default/docker.sock" + ```console + $ groups + docker sudo users ``` - To persist the setting across restarts, add that `export` line to your shell's startup file, such as `~/.zshrc`, `~/.bashrc`, or `~/.config/fish/config.fish`. - -#### Linux +#### macOS -1. Install Docker, if you haven't already: +1. If Colima is not installed, install it with [Homebrew](https://brew.sh): ```sh - curl -sSL https://get.docker.com | sh + brew install colima docker ``` -1. Start the Docker daemon: +1. Start Colima to launch the Docker daemon: ```sh - sudo systemctl start docker + colima start ``` -1. Assign your user to the `docker` group so Coder can access the daemon - without root: +1. Verify that the daemon is reachable: ```sh - sudo usermod -aG docker $USER - newgrp docker + docker ps ``` -1. Confirm the group membership: +1. If `docker ps` works but Coder still cannot connect, point `DOCKER_HOST` at the Colima socket, then restart the Coder server: - ```console - $ groups - docker sudo users + ```sh + export DOCKER_HOST="unix://${HOME}/.colima/default/docker.sock" ``` + To persist the setting across restarts, add that `export` line to your shell's startup file, such as `~/.zshrc`, `~/.bashrc`, or `~/.config/fish/config.fish`. + #### Windows 1. If Podman Desktop is not installed, @@ -422,6 +423,8 @@ error: configure http(s): listen tcp 127.0.0.1:3000: bind: address already in us Another process is already listening on port 3000. Identify and stop it, then start the server again. +
+ #### Linux 1. Stop the process: @@ -481,3 +484,5 @@ then start the server again. ```sh coder server ``` + +
diff --git a/docs/images/screenshots/create-quickstart-template.png b/docs/images/screenshots/create-quickstart-template.png new file mode 100644 index 0000000000000..ced09ecd599d8 Binary files /dev/null and b/docs/images/screenshots/create-quickstart-template.png differ diff --git a/docs/images/screenshots/create-workspace.png b/docs/images/screenshots/create-workspace.png new file mode 100644 index 0000000000000..bff860d72c226 Binary files /dev/null and b/docs/images/screenshots/create-workspace.png differ diff --git a/docs/images/screenshots/login-with-github-logged-in.png b/docs/images/screenshots/login-with-github-logged-in.png new file mode 100644 index 0000000000000..f5a298a84f678 Binary files /dev/null and b/docs/images/screenshots/login-with-github-logged-in.png differ diff --git a/docs/images/screenshots/login-with-github-logged-out.png b/docs/images/screenshots/login-with-github-logged-out.png new file mode 100644 index 0000000000000..308be6a026f23 Binary files /dev/null and b/docs/images/screenshots/login-with-github-logged-out.png differ diff --git a/docs/images/screenshots/quickstart-default-presets.png b/docs/images/screenshots/quickstart-default-presets.png new file mode 100644 index 0000000000000..7b853de5a3b3c Binary files /dev/null and b/docs/images/screenshots/quickstart-default-presets.png differ diff --git a/docs/images/screenshots/quickstart-language-param-ruby.png b/docs/images/screenshots/quickstart-language-param-ruby.png new file mode 100644 index 0000000000000..5542150b5da78 Binary files /dev/null and b/docs/images/screenshots/quickstart-language-param-ruby.png differ diff --git a/docs/images/screenshots/quickstart-language-param.png b/docs/images/screenshots/quickstart-language-param.png new file mode 100644 index 0000000000000..94bf9f043f1d4 Binary files /dev/null and b/docs/images/screenshots/quickstart-language-param.png differ diff --git a/docs/images/screenshots/quickstart-ruby-preset.png b/docs/images/screenshots/quickstart-ruby-preset.png new file mode 100644 index 0000000000000..0b8e8682bda1c Binary files /dev/null and b/docs/images/screenshots/quickstart-ruby-preset.png differ diff --git a/docs/images/screenshots/quickstart-updated-presets.png b/docs/images/screenshots/quickstart-updated-presets.png new file mode 100644 index 0000000000000..921221a0e8f29 Binary files /dev/null and b/docs/images/screenshots/quickstart-updated-presets.png differ diff --git a/docs/images/screenshots/template-web-editor.png b/docs/images/screenshots/template-web-editor.png new file mode 100644 index 0000000000000..20677017fa948 Binary files /dev/null and b/docs/images/screenshots/template-web-editor.png differ diff --git a/docs/manifest.json b/docs/manifest.json index 29655770553b7..36b8f2c5acc32 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -86,7 +86,31 @@ "title": "Get started", "description": "Install Coder and launch your first workspace", "path": "./get-started/index.md", - "icon_path": "./images/icons/rocket.svg" + "icon_path": "./images/icons/rocket.svg", + "children": [ + { + "title": "Customize your template", + "description": "Add a language, install command-line tools, and authenticate to GitHub", + "path": "./get-started/customize-your-template/index.md", + "children": [ + { + "title": "Add a programming language", + "description": "Expose a new language through a parameter and install it at startup", + "path": "./get-started/customize-your-template/add-a-language.md" + }, + { + "title": "Install your own command-line tools", + "description": "Install personal command-line tools and make them persist", + "path": "./get-started/customize-your-template/install-command-line-tools.md" + }, + { + "title": "Clone private repositories", + "description": "Authenticate to GitHub with an external-auth data source", + "path": "./get-started/customize-your-template/authenticate-to-github.md" + } + ] + } + ] }, { "title": "Install",