Postil

Self-hosted

The same stack we run hosted: Postgres, the web app, and the worker. Free forever, no seat limit. Budget under 15 minutes from clone to a reviewed test PR.

Quickstart

git clone https://github.com/postil-dev/postil
cd postil
cp .env.example .env
# fill in: GitHub App credentials, webhook secret, a sealing key,
#          a session secret, and your LLM key. Each line in
#          .env.example explains its variable.

docker compose up -d
docker compose exec web bun run db:migrate

Both web and worker validate their configuration at boot. A missing or malformed variable stops the process with the variable name, what it is for, and an example value — not a stack trace from the first request that happened to need it.

Pointing it at a model

OpenRouter (default)

POSTIL_API_BASE=https://openrouter.ai/api/v1
POSTIL_API_KEY=sk-or-v1-...
REVIEW_MODEL=deepseek/deepseek-v4-pro
REVIEW_MODEL_CASCADE=qwen/qwen3-coder

Azure OpenAI

POSTIL_API_BASE=https://<resource>.openai.azure.com/openai/v1
POSTIL_API_KEY=<azure-api-key>
REVIEW_MODEL=<deployment-name>

Ollama (local, no API key)

POSTIL_API_BASE=http://ollama:11434/v1
POSTIL_API_KEY=ollama        # any non-empty value
REVIEW_MODEL=qwen3-coder:30b

Yes, Ollama actually works. The worker talks plain OpenAI-compatible chat completions, so anything that serves that API — vLLM, LiteLLM, TGI — works the same way.

postil doctor

Before opening a test PR, run the doctor inside the worker container. It checks the API base is reachable, the key is accepted, and the configured model responds:

docker compose exec worker postil doctor

  endpoint  http://ollama:11434/v1 ... ok (142ms)
  auth      key accepted ............ ok
  model     qwen3-coder:30b ......... ok (1.2s first token)

Every failure mode prints the failing layer and a suggested fix. The documented anti-goal: a reviewer that silently falls back to a provider you did not configure.

GitHub App setup

  1. Create a GitHub App on your org with permissions contents: read, pull_requests: write, checks: write, metadata: read, and the pull_request, installation, and installation_repositories events.
  2. Set the webhook URL to https://your-host/api/webhooks/github and generate a webhook secret (GITHUB_WEBHOOK_SECRET).
  3. Download the App private key and set GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY (PEM, base64 accepted).
  4. Install the App on a test repository and open a PR.

Operations

  • /api/health — database ping, suitable for liveness probes.
  • /api/metrics — Prometheus text (queue depth, reviews by status, silence rate, watchdog kills), bearer-protected by METRICS_TOKEN.
  • The worker's watchdog fails any review running longer than 10 minutes and completes its check-runs as failed, so a stuck review can never hold a PR hostage as eternally in-progress.
  • The CLI binary is baked into the worker image at a pinned commit; upgrading the reviewer is an image upgrade, not a runtime download.