Files
coder/q5vault/operations/verification.md
Antigravity 0e54b86dac coder: autostop TTL, nightly backups, Authelia OIDC, wildcard runbook
- 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
2026-08-20 11:05:47 +02:00

12 KiB
Raw Blame History

title, description, source, reviewed
title description source reviewed
✅ Verification Numbered proof checklist for the Coder stack with exact commands and expected output, including the deliberate contrast against the Authelia-gated cdsr.loca.zone host. verified deployment on the loca operator host, 2026-08-19; user, template, workspace and app checks verified 2026-08-20 2026-08-20

✅ Verification

Run this list in order after any change to the compose file, the nginx vhost, the certificate or the image pin. Items 1 to 6 were observed on 2026-08-19 with the outputs shown; items 8, 9 and 10 were observed on 2026-08-20 after the first-user bootstrap, the template push and the first workspace build, and item 11 was re-measured the same day. Item 7 becomes true again on every wiki build.

1️⃣ Both containers up, database healthy

cd /home/loca/dev/coder && sudo -n docker compose ps
  • expect container coder with state running and the single port mapping 127.0.0.1:52226->7080/tcp
  • expect container coder-database with state running and health healthy, and no port mapping at all
  • a coder-database stuck in starting means the pg_isready -U coder -d coder healthcheck has not passed yet; coder will not start until it does

2️⃣ Server health on loopback

curl -sf http://127.0.0.1:52226/healthz
  • expect exactly OK
  • a non-zero exit from curl -sf means the server is not answering; check sudo -n docker compose logs --tail 200 coder

3️⃣ Running build matches the pin

curl -sf http://127.0.0.1:52226/api/v2/buildinfo
  • expect version v2.35.3+65e2bfb, matching config/runtime-version.txt (v2.35.3) and the image tag in docker-compose.yml
  • expect dashboard_url https://code.loca.zone, which proves CODER_ACCESS_URL reached the server
  • expect telemetry false, which proves CODER_TELEMETRY_ENABLE took effect
  • no jq on this host; if the raw JSON needs shaping, use python3 -c with the json module

4️⃣ Public app host answers directly, with no auth redirect

curl -sSI https://code.loca.zone/
  • expect HTTP/1.1 200 OK (or HTTP/2 200), served by the Coder SPA, with no Location header
  • expect the security headers from snippets/security-headers-app.conf
  • a 302 to https://auth.loca.zone/... here is a regression: someone added an Authelia rule for code.loca.zone. Remove it, reload Authelia, and re-read operations/auth-model

5️⃣ Contrast check against the gated sibling host

curl -sSI https://cdsr.loca.zone/
  • expect HTTP/1.1 302 with a Location pointing at https://auth.loca.zone/, because cdsr.loca.zone carries an explicit one_factor Authelia rule
  • this is the control for item 4: the same nginx and the same Authelia produce a redirect there and a direct 200 here, which proves the difference is the per-host rule and not a broken gate
  • if both hosts return 200, Authelia itself is down or its rule set was truncated; if both return 302, the Coder vhost was gated by mistake

6️⃣ Certificate lineage covers both names

sudo -n certbot certificates
  • expect one lineage named code.loca.zone whose domains are code.loca.zone and wiki.code.loca.zone, expiring 2026-11-17
  • expect no *.code.loca.zone entry; a wildcard would need DNS-01 validation with DNS-provider credentials that are not configured here, see reference/limits
  • the vhost reads /etc/letsencrypt/live/code.loca.zone/fullchain.pem and privkey.pem, so a renewal that creates a second lineage such as code.loca.zone-0001 silently leaves nginx on the old files

7️⃣ Wiki host serves the built vault

