# Local operation stack: hintel + its own Postgres, entirely self-contained. # # This is deliberately a separate stack from any ad-hoc dev/test Postgres # container (e.g. the "hintel-pg" container used during development, on host # port 5433, or the native Postgres already running on the host on port # 5432) — this one owns its own named volume and its own Docker network, so # it can never be confused with, or accidentally point at, dev/test data. services: db: image: pgvector/pgvector:pg16 restart: unless-stopped environment: POSTGRES_USER: hintel POSTGRES_PASSWORD: hintel POSTGRES_DB: hintel volumes: - hintel_pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U hintel -d hintel"] interval: 5s timeout: 3s retries: 10 # Deliberately no "ports:" entry here. Publishing a port means exposing # it on the host's network (e.g. localhost:5433) so tools outside Docker # can reach it. Nothing outside this stack needs to talk to this # database directly — only the "app" service does, and it can already # reach "db" on port 5432 over the private network Compose creates for # this stack. Not publishing a port also guarantees this database can # never collide with the host's own Postgres (port 5432) or the # "hintel-pg" dev container (port 5433). # One-shot job: apply database migrations, then exit. `hintel serve` does # not run migrations itself (it assumes a ready schema), and on a brand # new named volume the database starts out completely empty. Running # migrations as their own short-lived service — rather than folding # "migrate then serve" into one entrypoint script inside the app # container — keeps the two concerns separate and easy to reason about: # this service's job is "did the schema apply, yes or no", visible as its # own line in `docker compose ps` and its own log stream, and it naturally # re-runs (and no-ops, since goose is idempotent) every time the stack # comes up. migrate: build: . depends_on: db: condition: service_healthy env_file: - .env environment: # Overrides whatever HINTEL_DATABASE_URL is set to in .env (which # points at the separate dev database on host port 5433) so this # container talks to its own "db" service instead. HINTEL_DATABASE_URL: postgres://hintel:hintel@db:5432/hintel?sslmode=disable # ENTRYPOINT is already ["hintel"] (set in the Dockerfile), so this is # just the argument appended to it — the resulting process is # "hintel migrate", not "hintel hintel migrate". command: ["migrate"] app: build: . restart: unless-stopped depends_on: db: condition: service_healthy migrate: # Only start the daemon once the migrate service has actually run # to completion successfully — not merely started. Without this, # `hintel serve` could start against a schema-less database on a # fresh volume and fail immediately. condition: service_completed_successfully env_file: # Compose reads .env directly to populate the container's environment # (this is Compose's own env_file mechanism, unrelated to the # application's internal godotenv fallback in cmd/hintel/main.go, # which only matters for bare-metal `go run`/binary use and does # nothing here since the environment is already populated). - .env environment: # env_file values are applied first and "environment:" is applied on # top, so this line always wins over whatever HINTEL_DATABASE_URL is # in .env. That .env value points at the dev database on host port # 5433 (used by `go run`/tests on the host); this stack must never # touch that database, so it's overridden here to point at this # stack's own "db" service instead. HINTEL_DATABASE_URL: postgres://hintel:hintel@db:5432/hintel?sslmode=disable volumes: hintel_pgdata: