Self-hosting
Izri is open-core under AGPL-3.0. The four signals, the CLI, the MCP server, and the GitHub Action all run against a self-hosted instance with no license key.
You need the runner
Start here, because it is the failure everyone hits.
Izri is five services, not two. The API and web app alone look like a working stack — the dashboard loads, you can create a project, you can trigger a run. But triggering a run only enqueues a Redis job. With no runner draining that queue, the run sits at PENDING forever and nothing ever tells you why.
The API does warn: a runner-presence watchdog logs a WARN once the queue stays non-empty for more than 60 seconds. If a run is stuck, check the API logs first.
Services
| Service | Port | Purpose |
|---|---|---|
web |
3000 | React Router SSR dashboard |
api |
4000 | Hono server, tRPC, webhooks |
ai-service |
8000 | Python FastAPI — analysis and generation |
runner |
— | Drains the Redis queue and executes tests |
postgres |
5433 | Primary datastore |
redis |
6380 | Job queue |
Postgres is on 5433 and Redis on 6380, not their defaults, so a self-hosted instance doesn't collide with a database already running on the host.
Bring it up
git clone https://github.com/legendify-dev/izri.git
cd izri
cp .env.shared.example .env.shared
# edit .env.shared — see below
pnpm install
pnpm env:generate
docker compose up -dpnpm env:generate is not optional. It writes the per-app .env files from .env.shared; without it auth, the database layer, and tRPC fail in confusing ways rather than reporting a missing variable.
Environment
The variables that actually block a first run:
| Variable | Notes |
|---|---|
DATABASE_URL |
Postgres connection string. |
REDIS_URL |
Queue connection. |
API_URL / APP_URL |
Must match where the services are actually reachable. |
BETTER_AUTH_SECRET |
Change it. The example value is a placeholder. |
JWT_SECRET |
Change it too. |
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET |
OAuth login. |
GITHUB_APP_ID / GITHUB_APP_PRIVATE_KEY / GITHUB_APP_WEBHOOK_SECRET |
Required for PR checks and the sticky comment. |
AI_PROVIDER |
openrouter, openai, anthropic, or mock. |
The OAuth callback will bite you
The GitHub OAuth callback is derived from your configured app URL. If the URL your browser uses and the URL Izri is configured with disagree — a port mismatch is the usual cause — login fails as a silent redirect loop with no error message. Make them match exactly.
AI provider
Set AI_PROVIDER explicitly. mock runs the deterministic layers with no external calls and no cost, which is the right choice for evaluating the stack. The semantic scope layer and LLM insights need a real provider.
Point your tooling at it
Everything takes the same override:
export IZRI_API_URL=https://izri.internal.example.com- uses: legendify-dev/izri/apps/github-action@v1
with:
api-token: ${{ secrets.IZRI_API_TOKEN }}
api-url: https://izri.internal.example.comDatabase migrations
Schema changes ship as Drizzle migrations. After pulling a new version:
pnpm db:migrateMigrations are immutable once released — never hand-edit one.
What self-hosting does not include
The visual signal's semantic diff is a paid capability. Self-hosted instances get the three deterministic signals plus visual capture and pixel diff. See plans and quotas.
Related
- Troubleshooting — stuck runs, 401s, login loops.
- GitHub Action — pointing CI at your instance.
Reading this with an agent? /docs/install/self-hosting.md serves the raw markdown.
Edit this page on GitHub →All docs