Files
coder/q5vault/architecture/runtime-topology.md

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 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