Production Deployment Guide — HenKaiPan ASPM
This guide covers deploying HenKaiPan ASPM in production environments with Docker Compose. For Kubernetes deployments, see Kubernetes Deployment Guide.
Architecture Overview
┌──────────────┐
│ Reverse │
│ Proxy │
│ (nginx/ │
│ caddy) │
└──┬─────────┬──┘
│ │
:443 │ :9090 │
HTTPS │ metrics
┌──────▼──┐ ┌────▼─────┐
│ API │ │ Prometheus│
│ :8080 │ │ (optional)│
└──┬───┬──┘ └──────────┘
│ │
┌─────▼───▼──────┐
│ PostgreSQL │
│ + Redis │
└────────────────┘
┌──────────────┐
│ Worker │
│ (embedded │
│ scanners) │
└──────────────┘- API (
ghcr.io/dyallab/henkaipan-api): Serves the frontend and REST API on port 8080. Exposes Prometheus metrics on port 9090. - Worker (
ghcr.io/dyallab/henkaipan-worker): Executes background jobs (scans, webhooks, emails). Scanner binaries are bundled in the image. - PostgreSQL (port 5432): Primary data store.
- Redis (port 6379): Job queue, rate-limiting, and SSE event relay between worker and API processes.
Prerequisites
- Docker & Docker Compose v2.24+
- Linux host (recommended) or Windows with WSL2
- 8 GB RAM minimum (16 GB recommended for concurrent scans)
- 30 GB free disk (scanner images ~6 GB, plus data)
- Domain name with DNS pointing to your server
- Ports 80 and 443 open (for reverse proxy)
1. Initial Setup
# Clone the repository
git clone https://github.com/Dyallab/HenKaiPan-self-hosted.git
cd HenKaiPan-self-hosted
# Run the installer (checks prerequisites, generates secrets)
./install.sh
# To skip Ollama installation (e.g. if you use another AI provider):
# ./install.sh --skip-ollama
# Start the stack
docker compose up -d
# Verify
curl http://localhost:8080/api/healthThe API serves both the backend API and the embedded frontend on port 8080. Open http://your-server:8080 and log in with the admin credentials generated by install.sh.
Ollama: By default, the installer sets up Ollama (free, self-hosted AI) for summary generation. Use
./install.sh --skip-ollamaif you don't need it or plan to use a different AI provider.
2. Reverse Proxy with TLS
Option A: Nginx + Let's Encrypt (Recommended)
Create /etc/nginx/sites-available/henkaipan:
server {
listen 80;
server_name aspm.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name aspm.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/aspm.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/aspm.yourdomain.com/privkey.pem;
# Security headers
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
# API and frontend
location / {
proxy_pass http://127.0.0.1:8080;
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;
}
}Enable and obtain TLS certificate:
sudo ln -s /etc/nginx/sites-available/henkaipan /etc/nginx/sites-enabled/
sudo certbot --nginx -d aspm.yourdomain.com
sudo systemctl reload nginxOption B: Caddy (Simpler, Auto-TLS)
Create /etc/caddy/Caddyfile:
aspm.yourdomain.com {
reverse_proxy 127.0.0.1:8080
headers {
Strict-Transport-Security "max-age=63072000"
}
}Caddy automatically obtains and renews TLS certificates.
3. Environment Configuration
After running install.sh, review and adjust your .env file:
Required Security Settings
# Must be set behind HTTPS
COOKIE_SECURE=true
# Generate strong random values
JWT_SECRET=<openssl rand -base64 32>
SECRET_ENCRYPTION_KEY=<openssl rand -hex 32>
# Database (defaults are fine for single-host)
DATABASE_URL=postgres://aspm:aspm@postgres:5432/aspm?sslmode=disablePerformance Tuning (optional)
# Increase API server timeouts for large scan payloads
# (configurable via Go server, defaults are reasonable)
# Redis for rate limiting / queue
REDIS_ADDR=redis:63794. Security Hardening
Scanner Execution
Scanner binaries run directly within the worker process. All scanners are bundled in the worker Docker image, eliminating the need for Docker socket access.
Network Security
# Block external access to PostgreSQL and Redis
# These should only be accessible from the Docker network
docker compose port postgres 5432 # Should NOT be exposed publiclyIf you do not need external DB access, remove ports from postgres and redis in docker-compose.yml. They will remain accessible within the Docker network.
Secrets Management
- The
.envfile contains sensitive credentials. SetCOOKIE_SECURE=trueso auth cookies are only sent over HTTPS. - Restrict
.envfile permissions:chmod 600 .env - Consider using Docker Secrets or a secret manager (Hashicorp Vault, AWS Secrets Manager) for production.
Firewall Rules
# Allow only SSH, HTTP, and HTTPS
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw deny 8080 # API should only be accessed via reverse proxy
sudo ufw enable5. Database Backups
Automated Backup Script
The landing page provides a Backup & Restore guide with a backup script. Set up automated daily backups:
# Install backup script
mkdir -p /opt/henkaipan/backups
cp scripts/backup.sh /opt/henkaipan/backups/
# Add cron job (runs daily at 2 AM)
echo "0 2 * * * /opt/henkaipan/backups/backup.sh >> /var/log/henkaipan-backup.log 2>&1" | crontab -Restore Procedure
# Stop the API to prevent writes
docker compose stop api worker
# Restore from backup
docker compose exec -T postgres psql -U aspm -d aspm < /path/to/backup.sql
# Restart
docker compose up -d6. Monitoring
Prometheus Metrics
The API exposes metrics on port 9090 (configurable via PROMETHEUS_PORT):
# Quick check
curl http://localhost:9090/metricsA sample Prometheus configuration is available at monitoring/prometheus.yml. Key metrics include:
- Queue depth (Asynq)
- Request latency and rate
- Database connection pool stats
- Go runtime metrics
Health Checks
# Full health check (DB, Redis, Worker status)
curl http://localhost:8080/api/health
# Version info
curl http://localhost:8080/api/versionLogging
# View API logs
docker compose logs -f --tail=100 api
# View Worker logs
docker compose logs -f --tail=100 worker
# Limit log size in docker-compose.yml
x-logging: &logging
logging:
options:
max-size: "10m"
max-file: "3"7. Upgrading
Standard Upgrade
# Pull new images
docker compose pull
# Recreate containers with new images
docker compose up -d
# Verify health
curl http://localhost:8080/api/healthMigrations run automatically on API startup. Check API logs for migration output:
docker compose logs api | grep -i migrationPinning to a Specific Version
To control exactly which version runs, override the image tag in docker-compose.yml:
services:
api:
image: ghcr.io/dyallab/henkaipan-api:v1.0.0
worker:
image: ghcr.io/dyallab/henkaipan-worker:v1.0.0Rollback
If an upgrade causes issues:
- Revert image tags to the previous version in
docker-compose.yml - Recreate containers:
docker compose up -d- Verify rollback:
curl http://localhost:8080/api/version
curl http://localhost:8080/api/healthDatabase note: Migrations are additive (new columns, new tables). Downgrading the application code does not automatically revert schema changes. This is safe — the application remains compatible with the newer schema. If you need a full rollback including database schema, restore from a backup taken before the upgrade.
Pre-Upgrade Checklist
- Review the CHANGELOG.md for breaking changes
- Take a database backup
- Note current version:
curl http://localhost:8080/api/version - Test in a staging environment if possible
8. Production Checklist
Before going live:
Security
-
COOKIE_SECURE=trueset in.env -
COOKIE_DOMAIN=.example.comset for your domain -
COOKIE_SAMESITE=lax(default) orstrictfor stricter CSRF protection -
CORS_ALLOWED_ORIGINS=https://aspm.example.comset (comma-separated for multiple origins) -
TRUSTED_PROXIESset if behind a reverse proxy (e.g.10.0.0.0/8) -
PUBLIC_API_BASE=(empty) so API and frontend are served from same origin - Reverse proxy (nginx/caddy) with valid TLS certificate
- Firewall restricts direct access to API port 8080
- PostgreSQL and Redis ports not exposed to the internet
- Strong random
JWT_SECRETandSECRET_ENCRYPTION_KEYgenerated -
.envfile permissions:chmod 600 .env - Default credentials rotated (
ADMIN_USER,ADMIN_PASS) - SSO configured with TLS-terminated
SSO_REDIRECT_URIand matching IdP redirect URI (see SSO with Authelia)
Reliability
- Database backups configured (daily + offsite)
- Backup restore procedure tested
- Monitoring set up (health endpoint, Prometheus metrics)
- Log aggregation configured (log rotation, external sink if needed)
- Resource limits set in
docker-compose.yml(optional but recommended)
Maintenance
- Upgrade procedure documented and tested
- Rollback plan documented
- DNS records configured (A/AAAA record for your domain)
- Email notifications configured (SMTP) for alerts
9. Resource Sizing
| Deployment Size | Users | RAM | Disk | Workers |
|---|---|---|---|---|
| Small team | 1-10 | 8 GB | 30 GB | 1 |
| Medium team | 10-50 | 16 GB | 100 GB | 2 |
| Large team | 50-200 | 32 GB | 250 GB | 3-4 |
Disk usage is dominated by Docker images (scanners). Actual database storage is minimal for most deployments (< 1 GB for thousands of findings).
10. Related Documentation
- Operations Guide — Worker scaling, scanner requirements, troubleshooting
- Kubernetes Deployment Guide — K8s production deployment
- Quickstart — Getting started with Docker Compose
- Backup & Restore — Detailed backup procedures