Deployment Guide

June 24, 2026 · View on GitHub

中文

This guide covers building, deploying, and running HookRun in production environments.


No Go, no Docker — just one command:

curl -fsSL https://bluvenr.github.io/hookrun/install.sh | bash

What it does:

  1. Auto-detects your OS (Linux/macOS) and architecture (amd64/arm64)
  2. Downloads the latest pre-built binary from GitHub Releases
  3. Installs to /usr/local/bin/hookrun
  4. Runs hookrun init to generate config.yaml and hooks/example.yaml

Custom Options

Use environment variables to customize:

# Install a specific version
curl -fsSL https://bluvenr.github.io/hookrun/install.sh | HOOKRUN_VERSION=1.1.3 bash

# Custom install directory
curl -fsSL https://bluvenr.github.io/hookrun/install.sh | HOOKRUN_INSTALL_DIR=$HOME/bin bash

# Use GitHub webhook template
curl -fsSL https://bluvenr.github.io/hookrun/install.sh | HOOKRUN_TEMPLATE=github bash

# Skip config init (binary only)
curl -fsSL https://bluvenr.github.io/hookrun/install.sh | HOOKRUN_INIT_DIR=skip bash
VariableDefaultDescription
HOOKRUN_VERSIONlatestPin a specific version (e.g. 1.1.3)
HOOKRUN_INSTALL_DIR/usr/local/binBinary install path
HOOKRUN_INIT_DIR. (current dir)Directory for config init. skip to skip
HOOKRUN_TEMPLATEgenericInit template: generic, github, gitlab

After Install

# Validate configuration
hookrun validate

# Start server
hookrun start

# Check status
hookrun status

1. Build from Source

Prerequisites

  • Go 1.23 or later
  • Git

Build

git clone https://github.com/bluvenr/hookrun.git
cd hookrun
go build -o hookrun ./cmd/hookrun/

Build with Version Info

Use -ldflags to inject version and build time:

go build -ldflags "-X github.com/bluvenr/hookrun/internal/version.Version=1.1.3 -X 'github.com/bluvenr/hookrun/internal/version.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)'" \
  -o hookrun ./cmd/hookrun/

Cross-Compilation

Build for different platforms:

# Linux amd64
GOOS=linux GOARCH=amd64 go build -o hookrun-linux-amd64 ./cmd/hookrun/

# Linux arm64 (e.g. Raspberry Pi, AWS Graviton)
GOOS=linux GOARCH=arm64 go build -o hookrun-linux-arm64 ./cmd/hookrun/

# macOS amd64 (Intel)
GOOS=darwin GOARCH=amd64 go build -o hookrun-darwin-amd64 ./cmd/hookrun/

# macOS arm64 (Apple Silicon)
GOOS=darwin GOARCH=arm64 go build -o hookrun-darwin-arm64 ./cmd/hookrun/

# Windows amd64
GOOS=windows GOARCH=amd64 go build -o hookrun-windows-amd64.exe ./cmd/hookrun/

2. Directory Structure

Recommended production layout:

/opt/hookrun/
├── hookrun                 # Binary
├── config.yaml             # Global config
├── hooks/                  # Rule YAML files
│   ├── app-a.yaml
│   └── app-b.yaml
├── scripts/                # Custom scripts referenced by rules
│   └── deploy.sh
└── logs/                   # Log output directory
    └── hookrun-2026-06-11.log

Runtime data (PID file, status, signals) is stored in ~/.hookrun/:

~/.hookrun/
├── hookrun.pid             # Process PID
├── hookrun.status          # JSON status file
├── hookrun.reload          # Reload signal file (Windows)
└── hookrun.stop            # Stop signal file (Windows)

3. Linux Deployment

Option A: Direct Execution (Simple)

# Copy binary and config
cp hookrun /usr/local/bin/
mkdir -p /etc/hookrun/hooks
cp config.yaml /etc/hookrun/

# Run
cd /etc/hookrun
hookrun start

Create /etc/systemd/system/hookrun.service:

[Unit]
Description=HookRun - Webhook Action Engine
After=network.target

[Service]
Type=simple
User=hookrun
Group=hookrun
WorkingDirectory=/etc/hookrun
ExecStart=/usr/local/bin/hookrun start -f -c /etc/hookrun/config.yaml
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal

# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/etc/hookrun/logs
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Setup:

# Create user
sudo useradd -r -s /sbin/nologin hookrun

# Create directories
sudo mkdir -p /etc/hookrun/hooks /etc/hookrun/scripts /etc/hookrun/logs
sudo chown -R hookrun:hookrun /etc/hookrun

