7.5 KiB
7.5 KiB
title, description, source, reviewed
| title | description | source | reviewed |
|---|---|---|---|
| 🏗️ Coder SSOT | Single source of truth for the self-hosted Coder control plane at https://code.loca.zone, its private postgres, its deliberate auth model, and its operations. | verified deployment on the loca operator host, 2026-08-19; first user, template and first workspace verified 2026-08-20 | 2026-08-20 |
🏗️ Coder SSOT
Self-hosted Coder control plane (a workspace manager, not an editor) running as a pinned docker compose stack in /home/loca/dev/coder, reverse-proxied by Nginx, published loopback-only on 127.0.0.1:52226, backed by a private postgres container. Coder owns its own identity, so this vhost is deliberately not behind Authelia.
Sibling stack: the single-user browser IDE at cdsr.loca.zone is a different deployment and is documented in its own wiki.
🚀 Quick start
- open: https://code.loca.zone
- the Coder dashboard runs its own login; there is no
auth.loca.zoneredirect on this host - wiki (public, no login): https://wiki.code.loca.zone
- the Coder dashboard runs its own login; there is no
- stack root:
/home/loca/dev/coder- compose:
docker-compose.yml, image pinghcr.io/coder/coder:v2.35.3, databasepostgres:17 - runtime pin:
config/runtime-version.txtcontainsv2.35.3 - nginx source of truth:
deploy/code.nginx.conf, installed as/etc/nginx/sites-available/code.loca.zone - secrets:
.env(mode 600, git-ignored); it holdsCODER_PG_PASSWORDand its contents are never read into docs - admin login:
.admin-credentials(mode 600, git-ignored); it holds the initial Owner login and a CLI session token, and its contents are never read into docs, a ticket or a chat message
- compose:
- lifecycle, all of it needs
sudo -nbecauselocais not in thedockergroup:- bring up:
cd /home/loca/dev/coder && sudo -n docker compose up -d - status:
sudo -n docker compose ps - logs:
sudo -n docker compose logs -f coder - restart server only:
sudo -n docker compose restart coder - stop:
sudo -n docker compose down(containers go, the postgres volume stays)
- bring up:
- loopback health, from the host shell:
curl -sf http://127.0.0.1:52226/healthzreturnsOKcurl -sf http://127.0.0.1:52226/api/v2/buildinforeturns versionv2.35.3+65e2bfb,dashboard_urlhttps://code.loca.zone, telemetryfalse
- port
52226is the only host-visible socket, bound on127.0.0.1inside the51000-53000loca.zone app range; container-internal listener is0.0.0.0:7080
🔐 Auth model in two lines
- Coder authenticates users itself: the browser gets a host-scoped
coder_session_tokencookie, while thecoderCLI and every workspace agent authenticate with theCoder-Session-Tokenheader, so nginx passes all traffic through untouched with noauth_request. - Authelia
default_policyisbypassand no rule namescode.loca.zone, so nothing gates this vhost; adding a rule would answer every cookie-less agent and CLI request with a 302 toauth.loca.zoneand break the deployment. Full reasoning: operations/auth-model.
👤 Bootstrapped state and the operator's next actions
- setup is done; this is no longer a deployment waiting for a first pass:
- Owner account
loca(admin@loca.zone) exists, is active and holds the Owner role, soGET /api/v2/users/firstanswers200 - the default organization is
coder - the
dockertemplate is pushed and its version is active, and one workspace (ws1) is running against it; see operations/workspaces
- Owner account
- how that account was created, for the record:
POST /api/v2/users/firstover loopback, with a randomly generated 32-character password written straight into/home/loca/dev/coder/.admin-credentials(mode 600, git-ignored) and never displayed, logged or copied anywhere else - the operator's actual next actions, in order:
- sign in at https://code.loca.zone and rotate that generated password in Account then Security, so the only stored copy stops being the live credential
- create further workspaces from the
dockertemplate, respecting the memory gate in reference/limits
- Coder OSS has no open signup:
POST /api/v2/usersrequires authentication, so every later account is admin-created only (coder users create, or Users then New user in the dashboard)
🧩 Reaching a workspace
- workspace app, path-based because
coder_appkeepssubdomain = false:https://code.loca.zone/@<username>/<workspace>.<agent>/apps/<slug>/- the trailing slash matters; the server redirects when it is missing
- web terminal:
https://code.loca.zone/@<username>/<workspace>.<agent>/terminal - arbitrary ports from your own machine:
coder port-forward <workspace> --tcp 8000:8080, orcoder ssh <workspace>plus normal SSH forwarding - dashboard port forwarding and
subdomain = trueapps are unavailable here because no wildcard access URL is configured; see reference/limits and operations/workspaces
⬆️ Upgrade in four steps
cd /home/loca/dev/coder
# 1. snapshot first: upstream states Coder does not support rollbacks
sudo -n docker compose exec -T database pg_dump -U coder coder | gzip > /home/loca/dev/coder/backup-$(date +%F).sql.gz
# 2. edit the image tag in docker-compose.yml and config/runtime-version.txt
sudo -n docker compose pull coder && sudo -n docker compose up -d coder
curl -sf http://127.0.0.1:52226/api/v2/buildinfo
Full procedure, including restore: operations/install-upgrade.
✅ Do / don't
- do: pin the image tag and keep
config/runtime-version.txtin step withdocker-compose.yml - do: keep postgres private; it publishes no host port and is reachable only over the compose network
- do: record
free -mbefore and after every workspace build; this host runs about ten co-tenant stacks and the gate is documented in reference/limits - do: leave TLS to nginx; the container speaks plain HTTP on loopback only
- don't: add an Authelia rule for
code.loca.zone; it would break agents, the CLI and DERP traffic - don't: pass
/var/run/docker.sockinto a workspace container; the socket stays on the control plane - don't: create Coder accounts for other people; this is a single-admin deployment with the Owner authoring every template
- don't: put secrets in this repo;
.envis the only home forCODER_PG_PASSWORDand it is git-ignored - don't: commit, paste or quote
.admin-credentialsanywhere; it stays at mode 600 in a git-ignored path, and its only legitimate use is one interactive sign-in before the password is rotated
Start here
| Need | Page |
|---|---|
| Architecture section hub | architecture/index |
| Understand the deployed shape | architecture/runtime-topology |
| Operations section hub | operations/index |
| Bring up, upgrade, back up | operations/install-upgrade |
| Understand or change auth | operations/auth-model |
| Build templates and reach workspaces | operations/workspaces |
| Re-run the proof checks | operations/verification |
| Reference section hub | reference/index |
| Look up a path, port or env var | reference/config-paths |
| Know what is deliberately off | reference/limits |
📚 Sources
- https://coder.com/docs/install/docker
- https://coder.com/docs/admin/setup
- https://coder.com/docs/admin/networking/port-forwarding
- https://coder.com/docs/install/upgrade
- https://coder.com/docs/admin/users
- https://coder.com/docs/reference/api/users
- https://coder.com/docs/reference/api/authentication
- https://github.com/coder/coder
- https://github.com/coder/coder/blob/main/coderd/workspaceapps/appurl/appurl.go
- https://github.com/coder/coder/blob/main/site/src/modules/apps/apps.ts