cd /home/loca/dev/wikis && ./build.sh code
curl -sSI https://wiki.code.loca.zone/
  • the build publishes a dated dist-* directory and repoints /home/loca/dev/wikis/code/current at it; that symlink is the nginx root for the wiki vhost
  • expect HTTP/1.1 200 OK with no auth redirect once the build has published current; before the first build there is no current symlink, so nginx cannot serve the site
  • ./build.sh code --check-only validates without publishing, which is the safe way to test frontmatter changes
  • the wiki is public by design: Authelia default_policy is bypass and no rule names wiki.code.loca.zone

8️⃣ Initial user exists

curl -so /dev/null -w '%{http_code}\n' http://127.0.0.1:52226/api/v2/users/first
  • expect 200: the initial Owner exists, which is upstream's own existence check. Observed on 2026-08-20, after the bootstrap flipped this endpoint from 404 to 200
  • 404 is correct only on a fresh deployment that has never been bootstrapped; seeing it here means the postgres volume was lost and every user, template and workspace went with it
  • no credential is ever recorded in this wiki; the account and the handling of its generated password are described in operations/auth-model

9️⃣ Template pushed and workspace running

coder login https://code.loca.zone && coder templates list && coder list
sudo -n docker ps --filter name=coder-loca-ws1 --format '{{.Names}} {{.Status}}'
  • expect docker in the template list with an active version, and workspace ws1 owned by loca with status Started and health healthy; observed 2026-08-20
  • expect exactly one workspace container coder-loca-ws1 in state Up; it is a sibling container on the host daemon rather than part of this compose project, so docker compose ps will never list it
  • there is no coder binary on the host: the CLI lives inside the server container at /opt/coder, and the session file staged there during the bootstrap was deleted on purpose, so an in-container invocation needs a fresh login; the clean route is coder login from the operator's own machine
  • a workspace stuck in Starting for several minutes is usually the image pull; the first build pulled codercom/enterprise-base:ubuntu in about 1m30s and started in about 95s in total, see operations/workspaces

🔟 Agent connected, app healthy, app URL loads

coder show ws1
  • expect agent main connected with lifecycle ready, and app code-server with health healthy and subdomain false
  • then open https://code.loca.zone/@loca/ws1.main/apps/code-server/ in a browser: expect a full code-server workbench, redirected to ?folder=/home/coder. Verified in a real browser on 2026-08-20, and this is the proof that path-based apps work with no wildcard access URL and no extra certificate
  • the trailing slash matters; without it the server answers with a redirect
  • a 502 or a workbench that never finishes connecting points at the agent rather than nginx; re-run coder show ws1 and read the agent logs inside the workspace container

1️⃣1️⃣ Resource pressure gate

free -m
  • measured on 2026-08-19 before Coder existed: 36447 of 60278 MB used, and swap 15926 of 16383 MB consumed, alongside roughly ten co-tenant stacks including zulip, mattermost and eight postgres instances
  • re-measured on 2026-08-20 immediately before the first workspace build: swap had drained to 0 of 16383 MB and roughly 31 GB of RAM was available, so the eight-gigabyte floor below passed on measurement rather than assumption; record free -m before and after every later build so the delta of one workspace stays known
  • rule: do not create additional workspaces if available memory drops below about 8 GB
  • container ceilings already in place: mem_limit: 8g on coder, mem_limit: 2g on database; see reference/limits

1️⃣2️⃣ Ad-hoc port hygiene

ss -ltn
  • expect 127.0.0.1:52226 present and no other new listener from this stack
  • any dev server started from a workspace or an IDE terminal must avoid every port in the AGENTS.md registry; 52222 pwiz, 52223 reserved for pwiz, 52224 cdsr and 52226 code are taken
  • loca.zone application backends bind loopback inside 51000-53000

1️⃣3️⃣ Autostop TTL is armed

TOKEN=$(sed -n 's/^session_token=//p' /home/loca/dev/coder/.admin-credentials)
curl -s -H "Coder-Session-Token: $TOKEN" http://127.0.0.1:52226/api/v2/templates \
  | python3 -c "import json,sys; [print(t['name'], t['default_ttl_ms'], t['activity_bump_ms']) for t in json.load(sys.stdin)]"
