Skip to main content
This guide covers production-ready deployment of Heimdall with security hardening, monitoring, and backup strategies.

Pre-Deployment Checklist

1

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.
2

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
3

Set production database

Use a managed database service or a dedicated PostgreSQL instance:
DATABASE_URL=postgres://heimdall:[email protected]:5432/heimdall
4

Configure TLS

Either:
  • Use a reverse proxy (recommended) → set TLS_ENABLED=false
  • Or enable TLS in Heimdall → set TLS_ENABLED=true and provide certificates
5

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

1

Install PostgreSQL

# Ubuntu/Debian
sudo apt update
sudo apt install postgresql-17 postgresql-contrib

# macOS
brew install postgresql@17
2

Create database and user

sudo -u postgres psql
CREATE DATABASE heimdall;
CREATE USER heimdall WITH ENCRYPTED PASSWORD 'your_secure_password';
GRANT ALL PRIVILEGES ON DATABASE heimdall TO heimdall;
\q
3

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
4

Test connection

psql "postgres://heimdall:your_secure_password@localhost:5432/heimdall" -c "SELECT 1;"

Managed Database Services

Heimdall works with managed PostgreSQL services:
DATABASE_URL=postgres://heimdall:[email protected]:5432/heimdall
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:
# 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:
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

1

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/
2

Schedule with cron

# Daily backups at 2 AM
0 2 * * * /opt/scripts/backup-heimdall.sh >> /var/log/heimdall-backup.log 2>&1
3

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:
ComponentCPURAMStorage
Heimdall2 cores4 GB20 GB
PostgreSQL2 cores4 GB50 GB
Total4 cores8 GB70 GB
For heavy usage (multiple concurrent scans):
ComponentCPURAMStorage
Heimdall4-8 cores8-16 GB50 GB
PostgreSQL4 cores8 GB200 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

1

Pull latest code

cd /opt/heimdall
git pull origin main
2

Rebuild

cargo build --release
# or
docker compose build
3

Restart service

sudo systemctl restart heimdall
# or
docker compose restart heimdall
Schema changes are applied automatically on startup.
4

Verify

curl http://localhost:8080/health
journalctl -u heimdall -n 50

Zero-Downtime Updates

For critical deployments:
  1. Run two instances behind a load balancer
  2. Update one instance at a time
  3. 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

Build docs developers (and LLMs) love