- 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
12 KiB
12 KiB
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
coderwith staterunningand the single port mapping127.0.0.1:52226->7080/tcp - expect container
coder-databasewith staterunningand healthhealthy, and no port mapping at all - a
coder-databasestuck instartingmeans thepg_isready -U coder -d coderhealthcheck has not passed yet;coderwill 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 -sfmeans the server is not answering; checksudo -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
versionv2.35.3+65e2bfb, matchingconfig/runtime-version.txt(v2.35.3) and the image tag indocker-compose.yml - expect
dashboard_urlhttps://code.loca.zone, which provesCODER_ACCESS_URLreached the server - expect
telemetryfalse, which provesCODER_TELEMETRY_ENABLEtook effect - no jq on this host; if the raw JSON needs shaping, use
python3 -cwith thejsonmodule
4️⃣ Public app host answers directly, with no auth redirect
curl -sSI https://code.loca.zone/
- expect
HTTP/1.1 200 OK(orHTTP/2 200), served by the Coder SPA, with noLocationheader - expect the security headers from
snippets/security-headers-app.conf - a
302tohttps://auth.loca.zone/...here is a regression: someone added an Authelia rule forcode.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 302with aLocationpointing athttps://auth.loca.zone/, becausecdsr.loca.zonecarries an explicitone_factorAuthelia rule - this is the control for item 4: the same nginx and the same Authelia produce a redirect there and a direct
200here, 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 return302, the Coder vhost was gated by mistake
6️⃣ Certificate lineage covers both names
sudo -n certbot certificates
- expect one lineage named
code.loca.zonewhose domains arecode.loca.zoneandwiki.code.loca.zone, expiring 2026-11-17 - expect no
*.code.loca.zoneentry; 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.pemandprivkey.pem, so a renewal that creates a second lineage such ascode.loca.zone-0001silently 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/currentat it; that symlink is the nginx root for the wiki vhost - expect
HTTP/1.1 200 OKwith no auth redirect once the build has publishedcurrent; before the first build there is nocurrentsymlink, so nginx cannot serve the site ./build.sh code --check-onlyvalidates without publishing, which is the safe way to test frontmatter changes- the wiki is public by design: Authelia
default_policyisbypassand no rule nameswiki.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 from404to200 404is 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
dockerin the template list with an active version, and workspacews1owned bylocawith statusStartedand healthhealthy; observed 2026-08-20 - expect exactly one workspace container
coder-loca-ws1in stateUp; it is a sibling container on the host daemon rather than part of this compose project, sodocker compose pswill never list it - there is no
coderbinary 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 iscoder loginfrom the operator's own machine - a workspace stuck in
Startingfor several minutes is usually the image pull; the first build pulledcodercom/enterprise-base:ubuntuin about 1m30s and started in about 95s in total, see operations/workspaces
🔟 Agent connected, app healthy, app URL loads
coder show ws1
- expect agent
mainconnected with lifecycleready, and appcode-serverwith healthhealthyandsubdomainfalse - 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
502or a workbench that never finishes connecting points at the agent rather than nginx; re-runcoder show ws1and 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 -mbefore 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: 8goncoder,mem_limit: 2gondatabase; see reference/limits
1️⃣2️⃣ Ad-hoc port hygiene
ss -ltn
- expect
127.0.0.1:52226present 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;
52222pwiz,52223reserved for pwiz,52224cdsr and52226code 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
dockerat28800000default TTL (8h) and3600000activity bump (1h) - expect
ws1ttl_ms28800000and a non-nulldeadlineroughly 8h ahead - a
deadlineofNoneon a running workspace means that build predates the TTL: the schedule only attaches at build time, socoder restart ws1 -yis required once.coder schedule extendrefuses withWorkspace shutdown is manual.in that state - the subcommand is
coder schedule stop <ws> 8h; there is nooverride-stopin 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
activewith a next elapse at 04:30 local,Persistent=trueso a missed run catches up after downtime - expect three non-empty artifacts in the dated directory:
coder-db.sql.gz(pg_dumpof 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-credentialsand.ai-credentialsare deliberately excluded — they are keys, not data; verify withsudo -n tar tzf $D/stack-configs.tgz | grep -c credentialsreturning0
🧹 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 cleanand exit code0;--quietsuppresses 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 coderesolves/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 Goall 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, whichbuild.shruns itself: that validator only gates wikis shipping awiki-ia.jsonmanifest, and this wiki ships none, so itsSSOT Validation passedline 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 asfile:lineplus 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
.mdin 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
python3and needs no pyyaml