curl -s -H "Coder-Session-Token: $TOKEN" http://127.0.0.1:52226/api/v2/users/loca/workspace/ws1 \
  | python3 -c "import json,sys; w=json.load(sys.stdin); print(w['ttl_ms'], w['latest_build']['deadline'])"
  • expect template docker at 28800000 default TTL (8h) and 3600000 activity bump (1h)
  • expect ws1 ttl_ms 28800000 and a non-null deadline roughly 8h ahead
  • a deadline of None on a running workspace means that build predates the TTL: the schedule only attaches at build time, so coder restart ws1 -y is required once. coder schedule extend refuses with Workspace shutdown is manual. in that state
  • the subcommand is coder schedule stop <ws> 8h; there is no override-stop in v2.35.3

1️⃣4️⃣ Nightly backup timer

systemctl list-timers stacks-backup --no-pager
sudo -n systemctl start stacks-backup.service
D=/home/loca/backups/stacks/$(date +%F)
sudo -n gunzip -t $D/coder-db.sql.gz && sudo -n ls -l $D
  • expect the timer active with a next elapse at 04:30 local, Persistent=true so a missed run catches up after downtime
  • expect three non-empty artifacts in the dated directory: coder-db.sql.gz (pg_dump of the Coder control plane), continue-config.tgz (/home/loca/.continue, which lives outside both projects), stack-configs.tgz (both stacks' config, deploy, scripts and templates)
  • artifacts are root-owned; retention keeps 14 dated directories
  • .env, .admin-credentials and .ai-credentials are deliberately excluded — they are keys, not data; verify with sudo -n tar tzf $D/stack-configs.tgz | grep -c credentials returning 0

🧹 Pre-publish wiki lint gate

Run this before every build.sh invocation, so before item 7 above. It is a gate, not a smoke test: it exits non-zero and publishes nothing.

python3 /home/loca/dev/wikis/lint-vault.py /home/loca/dev/coder/q5vault
  • expect 11 pages, 0 findings, all 5 checks clean and exit code 0; --quiet suppresses that line so only failures print, which is the form to use in a wrapper
  • the vault may also be named instead of pathed: python3 /home/loca/dev/wikis/lint-vault.py code resolves /home/loca/dev/wikis/code/content, the symlink into this vault
  • why it exists: a frontmatter description: whose value held an unquoted colon-space made the Quartz note-properties transformer fail its YAML parse, and esbuild then died with a Go all goroutines are asleep - deadlock! panic rather than a readable error. One unquoted colon cost a whole build with no usable diagnostic
  • it complements scripts/validate-ssot.py, which build.sh runs itself: that validator only gates wikis shipping a wiki-ia.json manifest, and this wiki ships none, so its SSOT Validation passed line is a vacuous pass here and this linter is the only real pre-publish gate on the vault
  • check 1, frontmatter scalars: unquoted values containing a colon-space, starting with #, @, *, &, !, % or a backtick, ending in a colon, carrying an inline # comment that would silently truncate the value, or holding an unterminated quote. Reported as file:line plus the offending key
  • check 2, wikilinks in tables: an unescaped | inside [[...]] on a table row splits the cell, and an alias containing / is truncated at the slash when rendered
  • check 3, dead wikilinks: every target must resolve to a real .md in the vault, tried vault-root-relative first and then relative to the linking page, tolerating a leading ./ and an optional #anchor
  • check 4, house style: no markdown bold anywhere
  • check 5, page shape: no second H1 in one file, and no page without a frontmatter block
  • checks 2 to 5 ignore fenced blocks and inline code spans, because nothing in those regions renders as markdown; a wikilink shown as an example is not a link
  • standard library only, so it runs on any host with python3 and needs no pyyaml

📚 Sources