All environment variables for the Event Handler (set in .env in your project root):
| Variable | Description | Required |
|---|---|---|
APP_URL |
Public URL for webhooks, Telegram, and Traefik hostname | Yes |
APP_HOSTNAME |
Hostname extracted from APP_URL (used by Traefik) | Yes |
AUTH_SECRET |
Secret for NextAuth session encryption (auto-generated by setup) | Yes |
AUTH_TRUST_HOST |
Trust host header behind reverse proxy (set true for Docker/Traefik) |
For Docker |
GH_TOKEN |
GitHub PAT for creating branches/files | Yes |
GH_OWNER |
GitHub repository owner | Yes |
GH_REPO |
GitHub repository name | Yes |
TELEGRAM_BOT_TOKEN |
Telegram bot token from BotFather | For Telegram |
TELEGRAM_CHAT_ID |
Restricts bot to this chat only | For security |
TELEGRAM_WEBHOOK_SECRET |
Secret for webhook validation | No |
TELEGRAM_VERIFICATION |
Verification code for getting your chat ID | For Telegram setup |
GH_WEBHOOK_SECRET |
Secret for GitHub Actions webhook auth | For notifications |
LLM_PROVIDER |
LLM provider: anthropic, openai, google, or custom (default: anthropic) |
No |
LLM_MODEL |
LLM model name override (provider-specific default if unset) | No |
LLM_MAX_TOKENS |
Max tokens for LLM responses (default: 4096) |
No |
ANTHROPIC_API_KEY |
API key for Anthropic provider | For anthropic provider |
OPENAI_API_KEY |
API key for OpenAI provider / Whisper voice transcription | For openai provider or voice |
GOOGLE_API_KEY |
API key for Google provider | For google provider |
CUSTOM_API_KEY |
API key for custom OpenAI-compatible provider (not needed for local models) | For custom provider |
OPENAI_BASE_URL |
Custom OpenAI-compatible base URL (e.g. http://localhost:11434/v1 for Ollama) |
For custom provider |
WEB_SEARCH |
Set to false to disable web search tool (available for anthropic and openai only) |
No |
ASSEMBLYAI_API_KEY |
API key for AssemblyAI voice transcription (hides voice button if unset) | For voice input |
DATABASE_PATH |
Override SQLite database location (default: data/thepopebot.sqlite) |
No |
THEPOPEBOT_VERSION |
Package version for Docker image tags (auto-set by setup) | No |
LETSENCRYPT_EMAIL |
Email for Let's Encrypt SSL (docker-compose only) | No |
EVENT_HANDLER_IMAGE_URL |
Custom event handler Docker image | No |
JOB_IMAGE_URL |
Custom job agent Docker image | No |
API keys for /api routes are database-backed and managed via the web UI Settings page. Use the x-api-key header for authentication.
Set via npx thepopebot set-agent-secret and npx thepopebot set-agent-llm-secret. Each value is stored exactly as provided.
| Prefix | Purpose | Example |
|---|---|---|
AGENT_ |
Protected credentials (filtered from LLM's bash) | AGENT_GH_TOKEN, AGENT_ANTHROPIC_API_KEY |
AGENT_LLM_ |
LLM-accessible credentials (skills, browser logins) | AGENT_LLM_BRAVE_API_KEY |
The run-job.yml workflow automatically collects these at runtime and passes them to the Docker container.
| Secret | Description | Required |
|---|---|---|
GH_WEBHOOK_SECRET |
Random secret for webhook authentication | Yes |
Configure in Settings → Secrets and variables → Actions → Variables:
| Variable | Description | Required | Default |
|---|---|---|---|
APP_URL |
Public URL for the event handler (e.g., https://mybot.example.com) |
Yes | — |
AUTO_MERGE |
Set to false to disable auto-merge of job PRs |
No | Enabled |
ALLOWED_PATHS |
Comma-separated path prefixes for auto-merge | No | /logs |
JOB_IMAGE_URL |
Docker image path for job agent (e.g., ghcr.io/myorg/mybot) |
No | stephengpope/thepopebot:pi-coding-agent-job-${THEPOPEBOT_VERSION} |
EVENT_HANDLER_IMAGE_URL |
Docker image path for event handler | No | stephengpope/thepopebot:event-handler-${THEPOPEBOT_VERSION} |
RUNS_ON |
GitHub Actions runner label (e.g., self-hosted) |
No | ubuntu-latest |
LLM_PROVIDER |
LLM provider (anthropic, openai, google, custom) |
No | anthropic |
LLM_MODEL |
LLM model name for the agent | No | Provider default |
OPENAI_BASE_URL |
Custom OpenAI-compatible base URL (for custom provider) |
No | — |
AGENT_BACKEND |
Agent runner: pi (API credits) or claude-code (subscription) |
No | pi |
For self-hosted deployment, build the project and start Docker:
npm run build
docker compose up -dImportant: The .next/ build directory must exist before starting the container. If the container starts without a valid build, it will crash-loop until one is available.
This starts three services:
- Traefik — Reverse proxy with automatic SSL (Let's Encrypt if
LETSENCRYPT_EMAILis set) - Event Handler — Node.js runtime + PM2, serves the bind-mounted Next.js app on port 80. An anonymous volume (
/app/node_modules) preserves the container's pre-built Linux-compiled native modules (likebetter-sqlite3) - Runner — Self-hosted GitHub Actions runner for executing jobs
Set RUNS_ON=self-hosted as a GitHub repository variable to route workflows to your runner.
See the Architecture docs for more details.
If your public URL changes (e.g., after restarting ngrok or changing domains):
- Update
APP_URLandAPP_HOSTNAMEin your.envfile - Update the
APP_URLGitHub repository variable - Restart Docker:
docker compose up -d - If Telegram is configured, re-register the webhook:
npm run setup-telegramIf you're deploying to a platform where you can't run the setup script (Vercel, Railway, etc.), configure Telegram manually:
-
Set environment variables in your platform's dashboard (see
.env.examplefor reference):TELEGRAM_BOT_TOKEN- Your bot token from @BotFatherTELEGRAM_WEBHOOK_SECRET- Generate withopenssl rand -hex 32TELEGRAM_VERIFICATION- A verification code likeverify-abc12345
-
Deploy and register the webhook:
curl -X POST https://your-app.vercel.app/api/telegram/register \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{"bot_token": "YOUR_BOT_TOKEN", "webhook_url": "https://your-app.vercel.app/api/telegram/webhook"}'
This registers your webhook with the secret from your env.
-
Get your chat ID:
- Message your bot with your
TELEGRAM_VERIFICATIONcode (e.g.,verify-abc12345) - The bot will reply with your chat ID
- Message your bot with your
-
Set
TELEGRAM_CHAT_ID:- Add the chat ID to your environment variables
- Redeploy
Now your bot only responds to your authorized chat.