Skip to content

Configuration

Configuration

Every field resolves through the same precedence: CLI flag > environment variable > settings file > default. This is purely additive — with no settings file configured (the common case), every field resolves exactly as it did before the settings file existed. Config loading (service/src/config.ts's loadServiceConfig) runs at process startup, before server.listen() — any misconfiguration here crashes the process immediately rather than surfacing as a mysterious per-request failure later (see 0005's sibling concern documented in Architecture).

Fields

Field CLI flag Env var Settings file path Default
BuildKit endpoint --buildkit-endpoint BUILDKIT_ENDPOINT buildkit.endpoint (unset — readiness fails)
HTTP port --port PORT service.port 8080
Buildx builder name --buildx-builder-name BUILDX_BUILDER_NAME (none) devcontainer-builder-remote
SSH host key policy --ssh-host-key-policy SSH_HOST_KEY_POLICY sshHostKeyPolicy tofu
Plain-HTTP registries --insecure-registries (comma-separated) INSECURE_REGISTRIES (comma-separated) insecureRegistries [] (HTTPS everywhere)
Accept git:///http:// repository URLs --allow-insecure-git-protocols ALLOW_INSECURE_GIT_PROTOCOLS ("true"/"false") git.allowInsecureProtocols false (only https://, ssh://, SCP-style)
Git credentials file path --git-credentials-config-path GIT_CREDENTIALS_CONFIG_PATH (none — see below) (unset — empty list)
Registry mapping file path --registry-mapping-config-path REGISTRY_MAPPING_CONFIG_PATH (none — see below) (unset — empty list)
Default platforms --build-platforms (comma-separated) BUILD_PLATFORMS (comma-separated) build.platforms [] (no --platform passed)
Default no-cache --build-no-cache BUILD_NO_CACHE ("true"/"false") build.noCache false
Default cache-from --build-cache-from BUILD_CACHE_FROM build.cacheFrom (unset)
Default cache-to --build-cache-to BUILD_CACHE_TO build.cacheTo (unset)
Default BuildKit mode --buildkit-mode BUILDKIT_MODE build.mode auto
Clone timeout (seconds) --clone-timeout CLONE_TIMEOUT_SECONDS build.cloneTimeoutSeconds 600
Build+push timeout (seconds) --build-timeout BUILD_TIMEOUT_SECONDS build.timeoutSeconds 3600
Concurrent builds per instance --max-concurrent-builds MAX_CONCURRENT_BUILDS build.maxConcurrent 4 (0 = no limit)
Fallback image (repos without a devcontainer.json, ADR-0013) --fallback-image FALLBACK_IMAGE build.fallbackImage (unset — such builds fail); the chart sets mcr.microsoft.com/devcontainers/base:ubuntu
Sentry/GlitchTip DSN --sentry-dsn SENTRY_DSN sentry.dsn (unset — error tracking off)
Service name --service-name SERVICE_NAME observability.serviceName devcontainer-builder
Deployment environment --environment DEPLOYMENT_ENVIRONMENT observability.environment development
Command-log retention (per kind) --command-log-retention COMMAND_LOG_RETENTION logs.retention 10
Settings file path itself --settings SERVICE_CONFIG_PATH — (unset)

Both new fields land on every structured log line (service.name/ deployment.environment.name — see Architecture). There's no field for the service's own version — it's always this build's own package.json version, never something an operator would choose to override. The Helm chart sets SERVICE_NAME unconditionally (from .Chart.Name, matching the app.kubernetes.io/name label every other part of the chart already uses) and DEPLOYMENT_ENVIRONMENT from its own environment value — see Helm reference.

git clone/devcontainer build --push output is captured to a file per invocation rather than the pod's own stdout (see 0010), fetchable via GET /logs/{id} (see HTTP API (Redoc)) - commandLogRetention caps how many of these are kept per kind (git/ docker) before the oldest are pruned.

Tracing's service.name is OTEL_SERVICE_NAME if set, else the same SERVICE_NAME env var the logs use (so the two agree by default), else devcontainer-builder. Like DEPLOYMENT_ENVIRONMENT below, it's read before this config loads, so --service-name/observability.serviceName only affect the logs - set OTEL_SERVICE_NAME too if you use those.

OpenTelemetry tracing is deliberately not in this table — it's bootstrapped from the standard OTEL_EXPORTER_OTLP_ENDPOINT/ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT env vars before this config even loads (see Architecture), the same env vars any OTel SDK reads — not an app-specific flag with its own precedence chain. DEPLOYMENT_ENVIRONMENT is the one exception read in both places independently: tracing.ts reads it directly (for the trace resource's deployment.environment.name) since it runs before this config ever loads, while config.ts reads the identical env var again (for the log fields above) — the same value, read twice, rather than plumbing OTel's bootstrap through this file's own precedence chain.

gitCredentials/registryMapping have no CLI flag or env var of their own for the entries themselves (only a path to a file, for both the dedicated-file and settings-file forms) — there's no practical way to express a structured list as a single flag or env var value.

The settings file

One JSON or YAML file (SERVICE_CONFIG_PATH/--settings), parsed by extension (.json, .yaml, or .yml — anything else is a startup error). Its shape deliberately mirrors the Helm chart's own values.yaml keys and nesting, not a flat, invented shape — an operator who already knows the chart's values recognizes this file immediately:

{
  "buildkit": { "endpoint": "tcp://buildkit.example:1234" },
  "build": {
    "platforms": ["linux/amd64", "linux/arm64"],
    "noCache": false,
    "cacheFrom": "type=registry,ref=ghcr.io/example/app:buildcache",
    "cacheTo": "type=registry,ref=ghcr.io/example/app:buildcache,mode=max",
    "mode": "auto",
    "cloneTimeoutSeconds": 600,
    "timeoutSeconds": 3600,
    "maxConcurrent": 4
  },
  "git": { "allowInsecureProtocols": false },
  "service": { "port": 8080 },
  "sshHostKeyPolicy": "pinned",
  "gitCredentials": {
    "entries": [
      { "host": "github.com", "kind": "https", "username": "svc-bot", "token": "ghp_example" },
      { "host": "gitlab.internal.example.com", "kind": "ssh", "privateKey": "-----BEGIN OPENSSH PRIVATE KEY-----\n...", "pinnedHostKey": "gitlab.internal.example.com ssh-ed25519 AAAA..." }
    ]
  },
  "registryMapping": {
    "rules": [
      { "hostMatch": "github.com", "pathPrefix": "org-a/", "registry": "ghcr.io/org-a" }
    ]
  },
  "observability": { "serviceName": "devcontainer-builder", "environment": "production" }
}

Every other key values.yaml has (image, resources, registryAuth, ...) has no runtime-config meaning here and is silently ignored if present — this file only ever feeds loadServiceConfig, it's never re-templated back into the chart.

A malformed or non-object settings file crashes at startup

Invalid JSON/YAML, a file that parses to something other than an object, or a wrong-typed known field (buildkit.endpoint not a string, build.platforms not an array of strings, build.noCache not a boolean, build.cacheFrom/build.cacheTo not a string, build.mode not "auto"/"never", build.cloneTimeoutSeconds/ build.timeoutSeconds/build.maxConcurrent not a number (or not a positive integer - 0 is allowed for maxConcurrent), git.allowInsecureProtocols not a boolean, service.port not a number, gitCredentials/registryMapping not an object, .entries/.rules not an array) all throw synchronously at startup — the same "fail loud, fail immediately" behavior as every other config source, not a silently ignored or partially-applied file.

gitCredentials.entries / registryMapping.rules

Same array shape whether they arrive via the settings file (gitCredentials.entries/registryMapping.rules) or a dedicated file (GIT_CREDENTIALS_CONFIG_PATH/REGISTRY_MAPPING_CONFIG_PATH — a bare JSON/YAML array, not wrapped in an object). A dedicated-file path, if set, wins entirely over the settings file's own entries for that same list — they don't merge.

Git credential entry

Field Type Notes
host string Required, non-empty. Matched exactly against the clone URL's host.
kind "https" | "ssh" Required.
username, token string Required if kind: "https".
privateKey string Required, non-empty, if kind: "ssh".
pinnedHostKey string Optional for kind: "ssh" — required if sshHostKeyPolicy: "pinned".

Registry mapping rule

Field Type Notes
hostMatch string Optional. Exact match against the clone URL's host.
pathPrefix string Optional. Prefix match against the clone URL's path.
registry string Required, non-empty.

Rules are checked in order; the first rule whose hostMatch (if given) and pathPrefix (if given) both match wins. A rule with neither field set is a universal fallback. See 0003: Registry resolution via mapping rules.

One bad entry is skipped and logged, not fatal

Inside an otherwise-valid array, one entry that fails its own shape check (missing host, wrong kind, etc.) is logged (skipping invalid <git credentials|registry mapping> config entry at index <n>) and dropped — the service still starts, using every other valid entry. This is different from the array/settings file itself being malformed (see the warning above), which is fatal. The array-vs-file distinction exists because a single typo'd entry shouldn't take down every other configured host, but a genuinely broken config file should never be silently treated as "no config."

INSECURE_REGISTRIES

Registry hosts (host or host:port, without any namespace path) that the service's own registry calls — GET/DELETE /image and GET /devcontainer — reach over http:// instead of https://, e.g. an in-cluster registry:2 without TLS. Builds aren't affected: whether BuildKit pushes to a plain-HTTP registry is BuildKit's own config (buildkitd.toml). There is deliberately no automatic HTTPS-to-HTTP fallback — see 0011.

An explicit http:// prefix on a request's registry is only honored for a host listed here - for any other host it's a 400, so a caller can't send the service's (or its own) registry credentials over cleartext. An https:// prefix is always accepted.

ALLOW_INSECURE_GIT_PROTOCOLS

Off by default: POST /build accepts only https://, ssh:// and SCP-style [user@]host:path repository URLs, and every git the service runs gets GIT_ALLOW_PROTOCOL=https:ssh. Set to true to also accept git:// and http:// (unauthenticated and unencrypted, meant for test git servers - the BDD suite sets it through the chart's extraEnv). Other schemes (file://, ftp://, git's ext:: helpers, ...) are always rejected with 400.

Timeouts and concurrency

CLONE_TIMEOUT_SECONDS bounds the git clone; BUILD_TIMEOUT_SECONDS bounds everything after it (remote builder setup, devcontainer build --push, the config-label build). When one runs out, the running command and its child processes are killed (SIGTERM, then SIGKILL after 10s) and the build fails with a 500 whose error names the phase, e.g. ... was killed by SIGTERM: git clone timed out after 600s. The service's own registry calls (/image, /devcontainer, the post-push user probe) time out after 30s each.

MAX_CONCURRENT_BUILDS caps builds running at once in one instance; past it, POST /build answers 429 with Retry-After: 30 instead of queueing.

SSH_HOST_KEY_POLICY

tofu (default) or pinned — any other value is a startup error (SSH_HOST_KEY_POLICY must be "tofu" or "pinned", got "<value>"). See Credential handling for what each policy actually does at clone time.