This guide covers production-ready deployment of Heimdall with security hardening, monitoring, and backup strategies.
Pre-Deployment Checklist
Generate secure keys
# Encryption key (32 bytes)
openssl rand -hex 32
# Webhook secret (20 bytes)
openssl rand -hex 20
Store these in your .env file.
Configure AI providers
Set at least one API key: ANTHROPIC_API_KEY = sk-ant-...
# or OPENAI_API_KEY=sk-...
# or OLLAMA_URL=http://localhost:11434
Set production database
Use a managed database service or a dedicated PostgreSQL instance:
Configure TLS
Either:
Use a reverse proxy (recommended) → set TLS_ENABLED=false
Or enable TLS in Heimdall → set TLS_ENABLED=true and provide certificates
Update CORS origin
CORS_ALLOWED_ORIGIN = https://heimdall.example.com
Building the Release Binary
Option 1: Native Build
# Install Rust (if not already installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default stable
# Clone repository
git clone https://github.com/modestnerd/heimdall.git
cd heimdall
# Build optimized release binary
cargo build --release
# Binary location
ls -lh target/release/heimdall
Option 2: Docker Build
# Build Docker image
docker compose build --build-arg DB_DRIVER=postgres
# Or use multi-platform builds
docker buildx build \
--platform linux/amd64,linux/arm64 \
--build-arg DB_DRIVER=postgres \
-t heimdall:latest .
The Docker build automatically handles schema generation and migration compilation.
Database Setup
PostgreSQL (Recommended)
Install PostgreSQL
# Ubuntu/Debian
sudo apt update
sudo apt install postgresql-17 postgresql-contrib
# macOS
brew install postgresql@17
Create database and user
CREATE DATABASE heimdall ;
CREATE USER heimdall WITH ENCRYPTED PASSWORD 'your_secure_password' ;
GRANT ALL PRIVILEGES ON DATABASE heimdall TO heimdall;
\q
Configure connection pooling (optional)
Use PgBouncer for connection pooling: sudo apt install pgbouncer
Edit /etc/pgbouncer/pgbouncer.ini: [databases]
heimdall = host =localhost port =5432 dbname =heimdall
[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = md5
pool_mode = transaction
max_client_conn = 100
default_pool_size = 20
Test connection
psql "postgres://heimdall:your_secure_password@localhost:5432/heimdall" -c "SELECT 1;"
Managed Database Services
Heimdall works with managed PostgreSQL services:
AWS RDS
Google Cloud SQL
Azure Database
DigitalOcean
Always use SSL/TLS for database connections in production by adding ?sslmode=require to your connection string.
Security Hardening
1. Environment Variables
Never commit .env files. Use secrets management:
Docker Secrets
Kubernetes Secrets
Vault
# Create secrets
echo "your_encryption_key" | docker secret create encryption_key -
echo "your_webhook_secret" | docker secret create webhook_secret -
# Reference in docker-compose.yml
secrets:
- encryption_key
- webhook_secret
2. Network Security
# Firewall rules (UFW example)
sudo ufw allow 22/tcp # SSH
sudo ufw allow 443/tcp # HTTPS
sudo ufw deny 8080/tcp # Block direct access to Heimdall
sudo ufw enable
# Only allow reverse proxy to access Heimdall
APP_HOST = 127.0.0.1 # Bind to localhost only
APP_PORT = 8080
3. Docker Socket Security
Heimdall requires Docker socket access for the Garmr sandbox. This is a privileged operation.
Minimize risk:
# docker-compose.yml
services :
heimdall :
volumes :
- /var/run/docker.sock:/var/run/docker.sock:ro # Read-only mount
user : "1000:999" # Non-root user in docker group
security_opt :
- no-new-privileges:true
cap_drop :
- ALL
cap_add :
- NET_BIND_SERVICE
4. Database Security
# PostgreSQL authentication (pg_hba.conf)
# Require SSL for remote connections
hostssl all all 0.0.0.0/0 scram-sha-256
# Rotate passwords regularly
ALTER USER heimdall WITH PASSWORD 'new_secure_password' ;
# Enable audit logging
ALTER DATABASE heimdall SET log_statement = 'mod' ;
5. Rate Limiting
Implement rate limiting at the reverse proxy level (see Reverse Proxy ).
Running as a System Service
Create a systemd service for automatic startup and restart:
# /etc/systemd/system/heimdall.service
[Unit]
Description = Heimdall Security Scanner
After = network.target postgresql.service
Requires = postgresql.service
[Service]
Type = simple
User = heimdall
Group = heimdall
WorkingDirectory = /opt/heimdall
EnvironmentFile = /opt/heimdall/.env
ExecStart = /opt/heimdall/heimdall
Restart = on-failure
RestartSec = 10
KillMode = mixed
KillSignal = SIGTERM
# Security
NoNewPrivileges = true
PrivateTmp = true
ProtectSystem = strict
ProtectHome = true
ReadWritePaths = /opt/heimdall/repos
# Logging
StandardOutput = journal
StandardError = journal
SyslogIdentifier = heimdall
[Install]
WantedBy = multi-user.target
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable heimdall
sudo systemctl start heimdall
sudo systemctl status heimdall
View logs:
journalctl -u heimdall -f
Monitoring and Logging
Health Checks
Heimdall exposes a health endpoint:
curl http://localhost:8080/health
# {"status":"ok"}
Integrate with monitoring tools:
Prometheus
Uptime Kuma
Healthchecks.io
scrape_configs :
- job_name : 'heimdall'
metrics_path : '/health'
static_configs :
- targets : [ 'localhost:8080' ]
Application Logs
Configure structured logging:
# Detailed logging
RUST_LOG = info, heimdall = debug, actix_web = info
# Production logging (less verbose)
RUST_LOG = info, heimdall = info
# Error-only logging
RUST_LOG = error, heimdall = warn
Ship logs to aggregation services:
# Install promtail
curl -s https://raw.githubusercontent.com/grafana/loki/main/tools/promtail.sh | bash
# Configure promtail.yml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: heimdall
journal:
labels:
job: heimdall
Database Monitoring
-- Monitor active connections
SELECT count ( * ) FROM pg_stat_activity WHERE datname = 'heimdall' ;
-- Check slow queries
SELECT pid, now () - query_start as duration, query
FROM pg_stat_activity
WHERE state = 'active' AND now () - query_start > interval '5 seconds' ;
-- Table sizes
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname || '.' || tablename)) AS size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname || '.' || tablename) DESC ;
Backup Strategies
Database Backups
Automated PostgreSQL backups
#!/bin/bash
# /opt/scripts/backup-heimdall.sh
BACKUP_DIR = "/backups/heimdall"
TIMESTAMP = $( date +%Y%m%d_%H%M%S )
# Create backup
pg_dump -h localhost -U heimdall -Fc heimdall > " $BACKUP_DIR /heimdall_ $TIMESTAMP .dump"
# Compress
gzip " $BACKUP_DIR /heimdall_ $TIMESTAMP .dump"
# Delete backups older than 30 days
find " $BACKUP_DIR " -name "*.dump.gz" -mtime +30 -delete
# Upload to S3 (optional)
aws s3 cp " $BACKUP_DIR /heimdall_ $TIMESTAMP .dump.gz" s3://my-backups/heimdall/
Schedule with cron
# Daily backups at 2 AM
0 2 * * * /opt/scripts/backup-heimdall.sh >> /var/log/heimdall-backup.log 2>&1
Test restore procedure
# Restore from backup
gunzip < /backups/heimdall/heimdall_20260312_020000.dump.gz | \
pg_restore -h localhost -U heimdall -d heimdall_test --clean --if-exists
Repository Data Backups
Cloned repositories are stored in the filesystem:
# Default location: /app/repos (in Docker) or ./repos (native)
# Backup script
#!/bin/bash
REPO_DIR = "/opt/heimdall/repos"
BACKUP_DIR = "/backups/heimdall-repos"
TIMESTAMP = $( date +%Y%m%d_%H%M%S )
tar -czf " $BACKUP_DIR /repos_ $TIMESTAMP .tar.gz" -C " $REPO_DIR " .
# Cleanup old backups
find " $BACKUP_DIR " -name "*.tar.gz" -mtime +7 -delete
Repository data can be safely deleted — Heimdall will re-clone repositories when needed for new scans.
Scaling Considerations
Vertical Scaling
Minimum recommended resources:
Component CPU RAM Storage Heimdall 2 cores 4 GB 20 GB PostgreSQL 2 cores 4 GB 50 GB Total 4 cores 8 GB 70 GB
For heavy usage (multiple concurrent scans):
Component CPU RAM Storage Heimdall 4-8 cores 8-16 GB 50 GB PostgreSQL 4 cores 8 GB 200 GB
Worker Configuration
# Enable background worker
WORKER_ENABLED = true
# Poll interval for queued scans (seconds)
WORKER_POLL_INTERVAL_SECS = 5
# Timeout for stale scans (minutes)
WORKER_STALE_TIMEOUT_MINS = 10
Database Connection Pool
# Increase max connections in postgresql.conf
max_connections = 200
# Use PgBouncer for connection pooling
default_pool_size = 20
max_client_conn = 100
Updates and Maintenance
Updating Heimdall
Pull latest code
cd /opt/heimdall
git pull origin main
Rebuild
cargo build --release
# or
docker compose build
Restart service
sudo systemctl restart heimdall
# or
docker compose restart heimdall
Schema changes are applied automatically on startup.
Verify
curl http://localhost:8080/health
journalctl -u heimdall -n 50
Zero-Downtime Updates
For critical deployments:
Run two instances behind a load balancer
Update one instance at a time
Use health checks to route traffic only to healthy instances
Troubleshooting
High CPU usage
Check for:
Multiple concurrent scans (review scans table)
Stuck worker processes (ps aux | grep heimdall)
Reduce WORKER_POLL_INTERVAL_SECS
High memory usage
Check for:
Large repositories being scanned
Memory leaks in AI provider connections (check logs)
Increase available RAM or limit concurrent scans
Database connection errors
# Check PostgreSQL is running
sudo systemctl status postgresql
# Test connection
psql $DATABASE_URL -c "SELECT 1;"
# Check connection limits
SELECT count ( * ) FROM pg_stat_activity ;
Scan failures
Check logs:
journalctl -u heimdall | grep ERROR
Common issues:
AI provider API key invalid/expired
Docker socket permission denied
Insufficient disk space for cloning repos
Next Steps
Reverse Proxy Setup Configure Nginx with TLS and SSE support
Configuration Reference Complete list of environment variables