--- title: "πŸ—ΊοΈ Runtime topology" description: "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." source: "verified deployment on the loca operator host, 2026-08-19" reviewed: 2026-08-19 --- # πŸ—ΊοΈ Runtime topology ## 🧩 Diagram ```mermaid 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 `coder` CLI to `https://code.loca.zone` - nginx server block, from `/home/loca/dev/coder/deploy/code.nginx.conf` - `listen 443 ssl`, `server_name code.loca.zone` - `include snippets/security-headers-app.conf` - `client_max_body_size 128m` so 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:52226` - `proxy_http_version 1.1` - `proxy_set_header Upgrade $http_upgrade` - `proxy_set_header Connection $connection_upgrade` - `proxy_set_header Host $host`, `X-Real-IP $remote_addr`, `X-Forwarded-For $proxy_add_x_forwarded_for`, `X-Forwarded-Proto $scheme` - `proxy_buffering off` so the web terminal and log streams are not held in nginx buffers - `proxy_read_timeout 86400` because 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.pem` and `privkey.pem` - one lineage named `code.loca.zone` covering `code.loca.zone` and `wiki.code.loca.zone`, expiring 2026-11-17 - port 80 blocks exist for both hostnames: `/.well-known/acme-challenge/` from `/var/www/html`, everything else `301` to https - Wiki vhost `wiki.code.loca.zone` - `include snippets/security-headers.conf`, static `root /home/loca/dev/wikis/code/current`, `index index.html` - immutable one-year cache for hashed assets, `try_files $uri $uri.html $uri/ =404` for pages - shares the same certificate lineage; it is public by omission because Authelia `default_policy` is `bypass` ## πŸ“¦ Containers, volumes and the compose network - compose project root `/home/loca/dev/coder`, so the compose project name is `coder` - service `coder`, container name `coder` - image `ghcr.io/coder/coder:v2.35.3`, `restart: unless-stopped`, `mem_limit: 8g` - `depends_on` service `database` with `condition: service_healthy`, so the server never starts against an unready database - volumes: `/var/run/docker.sock:/var/run/docker.sock` and named volume `coder_home` at `/home/coder` - `group_add: "105"`, the host gid of the docker socket, read at write time with `getent group docker | cut -d: -f3` - service `database`, container name `coder-database` - image `postgres:17`, `restart: unless-stopped`, `mem_limit: 2g` - named volume `coder_pgdata` at `/var/lib/postgresql/data` - healthcheck `pg_isready -U coder -d coder` every 5s, timeout 5s, 12 retries - publishes no host port at all; the only route to it is the compose network hostname `database` on port 5432 - 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_pgdata` and `coder_coder_home` - always address them through `sudo -n docker compose` from `/home/loca/dev/coder` rather than by raw docker volume name - 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:7080` binds all interfaces of the container's own network namespace - a container-local `127.0.0.1` bind would be unreachable from the host publish path, so the server would appear dead even though it is healthy - 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 - `52226` sits inside the loca.zone `51000-53000` application range and does not collide with `52222` pwiz, `52223` reserved for pwiz, or `52224` cdsr - consequence: `CODER_TLS_ENABLE` is never set; nginx terminates TLS, and `CODER_ACCESS_URL=https://code.loca.zone` tells Coder the external scheme and hostname to advertise to browsers and agents ## πŸ—οΈ Provisioner path to workspace containers - the built-in provisioner runs inside the `coder` container; there is no external provisioner daemon - template Terraform uses the `kreuzwerker/docker` provider, 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 from `getent group docker | cut -d: -f3` - workspace containers are therefore siblings on the host docker daemon, not children of the `coder` container - 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 is `https://code.loca.zone`, and holds open `/api/v2` websockets for the duration of the workspace - the `coder` CLI authenticates with the `Coder-Session-Token` header, 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 86400` plus `proxy_buffering off` satisfy 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 ## πŸ“š Sources - https://coder.com/docs/install/docker - https://coder.com/docs/admin/setup - https://coder.com/docs/admin/networking - https://coder.com/docs/reference/api/authentication - https://docs.docker.com/reference/compose-file/volumes/