Let me explore the repository to understand the Docker deployment setup.
Now let me check how config.yaml maps environment variables to config keys:
I see — these settings are actually in config.yaml, not env vars. Let me check the docker-compose env vars more closely to understand the mapping:
Now let me check how the docker-compose environment variables get mapped to config. It appears they may be handled through config.yaml rather than being direct env var to code mappings:
Now let me check for additional details about ports and the GitHub App PEM key:
Now let me check the e2e test setup for docker compose details:
Let me look at the image reference more closely since the docker-compose.yaml uses build: instead of image::
I notice the build: field is being used with a registry URL, which is unusual for docker-compose. This appears to be image: in intent. Let me verify by checking if there's a different docker-compose format being used:
Now I have enough information. Let me also verify how the docker-compose uses image: vs build::
The example docker-compose uses build: ghcr.io/myk-org/github-webhook-server:latest which is technically an error - this should be image:. I'll document this correctly using image: since that's the intended semantics (pulling a pre-built image from a registry).
Now I have everything I need. Let me write the documentation:
Deploying with Docker
Run the GitHub webhook server as a Docker container with persistent configuration, health monitoring, and automatic restarts — the recommended way to deploy for production use.
Prerequisites
- Docker Engine 20.10+ and Docker Compose V2 installed
- A GitHub personal access token (classic or fine-grained) with repository access
- A
config.yamlfile with at least one repository configured (see Configuring Repositories) - Network access from GitHub to your server on port 5000 (direct or via reverse proxy)
Quick Start
- Create a data directory and add your configuration:
mkdir -p webhook_server_data_dir
- Create a minimal
webhook_server_data_dir/config.yaml:
github-tokens:
- ghp_your_token_here
webhook-ip: https://your-domain.com/webhook_server
repositories:
my-repo:
name: my-org/my-repository
- Create a
docker-compose.yaml:
services:
github-webhook-server:
container_name: github-webhook-server
image: ghcr.io/myk-org/github-webhook-server:latest
volumes:
- "./webhook_server_data_dir:/home/podman/data:Z"
- "/tmp/podman-storage-${USER:-1000}:/tmp/storage-run-1000"
ports:
- "5000:5000"
privileged: true
restart: unless-stopped
- Start the server:
docker compose up -d
- Verify it's running:
curl http://localhost:5000/webhook_server/healthcheck
You should see {"status": 200, "message": "Alive"}.
Step-by-Step Setup
1. Prepare the Data Directory
The container expects your configuration files at /home/podman/data inside the container. Mount a local directory to this path.
Your data directory should contain:
config.yaml(required) — server and repository configurationwebhook-server.private-key.pem(optional) — GitHub App private key, only needed if using a GitHub App instead of personal access tokens
webhook_server_data_dir/
├── config.yaml
└── webhook-server.private-key.pem # optional
Note: Log files are also written to this directory. Make sure the directory is writable by the container user (UID 1000 by default).
2. Configure docker-compose.yaml
Here is the full production-ready docker-compose.yaml with all available options:
services:
github-webhook-server:
container_name: github-webhook-server
image: ghcr.io/myk-org/github-webhook-server:latest
volumes:
- "./webhook_server_data_dir:/home/podman/data:Z"
# Mount temporary directories to prevent boot ID mismatch issues
- "/tmp/podman-storage-${USER:-1000}:/tmp/storage-run-1000"
# Mount Google Cloud credentials for Vertex AI (optional)
# - $HOME/.config/gcloud:/home/podman/.config/gcloud:ro
environment:
- TZ=UTC
- ENABLE_LOG_SERVER=true
- ENABLE_MCP_SERVER=false
ports:
- "5000:5000"
privileged: true
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:5000/webhook_server/healthcheck && curl -f http://localhost:${SIDECAR_PORT:-9100}/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
restart: unless-stopped
Warning: The container requires
privileged: truebecause it uses Podman inside the container to build and push container images for your repositories. If you don't use container build features, the server still requires this flag for Podman runtime initialization.
3. Configure Volumes
Two volume mounts are important:
| Volume | Container Path | Purpose |
|---|---|---|
| Data directory | /home/podman/data |
Config, logs, and GitHub App private key |
| Podman temp storage | /tmp/storage-run-1000 |
Prevents Podman boot ID mismatch issues on container restart |
The :Z suffix on the data volume sets the correct SELinux context. Omit it if you're not using SELinux.
4. Set Environment Variables
Configure the server behavior through environment variables in the environment section:
| Variable | Default | Description |
|---|---|---|
TZ |
System default | Timezone for log timestamps (e.g., UTC, America/New_York) |
ENABLE_LOG_SERVER |
false |
Enable the built-in log viewer web UI and API |
ENABLE_MCP_SERVER |
false |
Enable the MCP server for AI agent integration |
SIDECAR_PORT |
9100 |
Port for the AI sidecar service |
Tip: Server settings like
port,ip-bind,max-workers,webhook-secret,verify-github-ips, andverify-cloudflare-ipsare configured inconfig.yaml, not as environment variables. See Configuration Reference for all options.
For a complete reference of all environment variables, see Environment Variables.
5. Expose Ports
The container exposes three ports:
| Port | Service | Expose Externally? |
|---|---|---|
| 5000 | Webhook server (main API) | Yes — GitHub sends webhooks here |
| 5001 | Internal tool server | No — binds to 127.0.0.1 inside the container |
| 9100 | AI sidecar | No — internal only |
Only port 5000 needs to be published. The other services are internal to the container.
ports:
- "5000:5000"
To use a different host port:
ports:
- "8080:5000"
6. Configure Health Checks
The built-in health check verifies both the main webhook server and the AI sidecar are responding:
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:5000/webhook_server/healthcheck && curl -f http://localhost:${SIDECAR_PORT:-9100}/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
The start_period gives the server 30 seconds to initialize before health checks begin failing. Increase this if your server manages many repositories and takes longer to start.
Check container health status:
docker inspect --format='{{.State.Health.Status}}' github-webhook-server
7. Start and Verify
# Start in the background
docker compose up -d
# Watch the logs
docker compose logs -f
# Check health status
docker compose ps
The HEALTHY status in docker compose ps confirms both services are running.
Advanced Usage
Configuring Webhook Security
Add these settings to your config.yaml to verify that incoming webhooks are genuinely from GitHub:
webhook-secret: your-secret-here
verify-github-ips: true
verify-cloudflare-ips: true # if behind Cloudflare
The webhook-secret must match the secret configured in your GitHub webhook settings. IP verification fetches GitHub's published IP ranges at startup and rejects requests from other sources.
Warning: If IP verification is enabled but the server cannot reach the GitHub or Cloudflare API at startup, it will refuse to start rather than run in an insecure state.
Tuning Worker Count
Control concurrency by setting max-workers in config.yaml:
max-workers: 50
The default is 10 workers. Increase this for servers handling many repositories or high webhook volume.
Enabling the Log Viewer
Set ENABLE_LOG_SERVER=true in your environment to activate the built-in web UI for browsing webhook processing logs:
environment:
- ENABLE_LOG_SERVER=true
Warning: The log viewer endpoints are unauthenticated. Only deploy with log server enabled on trusted networks (VPN, internal network). Access is restricted to private/loopback IP ranges by default.
See Using the Log Viewer for details.
Enabling AI Features
To use AI-powered features (conventional title suggestions, cherry-pick conflict resolution), provide API keys as environment variables:
environment:
- ANTHROPIC_API_KEY=sk-ant-xxx # For Claude Code
- GEMINI_API_KEY=xxx # For Gemini CLI
# - CURSOR_API_KEY=xxx # For Cursor Agent
# - SIDECAR_PORT=9100 # AI sidecar port (default: 9100)
You also need to configure the ai-features section in your config.yaml. See Enabling AI Features for setup details.
Mounting Google Cloud Credentials
If using Vertex AI for AI features, mount your Google Cloud credentials read-only:
volumes:
- "./webhook_server_data_dir:/home/podman/data:Z"
- "/tmp/podman-storage-${USER:-1000}:/tmp/storage-run-1000"
- "$HOME/.config/gcloud:/home/podman/.config/gcloud:ro"
Updating the Container
# Pull the latest image
docker compose pull
# Recreate the container with the new image
docker compose up -d
Your configuration and logs persist in the mounted data directory, so updates are safe.
Troubleshooting
Container exits immediately
Check the logs for configuration errors:
docker compose logs
The most common cause is a missing or invalid config.yaml. The server requires at least a repositories section with one entry.
Health check fails
If docker compose ps shows unhealthy:
# Check which health check is failing
docker exec github-webhook-server curl -f http://localhost:5000/webhook_server/healthcheck
docker exec github-webhook-server curl -f http://localhost:9100/health
The main server health check failing usually means a startup error. The sidecar health check failing means AI features won't work but doesn't affect core webhook processing.
Podman boot ID mismatch
If you see errors about Podman boot ID mismatches after container restarts, ensure the temp storage volume is mounted:
volumes:
- "/tmp/podman-storage-${USER:-1000}:/tmp/storage-run-1000"
The container runs an automatic cleanup script on startup to clear stale Podman runtime state.
Permission denied on data directory
The container runs as UID 1000. Ensure your data directory is owned by this user:
chown -R 1000:1000 webhook_server_data_dir/