Coder ships `coder exp mcp server` and omp is an MCP client, so the two compose with no custom code. The agent stays on the host where its credentials already live and reaches into the workspace, rather than shipping secrets into a container. - scripts/coder-mcp.sh wraps the server, passing a dedicated `omp-mcp` token via `docker exec --env-file` (mode 600 temp file, shredded by an EXIT trap) so the token never appears in argv or ps - allow-list exposes 26 of 32 tools; template mutation is excluded because pushing a template is the one real path from a Coder principal to the host docker socket - scripts/coder-token-rotate.sh handles the 168h token lifetime cap: mint, verify against the API, then revoke the predecessors - .omp/mcp.json registers it project-scoped so 26 tools do not load into every unrelated omp session The docker exec must stay in the foreground: backgrounding it makes bash redirect stdin from /dev/null with job control disabled, and the MCP transport dies on EOF with "MCP tool load failed ... Transport closed". Verified: fresh omp session in this directory answered from the tools - coder_get_authenticated_user returned loca, workspace ws1 running.
159 lines
11 KiB
Markdown
159 lines
11 KiB
Markdown
---
|
|
title: "🗂️ Paths, ports, env vars"
|
|
description: "Exhaustive lookup of every file, container, volume, port and CODER_* environment variable in the code.loca.zone deployment, plus the deployed docker-compose.yml verbatim."
|
|
source: "verified deployment on the loca operator host, 2026-08-19"
|
|
reviewed: 2026-08-19
|
|
---
|
|
|
|
# 🗂️ Paths, ports, env vars
|
|
|
|
## 📁 Project files
|
|
|
|
| Path | Purpose | Owner and notes |
|
|
| --- | --- | --- |
|
|
| `/home/loca/dev/coder/` | stack root; compose project name `coder` derives from this directory name | `loca`; run every compose command from here |
|
|
| `/home/loca/dev/coder/docker-compose.yml` | the whole deployment definition: images, pins, ports, env, volumes, memory ceilings | `loca`; edit here, never with `docker run` flags |
|
|
| `/home/loca/dev/coder/.env` | supplies `CODER_PG_PASSWORD` to compose interpolation | `loca`, mode 600, git-ignored; contents are never read into or quoted by any document |
|
|
| `/home/loca/dev/coder/.mcp-token` | dedicated Coder token named `omp-mcp`, consumed by `scripts/coder-mcp.sh` so the omp harness can call Coder's MCP tools | `loca`, mode 600, git-ignored; 168h maximum lifetime, rotate with `scripts/coder-token-rotate.sh` |
|
|
| `/home/loca/dev/coder/.omp/mcp.json` | project-scoped MCP registration; mounts the `coder` server only when omp runs in this directory | `loca`; holds no secret, the token is read by the wrapper |
|
|
| `/home/loca/dev/coder/scripts/coder-mcp.sh` | wraps `coder exp mcp server` with `docker exec --env-file` credential passing and a 26-tool allow-list that excludes template mutation | `loca`, mode 755; must run the docker exec in the foreground or the MCP transport dies on EOF |
|
|
| `/home/loca/dev/coder/scripts/coder-token-rotate.sh` | mints a replacement `omp-mcp` token, verifies it against the API, then revokes the previous ones | `loca`, mode 755; run weekly |
|
|
| `/home/loca/dev/coder/scripts/backup-stacks.sh` | nightly backup driver for both stacks, invoked by `stacks-backup.service` | `loca`, mode 755; runs as root from the timer, so no `sudo` inside |
|
|
| `/home/loca/dev/coder/.gitignore` | ignores `.env`, `.admin-credentials`, `.mcp-token`, `q5vault/.obsidian/`, and terraform working state under `templates/` | `loca`; keeps secrets, editor state and tfstate out of version control while the provider lockfile stays committed |
|
|
| `/home/loca/dev/coder/config/runtime-version.txt` | plain-text pin, currently `v2.35.3`; the human-readable twin of the image tag | `loca`; changes in the same commit as the tag |
|
|
| `/home/loca/dev/coder/deploy/code.nginx.conf` | source of truth for the four nginx server blocks | `loca`; the installed site file is a copy, so edit here first |
|
|
| `/home/loca/dev/coder/templates/docker/main.tf` | pre-validated docker workspace template; one `coder_app` at `subdomain = false`, persistent `/home/coder` volume, no docker socket in the workspace | `loca`; pushed by the operator with `coder templates push` |
|
|
| `/home/loca/dev/coder/templates/docker/.terraform.lock.hcl` | provider hash lock: `coder/coder` 2.18.0, `kreuzwerker/docker` 4.5.0 | `loca`; generated by the provisioner's own terraform (linux_amd64), committed on purpose |
|
|
| `/home/loca/dev/coder/templates/docker/README.md` | operator runbook for the template, incl. cited upstream sources and the validation performed | `loca` |
|
|
| `/home/loca/dev/coder/q5vault/` | this wiki, the real content vault | `loca`; the wiki grid symlinks into it |
|
|
| `/home/loca/dev/coder/quartz.config.coder.yaml` | Quartz configuration for this wiki: `pageTitle` `Coder SSOT`, `baseUrl` `wiki.code.loca.zone`, footer links to https://code.loca.zone and https://loca.zone | `loca`; derived from the pwiz config with the same plugin set |
|
|
|
|
## 🌐 Nginx, TLS and the wiki grid
|
|
|
|
| Path | Purpose | Owner and notes |
|
|
| --- | --- | --- |
|
|
| `/etc/nginx/sites-available/code.loca.zone` | installed copy of `deploy/code.nginx.conf`; four server blocks (app 443, wiki 443, app 80, wiki 80) | root; `sudo -n nginx -t` before any reload |
|
|
| `/etc/nginx/sites-enabled/code.loca.zone` | symlink activating the site | root |
|
|
| `/etc/letsencrypt/live/code.loca.zone/` | certificate lineage named `code.loca.zone`, covering `code.loca.zone` and `wiki.code.loca.zone`, expiring 2026-11-17; both vhosts read `fullchain.pem` and `privkey.pem` here | root, certbot-managed; HTTP-01 via `/var/www/html` webroot |
|
|
| `/home/loca/dev/wikis/code/content` | symlink to `/home/loca/dev/coder/q5vault` | `loca`; the vault stays in the project, the grid only points at it |
|
|
| `/home/loca/dev/wikis/code/quartz.config.yaml` | symlink to `/home/loca/dev/coder/quartz.config.coder.yaml` | `loca` |
|
|
| `/home/loca/dev/wikis/code/current` | symlink to the published `dist-*` directory and the nginx root for the wiki vhost | produced by `cd /home/loca/dev/wikis && ./build.sh code`; absent until the first build runs |
|
|
| `/home/loca/dev/services/authelia/config/configuration.yml` | shared Authelia access rules; `default_policy: bypass`, and deliberately no rule for `code.loca.zone` or `wiki.code.loca.zone` | root-adjacent shared service; do not add a rule for this stack, see [[operations/auth-model]] |
|
|
|
|
## 📦 Containers, volumes, network
|
|
|
|
| Object | Definition | Notes |
|
|
| --- | --- | --- |
|
|
| container `coder` | service `coder`, image `ghcr.io/coder/coder:v2.35.3`, `restart: unless-stopped`, `mem_limit: 8g` | the control plane; also runs the built-in provisioner |
|
|
| container `coder-database` | service `database`, image `postgres:17`, `restart: unless-stopped`, `mem_limit: 2g` | healthcheck `pg_isready -U coder -d coder`, interval 5s, timeout 5s, 12 retries; publishes no host port |
|
|
| volume `coder_pgdata` | mounted at `/var/lib/postgresql/data` in `coder-database` | all Coder state: users, templates, template versions, workspaces, Terraform state; survives `docker compose down` |
|
|
| volume `coder_home` | mounted at `/home/coder` in `coder` | server-side cache and config root |
|
|
| bind mount `/var/run/docker.sock` | host socket into `coder` at the same path | required by the built-in provisioner; never mounted into a workspace |
|
|
| `group_add: "105"` | host gid of the docker group, added to the `coder` container user | obtained with `getent group docker \| cut -d: -f3`, not guessed |
|
|
| compose network | default project network; `coder` reaches postgres at hostname `database` port 5432 | the only route to the database |
|
|
|
|
Named volumes are declared as `coder_pgdata` and `coder_home`; compose creates named volumes prefixed with the project name, so the docker-side names are `coder_coder_pgdata` and `coder_coder_home`. Address them through `sudo -n docker compose` from the project root.
|
|
|
|
## 🔌 Ports
|
|
|
|
| Port | Bound where | Meaning |
|
|
| --- | --- | --- |
|
|
| `52226` | host `127.0.0.1` only, published as `"127.0.0.1:52226:7080"` | the sole host-visible socket for this stack; nginx proxies to it |
|
|
| `7080` | inside the `coder` container, `0.0.0.0:7080` | the server's own listener; all interfaces of its own namespace, unreachable from outside the publish spec |
|
|
| `5432` | inside the `coder-database` container | reachable only over the compose network |
|
|
| `443` and `80` | host, nginx | `code.loca.zone` and `wiki.code.loca.zone` vhosts |
|
|
|
|
Registry context: `52222` pwiz, `52223` reserved for pwiz, `52224` cdsr (code-server), `52226` code (Coder). All loca.zone application backends bind loopback inside `51000-53000`. Ad-hoc dev servers started from an IDE or workspace terminal must avoid every registered port; check with `ss -ltn` first.
|
|
|
|
## ⚙️ Environment variables actually set
|
|
|
|
| Variable | Value | Why |
|
|
| --- | --- | --- |
|
|
| `CODER_HTTP_ADDRESS` | `0.0.0.0:7080` | binds all interfaces of the container namespace so the host publish can reach it; a container-local loopback bind would be unreachable |
|
|
| `CODER_ACCESS_URL` | `https://code.loca.zone` | the external URL that users and workspace agents use; upstream requires it whenever the built-in tunnel is not used, and it must not be localhost |
|
|
| `CODER_PG_CONNECTION_URL` | `postgresql://coder:${CODER_PG_PASSWORD}@database:5432/coder?sslmode=disable` | external-database mode against the private postgres container; `sslmode=disable` is acceptable only because the traffic never leaves the compose network |
|
|
| `CODER_TELEMETRY_ENABLE` | `"false"` | no usage data leaves the host; confirmed by `telemetry` `false` in `/api/v2/buildinfo` |
|
|
| `POSTGRES_USER` | `coder` | database role |
|
|
| `POSTGRES_PASSWORD` | `${CODER_PG_PASSWORD}` | interpolated from `.env`; the literal value is never documented |
|
|
| `POSTGRES_DB` | `coder` | database name |
|
|
|
|
Deliberately unset: `CODER_WILDCARD_ACCESS_URL` (see [[reference/limits]]), `CODER_TLS_ENABLE` and friends (nginx terminates TLS), every `CODER_OIDC_*` variable (see [[operations/auth-model]]), and `CODER_DISABLE_PATH_APPS` (path-based apps are the only app routing available without a wildcard).
|
|
|
|
## 📄 Deployed docker-compose.yml
|
|
|
|
The password appears only as the `${CODER_PG_PASSWORD}` placeholder, exactly as it does on disk.
|
|
|
|
```yaml
|
|
services:
|
|
database:
|
|
image: postgres:17
|
|
container_name: coder-database
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_USER: coder
|
|
POSTGRES_PASSWORD: ${CODER_PG_PASSWORD}
|
|
POSTGRES_DB: coder
|
|
volumes:
|
|
- coder_pgdata:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U coder -d coder"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 12
|
|
mem_limit: 2g
|
|
|
|
coder:
|
|
image: ghcr.io/coder/coder:v2.35.3
|
|
container_name: coder
|
|
restart: unless-stopped
|
|
depends_on:
|
|
database:
|
|
condition: service_healthy
|
|
ports:
|
|
- "127.0.0.1:52226:7080"
|
|
environment:
|
|
CODER_HTTP_ADDRESS: 0.0.0.0:7080
|
|
CODER_ACCESS_URL: https://code.loca.zone
|
|
CODER_PG_CONNECTION_URL: postgresql://coder:${CODER_PG_PASSWORD}@database:5432/coder?sslmode=disable
|
|
CODER_TELEMETRY_ENABLE: "false"
|
|
# No CODER_WILDCARD_ACCESS_URL. Nested names such as foo.code.loca.zone
|
|
# DO resolve via the *.loca.zone wildcard; the actual blocker is TLS
|
|
# issuance: this lineage covers only code.loca.zone and
|
|
# wiki.code.loca.zone, and a *.code.loca.zone certificate needs DNS-01
|
|
# validation with DNS-provider credentials that are not configured here.
|
|
# Path-based workspace apps and `coder port-forward` are used instead.
|
|
group_add:
|
|
# host gid of /var/run/docker.sock; required so the built-in provisioner
|
|
# can build docker-template workspaces.
|
|
- "105"
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
- coder_home:/home/coder
|
|
mem_limit: 8g
|
|
|
|
volumes:
|
|
coder_pgdata:
|
|
coder_home:
|
|
```
|
|
|
|
## 🔗 Endpoints worth bookmarking
|
|
|
|
| URL | Purpose |
|
|
| --- | --- |
|
|
| `http://127.0.0.1:52226/healthz` | loopback liveness, returns `OK` |
|
|
| `http://127.0.0.1:52226/api/v2/buildinfo` | running version, advertised dashboard URL, telemetry flag |
|
|
| `http://127.0.0.1:52226/api/v2/users/first` | `200`, the initial Owner exists; `404` only on a never-bootstrapped deployment |
|
|
| `https://code.loca.zone/` | dashboard, no auth redirect |
|
|
| `https://code.loca.zone/@<user>/<workspace>.<agent>/apps/<slug>/` | path-based workspace app |
|
|
| `https://code.loca.zone/@<user>/<workspace>.<agent>/terminal` | web terminal |
|
|
| `https://wiki.code.loca.zone/` | this wiki, public |
|
|
|
|
## 📚 Sources
|
|
|
|
- https://coder.com/docs/admin/setup
|
|
- https://coder.com/docs/install/docker
|
|
- https://coder.com/docs/reference/api/users
|
|
- https://docs.docker.com/reference/compose-file/volumes/
|
|
- 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
|