# Copy files
sudo cp hookrun /usr/local/bin/
sudo cp config.yaml /etc/hookrun/
sudo cp hooks/*.yaml /etc/hookrun/hooks/

# Set script permissions
sudo chmod +x /etc/hookrun/scripts/*.sh

# Enable and start
sudo systemctl daemon-reload
sudo systemctl enable hookrun
sudo systemctl start hookrun

# Check status
sudo systemctl status hookrun

Note: When using systemd, always use start -f (foreground) so systemd manages the process lifecycle directly.

Systemd Management Commands

# Stop
sudo systemctl stop hookrun

# Restart
sudo systemctl restart hookrun

# View logs
sudo journalctl -u hookrun -f

# Reload config (hot-reload)
hookrun reload -c /etc/hookrun/config.yaml

4. Docker Deployment

docker pull ghcr.io/bluvenr/hookrun:latest

docker run -d \
  --name hookrun \
  -p 9000:9000 \
  -v ./config.yaml:/app/config.yaml \
  -v ./hooks:/app/hooks \
  -v ./logs:/app/logs \
  --restart unless-stopped \
  ghcr.io/bluvenr/hookrun:latest

Available tags:

TagDescription
latestLatest stable release
1.1.3Specific version
1.1Latest patch in minor version

Option B: Build from Source

FROM golang:1.23-alpine AS builder
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -o hookrun ./cmd/hookrun/

FROM alpine:3.20
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /app
COPY --from=builder /build/hookrun /usr/local/bin/hookrun
COPY config.yaml /app/
COPY hooks/ /app/hooks/
EXPOSE 9000
ENTRYPOINT ["hookrun", "start", "-f", "-c", "/app/config.yaml"]
# Build image
docker build -t hookrun:latest .

# Run
docker run -d \
  --name hookrun \
  -p 9000:9000 \
  -v ./hooks:/app/hooks \
  -v ./logs:/app/logs \
  --restart unless-stopped \
  hookrun:latest

Docker Compose

Using GHCR image (recommended):

version: "3.8"
services:
  hookrun:
    image: ghcr.io/bluvenr/hookrun:latest
    container_name: hookrun
    ports:
      - "9000:9000"
    volumes:
      - ./config.yaml:/app/config.yaml
      - ./hooks:/app/hooks
      - ./logs:/app/logs
      - ./scripts:/app/scripts
    restart: unless-stopped

Or building from source:

version: "3.8"
services:
  hookrun:
    build: .
    container_name: hookrun
    ports:
      - "9000:9000"
    volumes:
      - ./hooks:/app/hooks
      - ./logs:/app/logs
      - ./scripts:/app/scripts
    restart: unless-stopped

Run:

docker compose up -d

5. Windows Deployment

Manual Setup

# Create directory
New-Item -ItemType Directory -Force -Path C:\hookrun\hooks

# Copy files
Copy-Item hookrun.exe C:\hookrun\
Copy-Item config.yaml C:\hookrun\
Copy-Item hooks\*.yaml C:\hookrun\hooks\

# Run
cd C:\hookrun
.\hookrun.exe start

Run as Windows Service

Use NSSM (Non-Sucking Service Manager):

nssm install HookRun C:\hookrun\hookrun.exe
nssm set HookRun AppParameters "start -f -c C:\hookrun\config.yaml"
nssm set HookRun AppDirectory C:\hookrun
nssm start HookRun

Windows-Specific Notes

  • Process control: Uses signal file IPC (polled every 2 seconds) instead of Unix signals
  • PID detection: Uses tasklist command to check if process is running
  • Shell: Commands execute via cmd /c

6. Reverse Proxy

Nginx

location /webhook/ {
    proxy_pass http://127.0.0.1:9000/webhook/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # Increase timeout for long-running actions
    proxy_read_timeout 300s;
}

Caddy

webhook.example.com {
    reverse_proxy localhost:9000
}

7. Security Recommendations

  1. Use authentication: Always set auth.token in rule configs
  2. IP whitelist: Restrict to known webhook source IPs where possible
  3. HTTPS: Use a reverse proxy with TLS termination
  4. Firewall: Only expose port 443 (HTTPS); keep port 9000 internal
  5. Least privilege: Run HookRun as a non-root user with minimal filesystem permissions
  6. Script permissions: Ensure scripts in scripts/ have appropriate execute permissions
  7. Config validation: Always run hookrun validate after editing configs

8. Monitoring

Health Check Endpoint

curl http://localhost:9000/health

Response:

{"status": "ok", "uptime": "2h30m15s", "rules": 3, "version": "x.y.z"}

When relay is configured, the response includes a relay field:

{"status": "ok", "uptime": "2h30m15s", "rules": 3, "version": "x.y.z", "relay": {"role": "upstream", "upstream_targets": 2, "downstream_connected": true}}

Relay Status API

Use GET /api/relay/status (no auth required) to get detailed relay information:

# Check relay role and connectivity
curl http://localhost:9000/api/relay/status

# Check registered downstream targets
curl http://localhost:9000/api/relay/status | jq '.upstream.targets_count'

Or use the CLI:

# Show relay status
hookrun relay status

# List registered targets (token auto-read from config)
hookrun relay targets

# Override token if needed
hookrun relay targets --token your-registry-token

Log Monitoring

# Tail logs
tail -f logs/hookrun-$(date +%Y-%m-%d).log

# Count requests today
grep -c "Webhook request" logs/hookrun-$(date +%Y-%m-%d).log

# Check for errors
grep "ERROR" logs/hookrun-$(date +%Y-%m-%d).log

Integration with Monitoring Tools

Use the /health endpoint with:

  • Prometheus: via blackbox_exporter HTTP probe
  • Uptime Kuma: HTTP(s) monitor on /health
  • Nagios/Zabbix: Custom check script hitting /health