6.9 KiB
6.9 KiB
title, description, source, reviewed
| title | description | source | reviewed |
|---|---|---|---|
| 🗺️ Runtime topology | Exact wiring of the Coder stack: nginx 443 to loopback 52226 to container coder, private postgres over the compose network, the docker.sock provisioner path, and the separate agent and CLI path. | verified deployment on the loca operator host, 2026-08-19 | 2026-08-19 |
🗺️ Runtime topology
🧩 Diagram
flowchart TD
browser["Browser: dashboard, web terminal, path-based apps"]
cli["coder CLI and workspace agents"]
nginx["nginx 443 vhost code.loca.zone, TLS lineage code.loca.zone, no auth_request"]
wikivhost["nginx 443 vhost wiki.code.loca.zone, static root wikis/code/current"]
publish["host publish 127.0.0.1:52226 to container 7080"]
coder["container coder, ghcr.io/coder/coder:v2.35.3, listens 0.0.0.0:7080, mem_limit 8g"]
db["container coder-database, postgres:17, volume coder_pgdata, mem_limit 2g"]
sock["host /var/run/docker.sock, gid 105, bind-mounted into coder"]
ws["workspace containers built by the built-in provisioner"]
browser -->|https 443| nginx
cli -->|https 443 with Coder-Session-Token| nginx
browser -->|https 443| wikivhost
nginx -->|proxy_pass, http 1.1, Upgrade, read timeout 86400| publish
publish --> coder
coder -->|compose network, database port 5432, sslmode disable| db
coder -->|terraform docker provider| sock
sock --> ws
ws -->|agent websockets back to CODER_ACCESS_URL| nginx
🌐 Browser and CLI path into the server
- Browser or
coderCLI tohttps://code.loca.zone- nginx server block, from
/home/loca/dev/coder/deploy/code.nginx.conflisten 443 ssl,server_name code.loca.zoneinclude snippets/security-headers-app.confclient_max_body_size 128mso template tarball pushes are not truncated- no
auth_request, no Authelia include: deliberate, see operations/auth-model location /proxy_pass http://127.0.0.1:52226proxy_http_version 1.1proxy_set_header Upgrade $http_upgradeproxy_set_header Connection $connection_upgradeproxy_set_header Host $host,X-Real-IP $remote_addr,X-Forwarded-For $proxy_add_x_forwarded_for,X-Forwarded-Proto $schemeproxy_buffering offso the web terminal and log streams are not held in nginx buffersproxy_read_timeout 86400because the dashboard, web terminal and agent websockets are long-lived; upstream requires that any reverse proxy between the control plane and clients or agents supports websockets
- TLS material
/etc/letsencrypt/live/code.loca.zone/fullchain.pemandprivkey.pem- one lineage named
code.loca.zonecoveringcode.loca.zoneandwiki.code.loca.zone, expiring 2026-11-17
- one lineage named
- port 80 blocks exist for both hostnames:
/.well-known/acme-challenge/from/var/www/html, everything else301to https
- nginx server block, from
- Wiki vhost
wiki.code.loca.zoneinclude snippets/security-headers.conf, staticroot /home/loca/dev/wikis/code/current,index index.html- immutable one-year cache for hashed assets,
try_files $uri $uri.html $uri/ =404for pages - shares the same certificate lineage; it is public by omission because Authelia
default_policyisbypass
📦 Containers, volumes and the compose network
- compose project root
/home/loca/dev/coder, so the compose project name iscoder - service
coder, container namecoder- image
ghcr.io/coder/coder:v2.35.3,restart: unless-stopped,mem_limit: 8g depends_onservicedatabasewithcondition: service_healthy, so the server never starts against an unready database- volumes:
/var/run/docker.sock:/var/run/docker.sockand named volumecoder_homeat/home/coder group_add: "105", the host gid of the docker socket, read at write time withgetent group docker | cut -d: -f3
- image
- service
database, container namecoder-database- image
postgres:17,restart: unless-stopped,mem_limit: 2g - named volume
coder_pgdataat/var/lib/postgresql/data - healthcheck
pg_isready -U coder -d coderevery 5s, timeout 5s, 12 retries - publishes no host port at all; the only route to it is the compose network hostname
databaseon port 5432
- image
- named volumes as declared in
docker-compose.yml:coder_pgdata,coder_home- compose creates named volumes prefixed with the project name, so on the docker side they appear as
coder_coder_pgdataandcoder_coder_home - always address them through
sudo -n docker composefrom/home/loca/dev/coderrather than by raw docker volume name
- compose creates named volumes prefixed with the project name, so on the docker side they appear as
- the compose network is the only path between the two containers; nothing else on the host can reach postgres
🔌 Why 0.0.0.0:7080 inside and loopback outside
CODER_HTTP_ADDRESS=0.0.0.0:7080binds all interfaces of the container's own network namespace- a container-local
127.0.0.1bind would be unreachable from the host publish path, so the server would appear dead even though it is healthy
- a container-local
- the publish spec
"127.0.0.1:52226:7080"is the actual exposure boundary- docker opens the port on the host loopback only, so the sole public route is nginx
52226sits inside the loca.zone51000-53000application range and does not collide with52222pwiz,52223reserved for pwiz, or52224cdsr
- consequence:
CODER_TLS_ENABLEis never set; nginx terminates TLS, andCODER_ACCESS_URL=https://code.loca.zonetells Coder the external scheme and hostname to advertise to browsers and agents
🏗️ Provisioner path to workspace containers
- the built-in provisioner runs inside the
codercontainer; there is no external provisioner daemon - template Terraform uses the
kreuzwerker/dockerprovider, which talks to the bind-mounted/var/run/docker.sock group_add: "105"gives the non-root container user access to that socket; upstream's docker install guide sets this fromgetent group docker | cut -d: -f3- workspace containers are therefore siblings on the host docker daemon, not children of the
codercontainer - the socket is never passed into a workspace; the risk model and its containment list live in operations/workspaces
🤖 Agent and CLI plane
- a workspace agent dials
CODER_ACCESS_URL, that ishttps://code.loca.zone, and holds open/api/v2websockets for the duration of the workspace - the
coderCLI authenticates with theCoder-Session-Tokenheader, never with a cookie - upstream requires that all clients and agents can reach the server over HTTP or HTTPS and that any proxy in between supports websockets;
proxy_read_timeout 86400plusproxy_buffering offsatisfy that here - relayed connections use the server's built-in DERP service over the same 443 vhost, so anything that interrupts unauthenticated-by-cookie requests interrupts workspace connectivity too