# Coder — self-hosted workspace manager (PILOT). Web dashboard at :7080 # that stamps out per-project dev containers from Terraform templates; # each workspace gets browser code-server with the Claude Code extension # pre-installed. Evaluating against the standalone code-server stack # (compose/code-server.yml, kept as-is during the pilot) — verdict # criteria in compose/coder/README.md. # # The server container holds the host docker socket and spawns workspace # containers as siblings (same pattern + same security posture as # OpenHands: fine Tailscale-only, never expose further). group_add needs # the socket's host GID — host-specific, so it comes from the sibling # .env, which pyinfra generates on the box on first deploy along with a # random one-time Postgres password. # # Workspaces don't publish host ports — code-server and terminals are # reached through the dashboard's tunnel. No port bookkeeping per # project. services: coder: # Pin and bump deliberately — Coder releases weekly and the workspace # provisioner/agent protocol moves with it. Verify at # https://github.com/coder/coder/releases. image: ghcr.io/coder/coder:v2.33.8 container_name: coder restart: unless-stopped ports: - "7080:7080" environment: CODER_HTTP_ADDRESS: "0.0.0.0:7080" # The URL clients are told to use — tailnet MagicDNS name, same # convention as every other service tile. CODER_ACCESS_URL: "${CODER_ACCESS_URL}" CODER_PG_CONNECTION_URL: "postgresql://coder:${POSTGRES_PASSWORD}@database/coder?sslmode=disable" group_add: # GID of /var/run/docker.sock — generated into .env by deploy.py. - "${DOCKER_GROUP_ID}" volumes: - /var/run/docker.sock:/var/run/docker.sock # Repo-shipped Terraform templates (source of truth: # pyinfra/framework/compose/coder/templates/). Push after changes: # docker compose exec coder coder templates push code-server \ # --directory /templates/code-server --yes - /srv/docker/coder/templates:/templates:ro depends_on: database: condition: service_healthy database: image: postgres:17 container_name: coder-db restart: unless-stopped environment: POSTGRES_USER: coder POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: coder volumes: # Entrypoint starts as root and chowns this to the postgres uid; # deploy.py just creates the mount point. - /srv/docker/coder/postgres:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U coder -d coder"] interval: 5s timeout: 5s retries: 5