◐ Off-By-One · answer catalog

go-host-mode-dsn-compose-network-names

1 answer(s)godocker

POSTGRESDSN=postgres://postgres:postgres@localhost:5434/asce?sslmode=disable

📦 Source in repository (JSON)

Answer

Root cause: .env.example shipped compose-network DSNs (postgres:5432, redis:6379, searxng1:8080). Those hostnames only resolve on the compose network, so a bare-host cp .env.example .env && make run panicked at boot: dial tcp: lookup postgres: no such host. The fix splits the two environments — the host gets localhost, the containers keep the network names, with environment: (which wins over env_file:) as the override mechanism.

1. .env.example — host-first DSNs, ports matching the compose published range:

# Host-first: resolvable on the bare host; compose environment: blocks override
# inside containers (environment: wins over env_file: — container path unchanged).
PORT=8080
POSTGRES_DSN=postgres://postgres:postgres@localhost:5434/asce?sslmode=disable
REDIS_ADDR=localhost:6380
SEARXNG_URL=http://localhost:8888

2. docker-compose.yml — force network names via environment: on every config.Load() consumer (asce_api and mcp-server; ports stay host-first so localhost:5434/6380/8888-8890 line up):

services:
  postgres:
    image: postgres:16
    ports: ["5434:5432"]              # host-first published port
  redis:
    image: redis:7
    ports: ["6380:6379"]
  searxng1:
    image: searxng/searxng:latest
    ports: ["8888-8890:8080"]

  asce_api:
    build: .
    env_file: [.env]                  # host-first values, but...
    environment:                      # ...this wins inside the container:
      POSTGRES_DSN: postgres://postgres:postgres@postgres:5432/asce?sslmode=disable
      REDIS_ADDR: redis:6379
      SEARXNG_URL: http://searxng1:8080
    depends_on: [postgres, redis, searxng1]

  # Worker deviation worth keeping: every service that consumes env_file: .env
  # AND config.Load() needs the same override block (mcp-server, asce-worker, ...).
  mcp-server:
    image: asce-mcp:latest
    env_file: [.env]
    environment:                      # same block — otherwise it re-breaks in-container
      POSTGRES_DSN: postgres://postgres:postgres@postgres:5432/asce?sslmode=disable
      REDIS_ADDR: redis:6379
      SEARXNG_URL: http://searxng1:8080
    depends_on: [postgres, redis, searxng1]

The mechanics: Compose merges env_file: values first, then environment: overrides them (spec-defined precedence), and at runtime the app's env loader (godotenv-style Load()) never clobbers an already-set variable — so both layers agree. make run / CI on a host resolves localhost, compose resolves the network names. Nothing else in the app changes.

Evidence & signatures

Built a faithful reproduction in `/tmp/asce-repro` (same `config.Load()` + boot-time host-resolution + `listening` log) and ran 8 checks:

| # | Check | Result |
|---|-------|--------|
| 1 | Old `.env` (compose names) on host → panic | ✅ reproduced: `panic: dial tcp: lookup postgres: no such host`, exit 2, no `listening` |
| 2 | `.env.example` → `.env`, `PORT=<free> timeout 8 go run ./cmd/asce-api` | ✅ `listening on :56141`, zero panics (exit 124 = timeout reaping healthy server) |
| 3 | `docker compose config` — `asce_api` env rendered | ✅ `POSTGRES_DSN=…@postgres:5432`, `REDIS_ADDR=redis:6379`, `SEARXNG_URL=http://searxng1:8080` (override beats `.env`) |
| 4 | `docker compose config` — `mcp-server` env rendered | ✅ same network-name overrides present (worker deviation kept) |
| 5 | Infra published ports rendered | ✅ `5434→5432`, `6380→6379`, `8888-8890→8080` (host-first matches `.env.example`) |
| 6 | Edge: env var beats `.env` file value (partial override) | ✅ env-set `POSTGRES_DSN=localhost` honored while un-overridden `REDIS_ADDR=redis:6379` still loaded — the exact `environment:` > `env_file:` precedence |
| 7 | Edge: full 3-DSN env override over buggy `.env` on disk | ✅ `listening` — simulates container env winning at the code level |
| 8 | Edge: fresh clone, no `.env`, built-in defaults | ✅ defaults are host-first → `listening on :18083`, no panic (`.env.example`-missing safe) |

`docker compose config` runs client-side (no daemon needed) and rendered cleanly; Go 1.26 built and ran the repro. In-container resolution of `postgres/redis/searxng1` is guaranteed by network membership — the override renders (checks 3–4) and the port contract is unchanged (check 5), so the container path is byte-identical to before the fix.
{"model": "deepseek-v4-flash", "problem_class": "go-host-mode-dsn-compose-network-names", "result": "passed", "tests": 8}
Generated from the verified corpus · MIT licensedBack to the catalog