Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
16a3e8c
feat(coderd): add Coder Quickstart base to the template builder
nickvigilante Jul 14, 2026
7e2dace
test(coderd): assert quickstart-first ordering in TemplateBuilderBases
nickvigilante Jul 14, 2026
320fd44
fix(coderd): make template builder base ordering a total order
nickvigilante Jul 14, 2026
d93480c
test(coderd): cover quickstart in the template builder bases spec table
nickvigilante Jul 15, 2026
462f314
feat(coderd): trim quickstart base and group it next to Docker
nickvigilante Jul 16, 2026
c9916f6
feat(coderd): guard against base/module name collisions in the builder
nickvigilante Jul 21, 2026
e880a63
test(coderd): enforce base included_modules match rendered blocks
nickvigilante Aug 4, 2026
88f9f58
refactor(coderd): modernize base ordering, dedupe guard comments
nickvigilante Aug 4, 2026
0254b2d
fix(coderd): correct quickstart install docs and language dispatch
nickvigilante Aug 4, 2026
6f4b53d
feat(coderd): address R5 review nits on the quickstart base
nickvigilante Aug 4, 2026
d86e090
feat(coderd): address R6 review nits on the quickstart base
nickvigilante Aug 24, 2026
3d2b759
feat(coderd): address R7 review nits on the quickstart base
nickvigilante Aug 24, 2026
b5a0cab
refactor(coderd): align preset label check and tidy HCL helper docs
nickvigilante Aug 24, 2026
586c53b
refactor(coderd): derive base modules from render, move base ordering…
nickvigilante Aug 26, 2026
73c2bfe
test(site): cover sortByPriority template-builder helper
nickvigilante Aug 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions coderd/templatebuilder/bases.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,60 @@ func BaseVariables(exampleID string) []ModuleVariable {
return bases[exampleID].Manifest.Variables
}

// baseIncludedModules derives, for each base, the catalog module IDs the base
// declares in its own rendered Terraform (e.g. "git-clone"). It renders each
// base's main.tf.tmpl once and extracts the module block labels, caching the
// result. Compose treats these names as occupied so a wizard-selected module
// cannot collide with one the base already renders.
//
// Caveat: derivation uses DefaultBaseRenderContext, so a `module` block gated
// behind a base variable would not be detected. No base declares variables
// today, so this is not currently reachable; revisit if that changes.
var baseIncludedModules = sync.OnceValues(func() (map[string][]string, error) {
bases, err := loadBases()
if err != nil {
return nil, err
}
catalog, err := loadCatalogMap()
if err != nil {
return nil, err
}
out := make(map[string][]string, len(bases))
for id := range bases {
// Only bases that render a main.tf.tmpl can declare modules.
if _, ok := bases[id].Templates["main.tf.tmpl"]; !ok {
continue
}
mainTF, err := RenderBaseTemplate(id, "main.tf.tmpl", DefaultBaseRenderContext(id))
if err != nil {
return nil, xerrors.Errorf("render base %q: %w", id, err)
}
// Keep only catalog-named module blocks: those are the module IDs a
// wizard-selected module could collide with. Non-catalog blocks a base
// renders (e.g. the azure_region helper) are never offered by the
// wizard, so they cannot collide and are not reserved.
var included []string
for _, name := range ExtractModuleNames(mainTF) {
if _, ok := catalog[name]; ok {
included = append(included, name)
}
}
out[id] = included
}
return out, nil
})

// BaseIncludedModules returns the catalog module IDs the given base declares in
// its own rendered Terraform. Returns nil if the base is unknown or declares
// none.
func BaseIncludedModules(exampleID string) []string {
all, err := baseIncludedModules()
if err != nil {
return nil
}
return all[exampleID]
}

// BaseTemplateFS returns a filesystem rooted at the given base template
// directory within the embedded bases catalog. Returns an error if
// exampleID is not a known base template.
Expand Down
64 changes: 64 additions & 0 deletions coderd/templatebuilder/bases/quickstart/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
display_name: Coder Quickstart
description: Get started with Coder by picking your languages and a repo
icon: ../../../site/static/icon/coder.svg
maintainer_github: coder
verified: true
tags: [docker, quickstart]
---

# Coder Quickstart

Get up and running with Coder in minutes. Choose your programming languages, optionally clone a Git repository, and start coding.

## How It Works

When you create a workspace from this template, you select:

1. **Languages** to pre-install (Python, Node.js, Go, Rust, Java, C/C++)
2. **A Git repository** to clone (optional)

Coder provisions a workspace with your selections and you can start developing immediately.

<!-- prerequisites:start -->

## Prerequisites

The host running Coder must have a Docker daemon accessible to the `coder` user:

```sh
# Add coder user to Docker group
sudo adduser coder docker

# Restart Coder server
sudo systemctl restart coder

# Verify access
sudo -u coder docker ps
```

<!-- prerequisites:end -->

## Architecture

This template provisions:

- **Docker container** (ephemeral) running Ubuntu with the Coder agent
- **Docker volume** (persistent) mounted at `/home/coder`

Files in your home directory (`/home/coder`) persist across workspace restarts. The language install script runs on every start and blocks login until it finishes. Most toolchains install into the ephemeral workspace container rather than your home directory, so they are reinstalled from the network on each start; the exception is Rust, whose toolchain lives under `~/.cargo` in your home directory and is detected and reused.

## Presets

Select a preset to auto-fill languages for common workflows:

| Preset | Languages |
| ------------------- | ------------------- |
| **Web Development** | Python, Node.js |
| **Backend (Go)** | Go |
| **Data Science** | Python |
| **Full Stack** | Python, Node.js, Go |

## Editors

VS Code Desktop is available on every workspace by default (Coder enables the VS Code Desktop display app automatically). To add more editors (VS Code in the browser, Cursor, JetBrains, Zed, Windsurf) or other tools, add them as modules in the next step of the template builder.
6 changes: 6 additions & 0 deletions coderd/templatebuilder/bases/quickstart/base.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"id": "quickstart",
"display_name": "Coder Quickstart",
"os": "linux",
"default_context": {}
}
98 changes: 98 additions & 0 deletions coderd/templatebuilder/bases/quickstart/install-languages.sh.tftpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/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
}

# has_language reports whether NAME is one of the selected languages. It matches
# whole comma-separated entries, so a value can never partially match another
# (guards against a future language whose name contains an existing one).
has_language() {
case ",$LANGUAGES," in
*",$1,"*) return 0 ;;
*) return 1 ;;
esac
}

if has_language 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 has_language 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 has_language 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 has_language 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 has_language 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 has_language 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

echo "Language setup complete."
Loading
Loading