- docker-compose: CODER_OIDC_* wired to Authelia (password login kept as break-glass) - scripts/backup-stacks.sh + stacks-backup.timer: nightly pg_dump, Continue state, stack configs - vault: verification items 13/14, auth-model OIDC section, limits DNS-01 runbook
8.7 KiB
title, description, source, reviewed
| title | description | source | reviewed |
|---|---|---|---|
| 🚧 Deliberate limits | What this Coder deployment does not enable and what each omission costs: no wildcard access URL, no Authelia gate, no open signup, no external provisioner, and a hard memory pressure gate. | verified deployment on the loca operator host, 2026-08-19 | 2026-08-20 |
🚧 Deliberate limits
Every entry here is a choice, not an oversight. Each one states the cost, the true blocker, and what it would take to lift it.
🌐 No wildcard access URL
CODER_WILDCARD_ACCESS_URL is unset.
- the true blocker is TLS issuance, not DNS
- nested names under the host wildcard do resolve;
foo.code.loca.zoneand evendeep.nested.code.loca.zoneanswer on this host, as does any nonexistent label underloca.zone - each certbot lineage covers only the SANs it was issued for; the
code.loca.zonelineage coverscode.loca.zoneandwiki.code.loca.zoneand nothing else - a
*.code.loca.zonecertificate requires DNS-01 validation with DNS-provider API credentials, which are not configured on this host; HTTP-01 via the/var/www/htmlwebroot cannot issue wildcards - upstream's own nginx walkthrough confirms the shape: a wildcard certificate is obtained with a DNS plugin and a provider API token, not with webroot validation
- nested names under the host wildcard do resolve;
- lost features, exactly:
- dashboard port forwarding, including the Open Ports panel and port sharing URLs
coder_appresources withsubdomain = true; upstream states such apps are not accessible when the administrator has not set up wildcards- web apps that require an absolute root path, notably Vite, the React dev server, the Next.js dev server, JupyterLab and RStudio per upstream
- kept features, exactly:
- path-based
coder_appresources at/@<user>/<workspace>.<agent>/apps/<slug>/ - the dashboard and the web terminal
coder port-forwardfrom the CLI- SSH port forwarding, including
coder ssh -Rand anything set up bycoder config-ssh - Coder Desktop and the IDE remotes (VS Code extension, JetBrains Gateway)
- path-based
Runbook: enabling *.code.loca.zone (blocked, do not start until both prerequisites exist)
Preflight re-confirmed 2026-08-20: sudo -n certbot plugins lists exactly nginx, standalone, webroot, no dns-* plugin is installed, and /etc/letsencrypt/ holds no DNS credential file. So this runbook cannot be executed today, and nothing below should be attempted piecemeal — a half-applied wildcard leaves nginx serving a name its certificate does not cover.
Two named prerequisites, both operator decisions:
- a DNS-provider API credential file for the
loca.zonezone, mode 600 root-owned, e.g./etc/letsencrypt/dns-<provider>.ini. The provider is genuinely unknown from this host:loca.zonenameservers point at178.18.250.170, self-hosted, so the credential shape cannot be guessed - the matching plugin package
python3-certbot-dns-<provider>, which is a package install and therefore ask-first
Then, in order:
# 1. expand the existing lineage rather than creating a second one
sudo certbot certonly \
--dns-<provider> \
--dns-<provider>-credentials /etc/letsencrypt/dns-<provider>.ini \
--cert-name code.loca.zone --expand \
-d code.loca.zone -d wiki.code.loca.zone -d '*.code.loca.zone'
sudo certbot certificates # confirm all three SANs on one lineage
# 2. nginx: add the wildcard to both blocks that own the app name
# /etc/nginx/sites-available/code.loca.zone
# line 3 (listen 443 ssl) -> server_name code.loca.zone *.code.loca.zone;
# line 56 (listen 80) -> server_name code.loca.zone *.code.loca.zone;
# leave the two wiki.code.loca.zone blocks alone: a wildcard on the app
# block would otherwise swallow the wiki host, and the wiki must stay static
sudo nginx -t && sudo systemctl reload nginx
# 3. Coder: add to the coder service environment in docker-compose.yml
# CODER_WILDCARD_ACCESS_URL: "*.code.loca.zone"
cd /home/loca/dev/coder && sudo -n docker compose config >/dev/null \
&& sudo -n docker compose up -d --force-recreate coder
curl -sf http://127.0.0.1:52226/healthz
Verified against the running v2.35.3 binary, not assumed: coder server --help documents --wildcard-access-url, $CODER_WILDCARD_ACCESS_URL taking the form "*.example.com", and --disable-path-apps, $CODER_DISABLE_PATH_APPS whose own help text says it is "recommended for security purposes if a --wildcard-access-url is configured". So the follow-up hardening step is:
- set
CODER_DISABLE_PATH_APPS: "true"only after subdomain apps are confirmed working, because it removes the path-based routes this deployment currently depends on, including thecode-serverapp onws1 - note the RFC 1035 label limit: app, agent, workspace and username together must stay under 63 characters in one hostname label
- renewal: the lineage keeps its
--dns-<provider>authenticator in/etc/letsencrypt/renewal/code.loca.zone.conf, so unattended renewal needs the credential file to stay in place; the existing nginx reload hook still applies
🔓 No Authelia gate
- cost: the dashboard is publicly reachable and Coder's own login page is the only thing between the internet and the deployment; there is no second factor at the proxy
- why the cost is accepted: an
auth_requestgate would 302 every cookie-less request, which is exactly what thecoderCLI, the workspace agents and DERP relay traffic send. A browser-only gate is impossible because the SPA and the agents share/api/v2 - compensating controls: Coder's own session handling, no open signup, a pinned server image, and a single admin account
- since 2026-08-20 identity is nonetheless unified: Coder consumes Authelia over OIDC at the application layer, with password login kept as break-glass, while the proxy stays ungated — full argument in operations/auth-model
🚫 No open signup
- cost: onboarding another person is a manual admin action; there is no self-service registration
- why:
POST /api/v2/usersrequires authentication, so only an admin can mint accounts, andPOST /api/v2/users/firstexists solely for the initial Owner - consequence to respect: do not create accounts for other people on this deployment. Template Admin is root-equivalent here because of the docker socket, so every additional privileged account widens host exposure
- if multi-user access is ever wanted, do it through OIDC with Authelia as the provider rather than by handing out passwords
🏗️ No external provisioner
- cost: template builds run inside the
codercontainer, so a heavy build competes with the control plane for the same 8 GB ceiling, and a build failure shows up in the server log rather than in a separate daemon's log - why: an external provisioner needs its own credentialed daemon and a scoped provisioner key; for a single-admin docker-only deployment that is more moving parts than it removes
- security note carried over from upstream: provisioner daemons hold the credentials that can create compute. Here that credential is the bind-mounted
/var/run/docker.sock, which makes the server root-equivalent on the host. Containment list: operations/workspaces
🧠 Memory and swap pressure gate
The measured state of this host on 2026-08-19, before Coder existed:
free -mshowed 36447 of 60278 MB used- swap was 15926 of 16383 MB consumed, that is effectively exhausted
- roughly ten co-tenant stacks were already running, including zulip, mattermost and eight postgres instances
Containment already in place:
mem_limit: 8gon thecodercontainer, so the control plane plus its in-process provisioner cannot exceed 8 GBmem_limit: 2gon thecoder-databasecontainer- postgres publishes no host port, so nothing outside the compose network can drive load into it
The rules, in order:
- record
free -mbefore bring-up and again after the first workspace build, so the real cost of one workspace is known rather than estimated - do not create additional workspaces if available memory drops below about 8 GB
- workspace containers are siblings on the host daemon and are not covered by the compose
mem_limitvalues, so give every template an explicit resource ceiling - if swap is already exhausted, stop and free memory elsewhere before building anything; a build that lands on an exhausted swap will thrash the co-tenant stacks, not just this one
📚 Sources
- https://coder.com/docs/admin/setup
- https://coder.com/docs/admin/networking/wildcard-access-url
- https://coder.com/docs/admin/networking/port-forwarding
- https://coder.com/docs/tutorials/reverse-proxy-nginx
- https://coder.com/docs/tutorials/best-practices/security-best-practices
- https://coder.com/docs/reference/api/users
- https://registry.terraform.io/providers/coder/coder/latest/docs/resources/app