Normalizes non-standard code-fence language tags across `docs/**` so a strict highlighter (Shiki, used by Fumadocs) won't fail the build on an unrecognized language, and unifies redundant synonym tags onto one canonical form per language. The current renderer (Speed-Highlight) detects the language from the code content, not the fence label, so this drift wasn't visible until now. ## Changes - `hcl` -> `tf` (199 fences, including indented ones nested in numbered/bulleted lists). Shiki ships `hcl` and `terraform` as two distinct grammars (not aliases); every `hcl`-tagged fence in `docs/**` is actually Terraform resource/data/provider syntax, so the more specific `terraform` grammar is correct for all of them. `tf` is Shiki's own alias for that grammar, and it's also what GitHub's own markdown renderer resolves to the same HCL/Terraform highlighting. - `pwsh`/`powershell` -> `ps1`. Both `ps` and `ps1` are registered PowerShell aliases in Shiki, but on GitHub's renderer only `.ps1` is a registered file extension (`.ps` isn't), so `ps1` renders identically to `powershell` there today while bare `ps` would silently lose highlighting. - `env` -> `dotenv` (a dedicated Shiki grammar for `KEY=VALUE` files) - `text`/`output`/`none`/`url` -> `txt`. Same built-in plain-text fallback either way, just shorter. - `Dockerfile` -> `dockerfile` (lowercase) - `bash`/`shell` -> `sh` (732 fences). Shiki and GitHub both alias all three to a single shell grammar; this was already the style guide's stated preference, just not enforced across the existing corpus until now. - `markdown` -> `md` (4 fences). Alias of the same grammar in both Shiki and GitHub. - `jsonc` -> `json` (1 fence). The block has no comments or trailing commas, so it doesn't need the comments-capable grammar. - `ts` -> `tsx` (2 fences, `docs/about/contributing/frontend.md`). Verified the actual content tokenizes identically under both grammars, and a sibling block in the same file already needs `tsx` for real JSX, so unifying to one tag is safe for this file. Documented a caveat: `tsx` mis-tokenizes the legacy angle-bracket type-assertion syntax (`<Type>value`), which is invalid in real `.tsx` files anyway, so use `value as Type` instead. - `yml` -> `yaml` (1 fence) - Updated `docs/.style/style-guide/formatting.md` to document all canonical tags `promql` (2 fences) and `caddyfile` (2 fences) are left as-is. Shiki doesn't bundle a grammar for either, so they need a custom grammar registration when the site adopts Shiki, rather than degrading to `txt`. Tracked as follow-up work under DOCS-118 and [DOCS-544](https://linear.app/codercom/issue/DOCS-544/vendor-a-local-promql-grammar-for-shiki-syntax-highlighting) (promql). Does not touch `offlinedocs/`. Linear: [DOCS-476](https://linear.app/codercom/issue/DOCS-476/normalize-docs-code-fence-languages-de-risk-shikifumadocs) <details> <summary>How the fence tags were verified</summary> Each tag was tested against a real `shiki@latest` highlighter instance (`codeToHtml`/`codeToTokens`) and cross-checked against GitHub's `@wooorm/starry-night` grammar sources (the renderer that actually displays these `.md` files today, in repo browsing and PR diffs), since that's what determines whether brevity is safe before Shiki adoption: ```text FAIL env -- Language `env` is not included in this bundle. FAIL Dockerfile -- Language `Dockerfile` is not included in this bundle. FAIL promql -- Language `promql` is not included in this bundle. FAIL caddyfile -- Language `caddyfile` is not included in this bundle. FAIL pwsh -- Language `pwsh` is not included in this bundle. FAIL output -- Language `output` is not included in this bundle. ``` `hcl` doesn't error in Shiki, since it's a real grammar, but that's exactly the trap: it was silently rendering every fence with the generic HCL grammar instead of the Terraform-specific one. Every `hcl`-tagged fence in `docs/**` was manually checked against `origin/main` and is genuinely Terraform content. For `ts`/`tsx`, tokenizing the actual doc content confirmed identical output under both grammars; a synthetic test with the legacy angle-bracket cast syntax confirmed `tsx` degrades on that specific construct, which the style guide now calls out. The first normalization pass only matched fence tags at column 0 (`^```tag$`), missing tags indented inside numbered/bulleted lists. A follow-up pass caught the remaining occurrences at any indentation level. </details> --- *This PR description and the underlying changes were prepared with Coder Agents assistance.*
13 KiB
Write a template from scratch
Tip
If you want to create a template without writing Terraform, use the template builder instead. The builder guides you through selecting base infrastructure and adding modules through a visual interface.
A template is a common configuration that you use to deploy workspaces.
This tutorial teaches you how to create a template that provisions a workspace as a Docker container with Ubuntu.
Before you start
You'll need a computer or cloud computing instance with both Docker and Coder installed on it.
What's in a template
The main part of a Coder template is a Terraform tf
file. A Coder template often has other files to configure the other resources
that the template needs. In this tour you'll also create a Dockerfile.
Coder can provision all Terraform modules, resources, and properties. The Coder
server essentially runs a terraform apply every time a workspace is created,
started, or stopped.
Tip
Haven't written Terraform before? Check out HashiCorp's Getting Started Guides.
Here's a simplified diagram that shows the main parts of the template we'll create:
1. Create template files
On your local computer, create a directory for your template and create the
Dockerfile. You will upload the files to your Coder instance later.
mkdir -p template-tour/build && cd $_
Enter content into a Dockerfile that starts with the
official Ubuntu image. In your editor, enter
and save the following text in Dockerfile then exit the editor:
FROM ubuntu
RUN apt-get update \
&& apt-get install -y \
sudo \
curl \
&& rm -rf /var/lib/apt/lists/*
ARG USER=coder
RUN useradd --groups sudo --no-create-home --shell /bin/bash ${USER} \
&& echo "${USER} ALL=(ALL) NOPASSWD:ALL" >/etc/sudoers.d/${USER} \
&& chmod 0440 /etc/sudoers.d/${USER}
USER ${USER}
WORKDIR /home/${USER}
Dockerfile adds a few things to the parent ubuntu image, which your template
needs later:
- It installs the
sudoandcurlpackages. - It adds a
coderuser, including a home directory.
2. Set up template providers
Edit the Terraform main.tf file to provision the workspace's resources.
Start by setting up the providers. At a minimum, we need the coder provider.
For this template, we also need the docker provider:
terraform {
required_providers {
coder = {
source = "coder/coder"
}
docker = {
source = "kreuzwerker/docker"
}
}
}
locals {
username = data.coder_workspace_owner.me.name
}
data "coder_provisioner" "me" {
}
provider "docker" {
}
provider "coder" {
}
data "coder_workspace" "me" {
}
data "coder_workspace_owner" "me" {
}
Notice that the provider blocks for coder and docker are empty. In a more
practical template, you would add arguments to these blocks to configure the
providers, if needed.
The
coder_workspace
data source provides details about the state of a workspace, such as its name,
owner, and so on. The data source also lets us know when a workspace is being
started or stopped. We'll use this information in later steps to:
- Set some environment variables based on the workspace owner.
- Manage ephemeral and persistent storage.
3. coder_agent
All templates need to create and run a
Coder agent.
This lets developers connect to their workspaces. The coder_agent resource
runs inside the compute aspect of your workspace, typically a VM or container.
In our case, it will run in Docker.
You do not need to have any open ports on the compute aspect, but the agent
needs curl access to the Coder server.
Add this snippet after the last closing } in main.tf to create the agent:
resource "coder_agent" "main" {
arch = data.coder_provisioner.me.arch
os = "linux"
startup_script = <<-EOT
set -e
# install and start code-server
curl -fsSL https://code-server.dev/install.sh | sh -s -- --method=standalone --prefix=/tmp/code-server
/tmp/code-server/bin/code-server --auth none --port 13337 >/tmp/code-server.log 2>&1 &
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
}
}
Because Docker is running locally in the Coder server, there is no need to
authenticate coder_agent. But if your coder_agent is running on a remote
host, your template will need
authentication credentials.
This template's agent also runs a startup script, sets environment variables, and provides metadata.
-
-
Installs code-server, a browser-based VS Code app that runs in the workspace.
We'll give users access to code-server through
coder_applater.
-
-
-
Sets environments variables for the workspace.
We use the data source from
coder_workspaceto set the environment variables based on the workspace's owner. This way, the owner can make git commits immediately without any manual configuration.
-
-
metadatablocks-
Your template can use metadata to show information to the workspace owner Coder displays this metadata in the Coder dashboard.
Our template has
metadatablocks for CPU and RAM usage.
-
4. coder_app
A
coder_app
resource lets a developer use an app from the workspace's Coder dashboard.
This is commonly used for web IDEs such as code-server, RStudio, and JupyterLab.
We installed code-server in the startup_script argument. To add code-server to
the workspace, make it available in the workspace with a coder_app resource.
See web IDEs for more examples:
resource "coder_app" "code-server" {
agent_id = coder_agent.main.id
slug = "code-server"
display_name = "code-server"
url = "http://localhost:13337/?folder=/home/${local.username}"
icon = "/icon/code.svg"
subdomain = false
share = "owner"
healthcheck {
url = "http://localhost:13337/healthz"
interval = 5
threshold = 6
}
}
You can also use a coder_app resource to link to external apps, such as links
to wikis or cloud consoles:
resource "coder_app" "coder-server-doc" {
agent_id = coder_agent.main.id
icon = "/emojis/1f4dd.png"
slug = "getting-started"
url = "https://coder.com/docs/code-server"
external = true
}
5. Persistent and ephemeral resources
Managing the lifecycle of template resources is important. We want to make sure that workspaces use computing, storage, and other services efficiently.
We want our workspace's home directory to persist after the workspace is stopped so that a developer can continue their work when they start the workspace again.
We do this in 2 parts:
- Our
docker_volumeresource uses thelifecycleblock with theignore_changes = allargument to prevent accidental deletions. - To prevent Terraform from destroying persistent Docker volumes in case of a
workspace name change, we use an immutable parameter, like
data.coder_workspace.me.id.
Later, we use the Terraform count meta-argument to make sure that our Docker container is ephemeral.
resource "docker_volume" "home_volume" {
name = "coder-${data.coder_workspace.me.id}-home"
# Protect the volume from being deleted due to changes in attributes.
lifecycle {
ignore_changes = all
}
}
For details, see Resource persistence.
6. Set up the Docker container
To set up our Docker container, our template has a docker_image resource that
uses build/Dockerfile, which we created earlier:
resource "docker_image" "main" {
name = "coder-${data.coder_workspace.me.id}"
build {
context = "./build"
build_args = {
USER = local.username
}
}
triggers = {
dir_sha1 = sha1(join("", [for f in fileset(path.module, "build/*") : filesha1(f)]))
}
}
Our docker_container resource uses coder_workspace start_count to start
and stop the Docker container:
resource "docker_container" "workspace" {
count = data.coder_workspace.me.start_count
image = docker_image.main.name
# Uses lower() to avoid Docker restriction on container names.
name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
# Hostname makes the shell more user friendly: coder@my-workspace:~$
hostname = data.coder_workspace.me.name
# Use the docker gateway if the access URL is 127.0.0.1
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/${local.username}"
volume_name = docker_volume.home_volume.name
read_only = false
}
}
7. Create the template in Coder
Save main.tf and exit the editor.
Now that we've created the files for our template, we can add them to our Coder deployment.
We can do this with the Coder CLI or the Coder dashboard. In this example, we'll use the Coder CLI.
-
Log in to your Coder deployment from the CLI. This is where you need the URL for your deployment:
$ coder login https://coder.example.com Attempting to authenticate with config URL: 'https://coder.example.com' Open the following in your browser: https://coder.example.com/cli-auth > Paste your token here: -
In your web browser, enter your credentials:
-
Copy the session token to the clipboard:
-
Paste it into the CLI:
> Welcome to Coder, marc! You're authenticated. $
Add the template files to Coder
Add your template files to your Coder deployment. You can upload the template through the CLI, or through the Coder dashboard:
CLI
-
Run
coder templates pushfrom the directory with your template files:$ pwd /home/docs/template-tour $ coder templates push > Upload "."? (yes/no) yes -
The Coder CLI tool gives progress information then prompts you to confirm:
> Confirm create? (yes/no) yes The template-tour template has been created! Developers can provision a workspace with this template using: coder create --template="template-tour" [workspace name] -
In your web browser, log in to your Coder dashboard, select Templates.
-
Once the upload completes, select Templates from the top to deploy it to a new workspace.
Dashboard
-
Create a
.zipof the template files.-
On Mac or Windows, highlight the files and then right click. A "compress" option is available through the right-click context menu.
-
To zip the files through the command line:
zip templates.zip Dockerfile main.tf
-
-
Select Templates from the top of the Coder dashboard, then Create Template.
-
Select Upload template:
-
Drag the
.zipfile into the Upload template section and fill out the details, then select Create template. -
Once the upload completes, select Templates from the top to deploy it to a new workspace.






