Skip to content

Deployment

Fly.io

prbot is pre-configured for Fly.io with a persistent SQLite volume.

1. Install flyctl

# macOS
brew install flyctl

# or via script
curl -L https://fly.io/install.sh | sh

2. Create the app

fly launch --no-deploy

3. Create a volume for the database

fly volumes create data --size 1 --region lhr

Note

Replace lhr with your preferred Fly.io region.

4. Set secrets

fly secrets set \
  PR_BOT_GITHUB_APP_ID="your-app-id" \
  PR_BOT_GITHUB_PRIVATE_KEY="$(cat path/to/private-key.pem)" \
  PR_BOT_GITHUB_WEBHOOK_SECRET="your-webhook-secret" \
  PR_BOT_SLACK__BOT_TOKEN="xoxb-your-token" \
  PR_BOT_SLACK__SIGNING_SECRET="your-signing-secret" \
  PR_BOT_DISCORD__BOT_TOKEN="your-discord-bot-token"

5. Deploy

fly deploy

The app will be available at https://your-app-name.fly.dev.

CI/CD

The included GitHub Actions workflow (.github/workflows/ci.yml) automatically deploys to Fly.io on pushes to main after all checks pass. Set the FLY_API_TOKEN secret in your GitHub repository settings.

Docker

Build and run with Docker directly:

docker build -t prbot .
docker run -p 8080:8080 \
  -v $(pwd)/data:/app/data \
  --env-file .env \
  prbot
Dockerfile
FROM python:3.14-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev --no-install-project
COPY . .
RUN uv sync --frozen --no-dev
CMD ["uv", "run", "uvicorn", "prbot.main:api", "--host", "0.0.0.0", "--port", "8080"]

Self-hosting checklist

Before going to production, make sure you have:

  • [x] Created and installed a GitHub App
  • [x] Set up at least one messaging integration:
    • Slack — create and install a Slack app
    • Discord — create and invite a Discord bot
  • [x] Set all required environment variables
  • [x] Configured your webhook URLs to point to your deployment:
    • Slack: https://your-domain.com/slack/events
    • GitHub: https://your-domain.com/github/webhooks
  • [x] Ensured the database volume is persistent (data is not lost on redeploy)
  • [x] Verified the /health endpoint returns {"status": "healthy"}

Discord doesn't need a webhook URL

The Discord integration connects via WebSocket, so no public URL configuration is needed — just the bot token.