infoxtractor/docs/deployment.md
Dirk Riemann 6d1bc720b4
All checks were successful
tests / test (push) Successful in 1m9s
tests / test (pull_request) Successful in 1m10s
feat(deploy): setup_server.sh + deployment runbook
- scripts/setup_server.sh: idempotent one-shot. Creates bare repo,
  post-receive hook (which rebuilds docker compose + gates on /healthz),
  infoxtractor Postgres role + DB on the shared postgis container, .env
  (0600) from .env.example with the password substituted in, verifies
  gpt-oss:20b is pulled.
- docs/deployment.md: topology, one-time setup command, normal deploy
  workflow, rollback-via-revert pattern (never force-push main),
  operational checklists for the common /healthz degraded states.
- First deploy section reserved; filled in after Task 5.3 runs.

Task 5.2 of MVP plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 12:16:58 +02:00

4.5 KiB

Deployment

On-prem deploy to 192.168.68.42. Push-to-deploy via a bare git repo + post-receive hook that rebuilds the Docker Compose stack. Pattern mirrors mammon and unified_messaging.

Topology

Mac (dev)
  │  git push server main
  ▼
192.168.68.42:/home/server/Public/infoxtractor/repos.git   (bare)
  │  post-receive → GIT_WORK_TREE=/…/app git checkout -f main
  │                 docker compose up -d --build
  │                 curl /healthz (60 s gate)
  ▼
Docker container `infoxtractor` (port 8994)
  ├─ host.docker.internal:11434  →  Ollama (gpt-oss:20b)
  └─ host.docker.internal:5431   →  postgis (database `infoxtractor`)

One-time server setup

Run once from the Mac. Idempotent.

export IX_POSTGRES_PASSWORD=<generate-a-strong-one>
./scripts/setup_server.sh

The script:

  1. Creates /home/server/Public/infoxtractor/repos.git (bare) + /home/server/Public/infoxtractor/app/ (worktree).
  2. Installs the post-receive hook (see scripts/setup_server.sh for the template).
  3. Creates the infoxtractor Postgres role + database on the shared postgis container.
  4. Writes /home/server/Public/infoxtractor/app/.env (mode 0600) from .env.example with the password substituted in.
  5. Verifies gpt-oss:20b is pulled in Ollama.
  6. Prints a hint to open UFW for port 8994 on the LAN subnet if it's missing.

After the script finishes, add the deploy remote to the local repo:

git remote add server ssh://server@192.168.68.42/home/server/Public/infoxtractor/repos.git

Normal deploy workflow

# after merging a feat branch into main
git push server main

# tail the server's deploy log
ssh server@192.168.68.42 "tail -f /tmp/infoxtractor-deploy.log"

# healthz gate (the post-receive hook also waits up to 60 s for this)
curl http://192.168.68.42:8994/healthz

# end-to-end smoke — this IS the real acceptance test
python scripts/e2e_smoke.py

If the post-receive hook exits non-zero (healthz never reaches 200), the deploy is considered failed. The previous container keeps running (the hook swaps via docker compose up -d --build, which first builds the new image and only swaps if the build succeeds; if the new container fails /healthz, it's still up but broken). Investigate with docker compose logs --tail 200 in ${APP_DIR} and either fix forward or revert (see below).

Rollback

Never force-push main. Rollbacks happen as forward commits via git revert:

git revert HEAD     # creates a revert commit for the last change
git push forgejo main
git push server main

First deploy

(fill in after running — timestamps, commit sha, e2e_smoke output)

  • Date: TBD
  • Commit: TBD
  • /healthz first-ok time: TBD
  • e2e_smoke.py status: TBD
  • Notes:

Operational checklists

After ollama pull on the host

The IX_DEFAULT_MODEL env var on the server's .env must match something in ollama list. Changing the default means:

  1. Edit /home/server/Public/infoxtractor/app/.envIX_DEFAULT_MODEL=<new>.
  2. docker compose --project-directory /home/server/Public/infoxtractor/app restart.
  3. curl http://192.168.68.42:8994/healthz → confirm ollama: ok.

If /healthz shows ollama: degraded

gpt-oss:20b (or the configured default) is not pulled. On the host:

ssh server@192.168.68.42 "docker exec ollama ollama pull gpt-oss:20b"

If /healthz shows ocr: fail

Surya couldn't initialize (model missing, CUDA unavailable, OOM). First run can be slow — models download on first call. Check container logs:

ssh server@192.168.68.42 "docker logs infoxtractor --tail 200"

If the container fails to start

ssh server@192.168.68.42 "tail -100 /tmp/infoxtractor-deploy.log"
ssh server@192.168.68.42 "docker compose -f /home/server/Public/infoxtractor/app/docker-compose.yml logs --tail 200"

Monitoring

  • Monitoring dashboard auto-discovers via the infrastructure.web_url label on the container: http://192.168.68.42:8001 → "infoxtractor" card.
  • Backup opt-in via backup.enable=true + backup.type=postgres + backup.name=infoxtractor labels. The daily backup script picks up the infoxtractor Postgres database automatically.

Ports

Port Direction Source Service
8994/tcp ALLOW 192.168.68.0/24 ix REST + healthz (LAN only; not publicly exposed)

No VPS Caddy entry; no infrastructure.docs_url label — this is an internal service.