Skip to content

Self-hosting

Quickstart

Run the full Metis stack on one machine with Docker Compose, connect it to GitHub, and get your first AI review. Plan on about 20 minutes, most of it spent creating the GitHub App.

  1. Clone the repository

    bash
    git clone https://github.com/KacemMathlouthi/metis.git
    cd metis
  2. Create your GitHub App

    Metis acts through a GitHub App that you own. Follow the GitHub App guide. For a local setup, use http://localhost:5173/auth/callback as the callback URL. You can fill in the webhook URL later, once your tunnel is running (step 6).

    When you're done, you will have:

    • the App ID, Client ID, and a client secret
    • the webhook secret you chose
    • a private key .pem file
  3. Add the private key

    The Compose file mounts the key from a fixed path. Save your .pem as backend/metis-ai-testing.2025-11-21.private-key.pem, or change the volumes entry for both the api and worker services in docker-compose.dev.yml to point at your file.

    The key is git-ignored (*.pem). Never commit it.

  4. Configure the backend

    Start from the example file:

    bash
    cp backend/.env.example backend/.env

    Generate the two application secrets:

    bash
    # JWT_SECRET_KEY: signs session cookies
    openssl rand -hex 32
    
    # ENCRYPTION_KEY: a Fernet key that encrypts GitHub tokens at rest
    openssl rand -base64 32 | tr '+/' '-_'

    Then edit backend/.env. This is the minimum for a working setup. The full list is in the configuration reference.

    backend/.env
    # GitHub App (see the GitHub App guide)
    GITHUB_APP_ID=123456
    GITHUB_CLIENT_ID=Iv23liExample
    GITHUB_CLIENT_SECRET_ID=your-client-secret
    GITHUB_WEBHOOK_SECRET=your-webhook-secret
    
    # LLM: any LiteLLM model string, plus that provider's key
    MODEL_NAME=gpt-4o
    OPENAI_API_KEY=sk-...
    
    # Secrets you generated above
    JWT_SECRET_KEY=...
    ENCRYPTION_KEY=...
    
    # Daytona sandboxes
    DAYTONA_API_KEY=dtn_...
    DAYTONA_API_URL=https://app.daytona.io/api
    DAYTONA_TARGET=us
    
    # Turn tracing off unless you have a LangSmith key
    LANGSMITH_TRACING=false
  5. Start the stack

    bash
    make dev
    # same as: docker compose -f docker-compose.dev.yml up --build

    The API runs database migrations automatically on startup. When everything is healthy:

    ServiceURL
    Dashboardhttp://localhost:5173
    APIhttp://localhost:8000
    API reference (OpenAPI)http://localhost:8000/api/docs
    pgAdminhttp://localhost:5050
  6. Expose the webhook endpoint

    GitHub needs to reach the API over the internet. Start a tunnel to port 8000:

    bash
    ngrok http 8000
    # Forwarding  https://f00d-1234.ngrok-free.app -> http://localhost:8000

    In your GitHub App settings, set the Webhook URL to the tunnel address followed by /webhooks/github, for example https://f00d-1234.ngrok-free.app/webhooks/github. GitHub sends a ping event. Check that the delivery succeeded under Advanced → Recent Deliveries.

  7. Sign in and enable a repository

    • Install your GitHub App on the account or organization that owns your repositories, if you haven't already.
    • Open http://localhost:5173 and click Login with GitHub.
    • Go to Repositories, click Sync from GitHub, then enable the repositories Metis should review.
  8. Open a pull request

    Open a PR on an enabled repository. Within a minute or two, inline findings start appearing on the diff, followed by a summary review and an updated PR description. Follow along with make logs.

Troubleshooting

SymptomLikely cause
Webhook deliveries fail with 401 Invalid Webhook SignatureGITHUB_WEBHOOK_SECRET doesn't match the secret in the GitHub App settings.
Deliveries return ignored: “Repository not enrolled”The repository is not enabled yet. Sync and enable it on the Repositories page.
GitHub shows “redirect_uri is not associated with this application”The app's callback URL must be exactly <FRONTEND_URL>/auth/callback.
The API exits on startup with a validation errorA required variable is missing from backend/.env. The error names it.
Reviews start, then failCheck the worker logs. The usual causes are an invalid LLM or Daytona key, or a MODEL_NAME that doesn't support tool calling.

Running without Docker

For development on Metis itself, run the infrastructure in Docker and the app processes natively. You need Python 3.10+, uv, Node.js 20+, and pnpm.

bash
make infra-up       # PostgreSQL, Redis and pgAdmin only
make setup          # uv sync (backend) + pnpm install (frontend)
make migrate        # alembic upgrade head

# then, each in its own terminal:
make backend-dev    # API on http://localhost:8000
make worker-dev     # Celery worker
make frontend-dev   # dashboard on http://localhost:5173

In this mode, the values in backend/.env are used as-is. The defaults in .env.example already point at localhost. Set GITHUB_SECRET_KEY_PATH to your key's path relative to backend/.

Going to production

docker-compose.dev.yml is meant for development: it runs the dashboard dev server and uses default database passwords. For a long-lived deployment:

  • Build the API and worker from backend/Dockerfile. Both use the same image: the worker runs celery -A app.core.celery_app worker, and the API runs alembic upgrade head before uvicorn.
  • Build the dashboard with the production target of frontend/Dockerfile, passing VITE_API_URL as a build argument. It serves static files with nginx on port 8080.
  • Use managed or properly secured PostgreSQL and Redis, with strong credentials.
  • Serve the dashboard and API over HTTPS, on subdomains of the same domain (for example app.example.com and api.example.com). Session cookies are SameSite=Lax, so they are only sent when both share a site.
  • Set FRONTEND_URL to the dashboard's public URL. It controls CORS and the OAuth redirect. Update the GitHub App's callback and webhook URLs to match.