Skip to main content

Local Development

For local development, simply run the Next.js dev server:
npm run dev

Production Deployment with Docker Compose

The Pope Bot uses Docker Compose to orchestrate three services in production:
npx thepopebot init   # Scaffold project
npm run setup          # Configure .env, GitHub secrets, Telegram
npm run build          # Next.js build → generates .next/
docker compose up -d   # Start services in background

Services Overview

ServiceImagePurpose
traefiktraefik:v3Reverse proxy with automatic HTTPS (Let’s Encrypt)
event-handlerstephengpope/thepopebot:event-handler-${THEPOPEBOT_VERSION}Node.js runtime + PM2, serves the bind-mounted Next.js app on port 80
runnermyoung34/github-runner:latestSelf-hosted GitHub Actions runner for executing agent jobs
The runner registers as a self-hosted GitHub Actions runner, enabling run-job.yml to spin up Docker agent containers directly on your server. It also has a read-only volume mount (.:/project:ro) so upgrade-event-handler.yml can run docker compose commands against the project’s compose file.

Understanding the Docker Setup

Event Handler Image

The event handler Dockerfile (templates/docker/event-handler/Dockerfile) is not a self-contained application image. It uses a multi-stage build:
  1. First stage installs build tools (python3, make, g++) to compile native addons during npm install
  2. Final image keeps only runtime dependencies (git, gh, PM2) and the pre-compiled node_modules
  3. It does not contain the Next.js app code and does not run next build

Volume Mount Strategy

The docker-compose.yml uses two volume mounts that work together:
volumes:
  - .:/app              # bind mount: host project → /app
  - /app/node_modules   # anonymous volume: preserves container's node_modules
How it works:
  • The bind mount (.:/app) overlays the entire /app directory with the host’s project files — app pages, config, .next/, .env, everything
  • This would clobber the container’s /app/node_modules with the host’s node_modules (which may be compiled for a different platform)
  • But the anonymous volume (/app/node_modules) shields that specific path from the bind mount
  • Docker processes volume mounts so the anonymous volume “wins” for /app/node_modules
  • The first time the container starts, Docker copies the image’s node_modules into the anonymous volume, and from then on it persists there independently
So the host’s node_modules (compiled for macOS) are never used inside the container. The container always uses its own Linux-compiled modules.

Why thepopebot is Installed Twice

The user runs npm install on the host and the Docker image has its own npm install. Both are necessary: Host installation:
  • Needed because next build must resolve all thepopebot/* imports to compile the app
  • Without thepopebot in local node_modules, the build fails immediately on unresolved imports
  • The .next/ output is just bundled JavaScript — it’s platform-independent
Container installation:
  • Next.js needs node_modules at runtime for native modules (like better-sqlite3) and server-side requires that aren’t bundled
  • Those native modules must be compiled for Linux
  • Different purposes, different platforms, both necessary

Build Requirements

Before running docker compose up, you must run npm run build on the host to generate .next/.
  • If the container starts without a valid .next/ build, PM2 will crash-loop with “Could not find a production build” until a build is available
  • After code changes, rebuild-event-handler.yml runs next build inside the container via docker exec (using the container’s node_modules)

VPS Deployment Guide

Deploy your agent to a cloud VPS with HTTPS.

1. Server Prerequisites

You need a VPS (any provider — Hetzner, DigitalOcean, AWS, etc.) with:
  • Docker + Docker Compose
  • Node.js 18+
  • Git
  • GitHub CLI (gh)
Point a domain (e.g., mybot.example.com) to your server’s IP address with a DNS A record.

2. Scaffold and Configure

SSH into your server and scaffold the project:
mkdir my-agent && cd my-agent
npx thepopebot@latest init
npm run setup
When the setup wizard asks for APP_URL, enter your production URL with https:// (e.g., https://mybot.example.com). Set the RUNS_ON GitHub variable so workflows use your server’s self-hosted runner instead of GitHub-hosted runners:
gh variable set RUNS_ON --body "self-hosted" --repo OWNER/REPO

3. Enable HTTPS with Let’s Encrypt

The docker-compose.yml has Let’s Encrypt support built in but commented out. Three edits to enable it:

a) Add your email to .env:

LETSENCRYPT_EMAIL=you@example.com

b) Uncomment TLS configuration in docker-compose.yml

In the traefik service command, remove the # from the TLS lines:
# Before (commented out):
# - --entrypoints.web.http.redirections.entrypoint.to=websecure
# ...

# After (uncommented):
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web

c) Switch event-handler to HTTPS entrypoint

In the event-handler labels, add a # to comment out the HTTP entrypoint, and remove the # from the two HTTPS lines:
# Before:
- traefik.http.routers.event-handler.entrypoints=web
# - traefik.http.routers.event-handler.entrypoints=websecure
# - traefik.http.routers.event-handler.tls.certresolver=letsencrypt

# After:
# - traefik.http.routers.event-handler.entrypoints=web
- traefik.http.routers.event-handler.entrypoints=websecure
- traefik.http.routers.event-handler.tls.certresolver=letsencrypt

4. Build and Launch

npm run build
docker compose up -d
Ports 80 and 443 must be open on your server. Port 80 is required even with HTTPS — Let’s Encrypt uses it for the ACME HTTP challenge to verify domain ownership.

Domain Configuration

Point your domain to your server’s IP address with a DNS A record:
Type: A
Name: mybot (or @ for root domain)
Value: YOUR_SERVER_IP
TTL: 3600 (or auto)
Let’s Encrypt will automatically request and renew certificates once the DNS is propagated.

Production Considerations

Monitoring

  • Check service status: docker compose ps
  • View logs: docker compose logs -f event-handler
  • Check runner status: docker compose logs -f runner

Updates

The framework includes automatic upgrade workflows:
  • upgrade-event-handler.yml — Pulls latest image and restarts container
  • Agent can trigger upgrades via the modify-self skill

Backups

Back up these critical files:
  • .env — Contains all secrets and configuration
  • data/thepopebot.sqlite — Database with API keys, sessions, chat history
  • config/ — Custom configuration files
  • skills/ — Custom skills

Resource Usage

  • Event handler: ~200MB RAM
  • Runner: ~100MB RAM
  • Traefik: ~50MB RAM
  • Agent job containers: 1-4GB RAM (ephemeral)

Security

See the Security page for production security best practices.

Build docs developers (and LLMs) love