Files
coder/docs/install/airgap.md
T
Nick Vigilante c84aa564ba docs: normalize code-fence languages for Shiki compatibility (#27161)
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.*
2026-07-15 14:07:09 -04:00

16 KiB

Air-gapped Deployments

All Coder features are supported in air-gapped / behind firewalls / disconnected / offline. This is a general comparison. Keep reading for a full tutorial running Coder air-gapped with Kubernetes or Docker.

Public deployments Air-gapped deployments
Terraform binary By default, Coder downloads Terraform binary from releases.hashicorp.com Terraform binary must be included in PATH for the VM or container image. Supported versions
Terraform registry Coder templates will attempt to download providers from registry.terraform.io or custom source addresses specified in each template Custom source addresses can be specified in each Coder template, or a custom registry/mirror can be used. More details below
STUN By default, Coder uses Google's public STUN server for direct workspace connections STUN can be safely disabled users can still connect via relayed connections. Alternatively, you can set a custom DERP server
DERP By default, Coder's built-in DERP relay can be used, or Tailscale's public relays. By default, Coder's built-in DERP relay can be used, or custom relays.
PostgreSQL If no PostgreSQL connection URL is specified, Coder will download Postgres from repo1.maven.org An external database is required, you must specify a PostgreSQL connection URL
Telemetry Telemetry is on by default, and can be disabled Telemetry can be disabled
Update check By default, Coder checks for updates from GitHub releases Update checks can be disabled
License validation License keys are validated locally using cryptographic signatures. No outbound connection to Coder is required No changes needed. See offline license validation
AI Governance Usage Count By default, deployments with the AI Governance Add On report usage data Contact us to request a license with usage reporting off.

Air-gapped container images

The following instructions walk you through how to build a custom Coder server image for Docker or Kubernetes

First, build and push a container image extending our official image with the following:

Note

Coder includes the latest supported version of Terraform in the official Docker images. If you need to bundle a different version of terraform, you can do so by customizing the image.

Here's an example Dockerfile:

FROM ghcr.io/coder/coder:latest

USER root

RUN apk add curl unzip

# Create directory for the Terraform CLI (and assets)
RUN mkdir -p /opt/terraform

# Terraform is already included in the official Coder image.
# See ../../scripts/Dockerfile.base#L15
# If you need to install a different version of Terraform, you can do so here.
# The below step is optional if you wish to keep the existing version.
# See ../../provisioner/terraform/install.go#L23-L24
# for supported Terraform versions.
ARG TERRAFORM_VERSION=1.11.0
RUN apk update && \
    curl -LOs https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip \
    && unzip -o terraform_${TERRAFORM_VERSION}_linux_amd64.zip \
    && mv terraform /opt/terraform \
    && rm terraform_${TERRAFORM_VERSION}_linux_amd64.zip
ENV PATH=/opt/terraform:${PATH}

# Additionally, a Terraform mirror needs to be configured
# to download the Terraform providers used in Coder templates.
# There are two options:

# Option 1) Use a filesystem mirror.
#  We can seed this at build-time or by mounting a volume to
#  /opt/terraform/plugins in the container.
#  https://developer.hashicorp.com/terraform/cli/config/config-file#filesystem_mirror
#  Be sure to add all the providers you use in your templates to /opt/terraform/plugins

RUN mkdir -p /home/coder/.terraform.d/plugins/registry.terraform.io
ADD filesystem-mirror-example.tfrc /home/coder/.terraformrc

# Optionally, we can "seed" the filesystem mirror with common providers.
# Comment out lines 40-49 if you plan on only using a volume or network mirror:
WORKDIR /home/coder/.terraform.d/plugins/registry.terraform.io
ARG CODER_PROVIDER_VERSION=2.2.0
RUN echo "Adding coder/coder v${CODER_PROVIDER_VERSION}" \
    && mkdir -p coder/coder && cd coder/coder \
    && curl -LOs https://github.com/coder/terraform-provider-coder/releases/download/v${CODER_PROVIDER_VERSION}/terraform-provider-coder_${CODER_PROVIDER_VERSION}_linux_amd64.zip
ARG DOCKER_PROVIDER_VERSION=3.0.2
RUN echo "Adding kreuzwerker/docker v${DOCKER_PROVIDER_VERSION}" \
    && mkdir -p kreuzwerker/docker && cd kreuzwerker/docker \
    && curl -LOs https://github.com/kreuzwerker/terraform-provider-docker/releases/download/v${DOCKER_PROVIDER_VERSION}/terraform-provider-docker_${DOCKER_PROVIDER_VERSION}_linux_amd64.zip
ARG KUBERNETES_PROVIDER_VERSION=2.36.0
RUN echo "Adding kubernetes/kubernetes v${KUBERNETES_PROVIDER_VERSION}" \
    && mkdir -p hashicorp/kubernetes && cd hashicorp/kubernetes \
    && curl -LOs https://releases.hashicorp.com/terraform-provider-kubernetes/${KUBERNETES_PROVIDER_VERSION}/terraform-provider-kubernetes_${KUBERNETES_PROVIDER_VERSION}_linux_amd64.zip
ARG AWS_PROVIDER_VERSION=5.89.0
RUN echo "Adding aws/aws v${AWS_PROVIDER_VERSION}" \
    && mkdir -p aws/aws && cd aws/aws \
    && curl -LOs https://releases.hashicorp.com/terraform-provider-aws/${AWS_PROVIDER_VERSION}/terraform-provider-aws_${AWS_PROVIDER_VERSION}_linux_amd64.zip

RUN chown -R coder:coder /home/coder/.terraform*
WORKDIR /home/coder

# Option 2) Use a network mirror.
#  https://developer.hashicorp.com/terraform/cli/config/config-file#network_mirror
#  Be sure uncomment line 60 and edit network-mirror-example.tfrc to
#  specify the HTTPS base URL of your mirror.

# ADD network-mirror-example.tfrc /home/coder/.terraformrc

USER coder

# Use the .terraformrc file to inform Terraform of the locally installed providers.
ENV TF_CLI_CONFIG_FILE=/home/coder/.terraformrc

Note

If you are bundling Terraform providers into your Coder image, be sure the provider version matches any templates or example templates you intend to use.

# filesystem-mirror-example.tfrc
provider_installation {
  filesystem_mirror {
    path = "/home/coder/.terraform.d/plugins"
  }
}
# network-mirror-example.tfrc
provider_installation {
  network_mirror {
    url = "https://terraform.example.com/providers/"
  }
}

Docker

Follow our docker-compose documentation and modify the docker-compose file to specify your custom Coder image. Additionally, you can add a volume mount to add providers to the filesystem mirror without re-building the image.

First, create an empty plugins directory:

mkdir $HOME/plugins

Next, add a volume mount to compose.yaml:

vim compose.yaml
# compose.yaml
services:
  coder:
    image: registry.example.com/coder:latest
    volumes:
      - ./plugins:/opt/terraform/plugins
    # ...
  environment:
    CODER_TELEMETRY_ENABLE: "false" # Disable telemetry
    CODER_BLOCK_DIRECT: "true" # force SSH traffic through control plane's DERP proxy
    CODER_DERP_SERVER_STUN_ADDRESSES: "disable" # Only use relayed connections
    CODER_UPDATE_CHECK: "false" # Disable automatic update checks
  database:
    image: registry.example.com/postgres:17
    # ...

The terraform providers mirror command can be used to download the required plugins for a Coder template. This can be uploaded into the plugins directory on your offline server.

Kubernetes

We publish the Helm chart for download on GitHub Releases. Follow our Kubernetes documentation and modify the Helm values to specify your custom Coder image.

# values.yaml
coder:
  image:
    repo: "registry.example.com/coder"
    tag: "latest"
  env:
    # Disable telemetry
    - name: "CODER_TELEMETRY_ENABLE"
      value: "false"
    # Disable automatic update checks
    - name: "CODER_UPDATE_CHECK"
      value: "false"
    # force SSH traffic through control plane's DERP proxy
    - name: CODER_BLOCK_DIRECT
      value: "true"
    # Only use relayed connections
    - name: "CODER_DERP_SERVER_STUN_ADDRESSES"
      value: "disable"
    # You must set up an external PostgreSQL database
    - name: "CODER_PG_CONNECTION_URL"
      value: ""
# ...

Air-gapped docs

Coder also provides air-gapped documentation in case you want to host it on your own server. The docs are exported as static files that you can host on any web server, as demonstrated in the example below:

  1. Go to the release page. In this case, we want to use the latest version.
  2. Download the documentation files from the "Assets" section. It is named as coder_docs_<version>.tgz.
  3. Extract the file and move its contents to your server folder.
  4. If you are using NodeJS, you can execute the following command: cd docs && npx http-server .
  5. Set the CODER_DOCS_URL environment variable to use the URL of your hosted docs. This way, the Coder UI will reference the documentation from your specified URL.

With these steps, you'll have the Coder documentation hosted on your server and accessible for your team to use.

Template builder

The template builder requires outbound access to registry.coder.com for terraform init to resolve module sources at template composition time.

For fully air-gapped deployments, disable the template builder:

CODER_DISABLE_TEMPLATE_BUILDER=true

When the builder is disabled, template creation falls back to the standard upload and CLI workflows. The New Template button on the Templates page links to the starter templates page instead of the builder.

For deployments using a self-hosted module registry mirror, set the registry URL instead of disabling the builder:

CODER_TEMPLATE_BUILDER_REGISTRY_URL=registry.internal.example.com

This makes the builder generate module source paths pointing at your mirror rather than registry.coder.com.

Coder Modules

To use Coder modules in offline installations, you can either:

Firewall exceptions

In restricted internet networks, Coder may require connection to internet. Ensure that the following web addresses are accessible from the machine where Coder is installed.

  • code-server.dev (install via AUR)
  • open-vsx.org (optional if someone would use code-server)
  • registry.terraform.io (to create and push template)
  • v2-licensor.coder.com (developing Coder in Coder)

JetBrains IDEs

Gateway, JetBrains' remote development product that works with Coder, has documented offline deployment steps.

Microsoft VS Code Remote - SSH

Installation of the Visual Studio Code Remote - SSH extension (for connecting a local VS Code to a remote Coder workspace) requires that your local machine has outbound HTTPS (port 443) connectivity to:

  • update.code.visualstudio.com
  • vscode.blob.core.windows.net
  • *.vo.msecnd.net

Next steps