Manual Installation
This guide walks you through every step of deploying Hawkra Self-Hosted manually. Use this approach when you need full control over the configuration, are working in a restricted environment, or prefer to understand each component before deploying.
Step 1: Install Docker and Docker Compose
Ubuntu / Debian
Install prerequisite packages:
sudo apt update
sudo apt install -y ca-certificates curl gnupg
Add the Docker GPG key and repository:
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Replace ubuntu with debian in both URLs above (the GPG key URL and the repository URL).
Install Docker Engine and Compose:
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Fedora / RHEL 9+
sudo dnf -y install dnf-plugins-core
sudo dnf config-manager addrepo --from-repofile=https://download.docker.com/linux/fedora/docker-ce.repo
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Enable and Verify
Start the Docker daemon and verify the installation:
sudo systemctl enable --now docker
sudo docker run --rm hello-world
Optionally, add your user to the docker group to run Docker commands without sudo:
sudo usermod -aG docker $USER
newgrp docker
Verify Docker Compose is available:
docker compose version
You should see version 2.0 or later.
Step 2: Create Directory Structure
Create the Hawkra installation directory and subdirectories:
sudo mkdir -p /opt/hawkra/{caddy,certs,license}
cd /opt/hawkra
The final directory structure will look like this:
/opt/hawkra/
.env
docker-compose.selfhosted.yml
Caddyfile
caddy/
docker-entrypoint.sh
certs/ # Empty unless using custom certificates
license/ # Place your license file here
Step 3: Create the Environment File
Generate secure random values for the required secrets:
POSTGRES_PW=$(openssl rand -base64 24)
JWT_SECRET=$(openssl rand -hex 32)
MASTER_KEY=$(openssl rand -hex 32)
echo "Database password: $POSTGRES_PW"
echo "JWT secret: $JWT_SECRET"
echo "Master key: $MASTER_KEY"
Save these values in a secure location before proceeding. If you lose the master encryption key later, all encrypted data becomes permanently unrecoverable.
Create the .env file. Replace your-domain.com with your actual domain, hostname, or IP address:
sudo tee /opt/hawkra/.env > /dev/null <<EOF
# =============================================================================
# Hawkra Self-Hosted Configuration
# =============================================================================
# Domain (REQUIRED)
# The domain, hostname, or IP address used to access Hawkra.
# Examples: hawkra.yourcompany.com, hawkra.local, 192.168.1.100
APP_DOMAIN=your-domain.com
# -----------------------------------------------------------------------------
# Database
# -----------------------------------------------------------------------------
POSTGRES_PASSWORD=$POSTGRES_PW
# The DATABASE_URL is constructed automatically in docker-compose.yml.
# Do not set it here unless you are using an external database.
# -----------------------------------------------------------------------------
# Authentication
# -----------------------------------------------------------------------------
# JWT signing secret. Generated with: openssl rand -hex 32
JWT_SECRET=$JWT_SECRET
# Access token lifetime in seconds (default: 1 hour)
JWT_EXPIRY_SECONDS=3600
# Refresh token lifetime in seconds (default: 7 days)
JWT_REFRESH_EXPIRY_SECONDS=604800
# -----------------------------------------------------------------------------
# Encryption
# -----------------------------------------------------------------------------
# Master key for encrypting per-workspace data encryption keys (DEKs).
# Generated with: openssl rand -hex 32
MASTER_ENCRYPTION_KEY=$MASTER_KEY
# Maximum number of decrypted workspace keys held in memory (default: 1000).
# Each entry is ~32 bytes. Increase for many concurrent active workspaces.
# DEK_CACHE_CAPACITY=1000
# -----------------------------------------------------------------------------
# URLs and Networking
# -----------------------------------------------------------------------------
FRONTEND_URL=https://your-domain.com
BACKEND_URL=https://your-domain.com
CORS_ALLOWED_ORIGINS=https://your-domain.com
COOKIE_DOMAIN=your-domain.com
COOKIE_SECURE=true
# -----------------------------------------------------------------------------
# TLS
# -----------------------------------------------------------------------------
# Uncomment to use Let's Encrypt for automatic TLS certificates.
# Requires a public domain pointing to this server and ports 80/443 open.
# LETS_ENCRYPT=true
# -----------------------------------------------------------------------------
# Self-Hosted License
# -----------------------------------------------------------------------------
# Annual license key provided with your purchase.
# Alternatively, place your license.key file in /opt/hawkra/license/
# SELFHOSTED_ANNUAL_KEY=your-license-key-here
# -----------------------------------------------------------------------------
# Version Pinning (optional)
# -----------------------------------------------------------------------------
# Pin to a specific release version. Defaults to latest.
# VERSION=1.0.0
# -----------------------------------------------------------------------------
# Rate Limiting
# -----------------------------------------------------------------------------
# API_RATE_LIMIT_PER_MINUTE=500
# -----------------------------------------------------------------------------
# Storage
# -----------------------------------------------------------------------------
# Storage backend: "local" (default) or "s3"
# STORAGE_BACKEND=local
# Self-hosted storage limits
# STORAGE_MAX_FILE_SIZE_MB=100
# STORAGE_MAX_TOTAL_MB=10000
# S3 configuration (when STORAGE_BACKEND=s3)
# STORAGE_S3_BUCKET=your-bucket
# STORAGE_S3_REGION=us-east-1
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
# -----------------------------------------------------------------------------
# AI Assistant (optional)
# -----------------------------------------------------------------------------
# LLM_MODE: "cloud" (default) uses Gemini, "local" uses a local LLM server
# LLM_MODE=cloud
# Cloud mode: Google Gemini
# GEMINI_API_KEY=
# GEMINI_MODEL=gemini-2.0-flash
# Local mode: OpenAI-compatible server (Ollama, llama.cpp, vLLM)
# When running in Docker, use the host's LAN IP or Docker host gateway.
# Do NOT use localhost or 127.0.0.1 -- that refers to the container itself.
# LOCAL_LLM_SERVER=http://10.10.10.12:8080
# -----------------------------------------------------------------------------
# OSINT API Keys (optional)
# -----------------------------------------------------------------------------
# SHODAN_API_KEY=
# HIBP_API_KEY=
# GEOIP_API_KEY=
# Brave Search API (optional -- cyber security news for daily AI briefings)
# BRAVE_SEARCH_API_KEY=
# -----------------------------------------------------------------------------
# SMTP Configuration (optional -- enables email features)
# -----------------------------------------------------------------------------
# SMTP_HOST=smtp.yourprovider.com
# SMTP_PORT=587
# SMTP_ENCRYPTION=starttls
# SMTP_USERNAME=
# SMTP_PASSWORD=
# SMTP_FROM_ADDRESS=hawkra@yourcompany.com
# -----------------------------------------------------------------------------
# Logging
# -----------------------------------------------------------------------------
# RUST_LOG=info,hawkra_backend=info
EOF
Restrict the file permissions so only root can read it:
sudo chmod 600 /opt/hawkra/.env
Keep your .env file secure. It contains sensitive secrets including the master encryption key, JWT secret, and database password. Never commit it to version control or share it over unencrypted channels.
Environment Variable Reference
The following table lists all required and commonly used environment variables:
| Variable | Required | Default | Description |
|---|---|---|---|
APP_DOMAIN | Yes | -- | Domain, hostname, or IP address for accessing Hawkra |
POSTGRES_PASSWORD | Yes | -- | PostgreSQL database password |
JWT_SECRET | Yes | -- | JWT token signing key (generate with openssl rand -hex 32) |
MASTER_ENCRYPTION_KEY | Yes | -- | Master encryption key for DEKs (generate with openssl rand -hex 32) |
FRONTEND_URL | Yes | -- | Full URL for the frontend (e.g., https://your-domain.com) |
BACKEND_URL | Yes | -- | Full URL for the backend (e.g., https://your-domain.com) |
CORS_ALLOWED_ORIGINS | Yes | -- | Allowed CORS origins, must match FRONTEND_URL |
COOKIE_DOMAIN | Yes | -- | Domain for auth cookies, must match APP_DOMAIN |
COOKIE_SECURE | No | true | Set Secure flag on cookies (always true for HTTPS) |
JWT_EXPIRY_SECONDS | No | 3600 | Access token lifetime (1 hour) |
JWT_REFRESH_EXPIRY_SECONDS | No | 604800 | Refresh token lifetime (7 days) |
LETS_ENCRYPT | No | false | Enable automatic Let's Encrypt certificates |
VERSION | No | latest | Pin container images to a specific release |
RUST_LOG | No | info,hawkra_backend=info | Backend log level |
API_RATE_LIMIT_PER_MINUTE | No | 500 | API rate limit per client IP |
Step 4: Create the Docker Compose File
Create the docker-compose.selfhosted.yml file:
sudo tee /opt/hawkra/docker-compose.selfhosted.yml > /dev/null <<'EOF'
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: hawkra
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-hawkra}
POSTGRES_DB: hawkra
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U hawkra"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
backend:
image: ghcr.io/reconhawk/hawkra-backend:${VERSION:-latest}
cap_add:
- NET_RAW
- NET_ADMIN
- NET_BIND_SERVICE
env_file: .env
environment:
DATABASE_URL: postgres://hawkra:${POSTGRES_PASSWORD:-hawkra}@postgres:5432/hawkra
REDIS_URL: redis://redis:6379
RUST_LOG: info,hawkra_backend=info
AGENT_BINARY_PATH: /app/agent-binaries
BACKEND_URL: https://${APP_DOMAIN:-localhost}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
volumes:
- file_storage:/app/data/files
- backend_config:/app/config
- ./license:/app/license
- /var/run/docker.sock:/var/run/docker.sock
logging:
options:
max-size: "10m"
max-file: "3"
frontend:
image: ghcr.io/reconhawk/hawkra-frontend:${VERSION:-latest}
environment:
NEXT_PUBLIC_API_URL: https://${APP_DOMAIN:-localhost}
NEXT_PUBLIC_SITE_URL: https://${APP_DOMAIN:-localhost}
NEXT_PUBLIC_EDITION: selfhosted
depends_on:
- backend
restart: unless-stopped
logging:
options:
max-size: "10m"
max-file: "3"
caddy:
image: caddy:2-alpine
entrypoint: /docker-entrypoint.sh
environment:
APP_DOMAIN: ${APP_DOMAIN:-localhost}
LETS_ENCRYPT: ${LETS_ENCRYPT:-false}
ports:
- "80:80"
- "443:443"
volumes:
- ./caddy/docker-entrypoint.sh:/docker-entrypoint.sh:ro
- ./Caddyfile:/etc/caddy/Caddyfile.template:ro
- ./certs:/certs:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- frontend
- backend
restart: unless-stopped
volumes:
postgres_data:
file_storage:
backend_config:
caddy_data:
caddy_config:
EOF
Service Details
| Service | Image | Memory Limit | Purpose |
|---|---|---|---|
postgres | postgres:16-alpine | -- | Application database with health check |
redis | redis:7-alpine | -- | Caching and job queue with health check |
backend | ghcr.io/reconhawk/hawkra-backend | -- | API server with nmap capabilities |
frontend | ghcr.io/reconhawk/hawkra-frontend | -- | Web application |
caddy | caddy:2-alpine | -- | Reverse proxy with automatic HTTPS |
The backend service requires Linux kernel capabilities NET_RAW, NET_ADMIN, and NET_BIND_SERVICE. These are needed for nmap network scanning functionality. Without these capabilities, nmap-based scans will fail.
The backend mounts /var/run/docker.sock to enable TalonStrike, which manages Kali Linux containers for advanced active reconnaissance. If you do not plan to use TalonStrike, you can remove the Docker socket mount for a stricter security posture.
Step 5: Create the Caddyfile
Create the Caddy reverse proxy configuration template:
sudo tee /opt/hawkra/Caddyfile > /dev/null <<'CADDYEOF'
APP_DOMAIN {
# TLS_DIRECTIVE
# Compression
encode gzip
# Security headers
header {
Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
X-XSS-Protection "0"
Cross-Origin-Resource-Policy "same-origin"
Cross-Origin-Opener-Policy "same-origin"
Cross-Origin-Embedder-Policy "require-corp"
-Server
}
# Agent poll/connect — small payloads only (8KB)
@agent_small {
path /api/agent/connect
path /api/agent/poll
}
handle @agent_small {
request_body {
max_size 8KB
}
reverse_proxy backend:3001
}
# Agent task results — medium payloads (10MB)
@agent_tasks {
path /api/agent/tasks/*
}
handle @agent_tasks {
request_body {
max_size 10MB
}
reverse_proxy backend:3001
}
# All other API routes (1000MB for file uploads)
handle /api/* {
request_body {
max_size 1000MB
}
reverse_proxy backend:3001
}
# Frontend
handle {
reverse_proxy frontend:3000
}
}
CADDYEOF
The Caddyfile is a template. At startup, the Caddy entrypoint script replaces APP_DOMAIN with your configured domain and # TLS_DIRECTIVE with the appropriate TLS configuration based on your setup (self-signed, custom certificates, or Let's Encrypt). You do not need to edit the Caddyfile directly.
Step 6: Create the Caddy Entrypoint Script
Create the entrypoint script that configures TLS at startup:
sudo tee /opt/hawkra/caddy/docker-entrypoint.sh > /dev/null <<'ENTRYEOF'
#!/bin/sh
set -e
CADDYFILE_TEMPLATE="/etc/caddy/Caddyfile.template"
CADDYFILE="/etc/caddy/Caddyfile"
CERT_DIR="/certs"
# Validate required domain environment variable
if [ -z "$APP_DOMAIN" ]; then
echo "ERROR: APP_DOMAIN environment variable is required"
echo "Set it in your .env file (e.g., APP_DOMAIN=hawkra.local)"
exit 1
fi
# Determine TLS mode
if [ "$LETS_ENCRYPT" = "true" ]; then
echo "Let's Encrypt mode — Caddy will auto-provision certificates"
TLS_LINE=""
elif [ -f "$CERT_DIR/cert.pem" ] && [ -f "$CERT_DIR/key.pem" ]; then
echo "Custom certificates found in $CERT_DIR — using provided certs"
TLS_LINE="tls /certs/cert.pem /certs/key.pem"
else
echo "No custom certificates found — using auto-generated self-signed certs"
TLS_LINE="tls internal"
fi
# Substitute domain placeholder and TLS directive
sed -e "s|APP_DOMAIN|$APP_DOMAIN|g" \
-e "s|# TLS_DIRECTIVE|$TLS_LINE|g" \
"$CADDYFILE_TEMPLATE" > "$CADDYFILE"
echo "Caddy configured for domain: $APP_DOMAIN"
exec caddy run --config "$CADDYFILE" --adapter caddyfile
ENTRYEOF
Make the entrypoint script executable:
sudo chmod +x /opt/hawkra/caddy/docker-entrypoint.sh
Step 7: Configure TLS Certificates (Optional)
Choose one of the following TLS options. If you skip this step, Caddy generates self-signed certificates automatically.
Option A: Self-Signed Certificates (Default)
No action required. Caddy generates self-signed certificates at startup. Browsers will display a certificate warning that you can accept to proceed.
Option B: Custom Certificates
If you have certificates from a corporate CA or commercial provider, place them in the certs/ directory:
sudo cp /path/to/your/cert.pem /opt/hawkra/certs/cert.pem
sudo cp /path/to/your/key.pem /opt/hawkra/certs/key.pem
sudo chmod 644 /opt/hawkra/certs/cert.pem
sudo chmod 600 /opt/hawkra/certs/key.pem
The entrypoint script detects these files automatically at startup.
Option C: Let's Encrypt
For public-facing servers with a DNS record pointing to the server:
- Ensure ports 80 and 443 are reachable from the internet
- Add the following to your
.envfile:
echo "LETS_ENCRYPT=true" | sudo tee -a /opt/hawkra/.env
Caddy handles certificate provisioning and renewal automatically.
Step 8: Configure DNS or Hostname
If your APP_DOMAIN is a real domain with a DNS record pointing to the server, skip this step.
For hostnames without DNS records, add an entry to /etc/hosts on the server and on every client machine that will access Hawkra:
sudo nano /etc/hosts
Add a line mapping the server IP to your hostname:
192.168.1.100 hawkra.yourcompany.local
Replace 192.168.1.100 with the server's actual IP address and hawkra.yourcompany.local with the value you set for APP_DOMAIN in your .env file.
This must be done before starting the containers. Caddy generates TLS certificates at startup using the configured domain.
Step 9: Set File Permissions
Ensure the license directory is writable and the Caddy entrypoint is executable:
sudo chmod 755 /opt/hawkra/license
sudo chmod +x /opt/hawkra/caddy/docker-entrypoint.sh
If you pre-placed a license file:
sudo chmod 644 /opt/hawkra/license/license.key
Step 10: Pull Container Images
Download all container images before starting the services:
cd /opt/hawkra
sudo docker compose -f docker-compose.selfhosted.yml pull
This downloads the following images:
postgres:16-alpineredis:7-alpineghcr.io/reconhawk/hawkra-backendghcr.io/reconhawk/hawkra-frontendcaddy:2-alpine
To pin to a specific release, set the VERSION variable in your .env file:
echo "VERSION=1.0.0" | sudo tee -a /opt/hawkra/.env
This ensures docker compose pull always fetches that specific version.
Step 11: Start Services
Start all services in detached mode:
cd /opt/hawkra
sudo docker compose -f docker-compose.selfhosted.yml up -d
Docker Compose starts the services in dependency order:
- PostgreSQL and Redis start first
- Backend starts after PostgreSQL and Redis are healthy
- Frontend starts after the backend
- Caddy starts after both the frontend and backend
Step 12: Verify Startup
Check that all services are running:
sudo docker compose -f docker-compose.selfhosted.yml ps
All services should show a status of Up or Up (healthy). Example output:
NAME SERVICE STATUS PORTS
hawkra-backend-1 backend Up (healthy)
hawkra-caddy-1 caddy Up 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp
hawkra-frontend-1 frontend Up
hawkra-postgres-1 postgres Up (healthy)
hawkra-redis-1 redis Up (healthy)
Verify the backend is listening:
sudo docker compose -f docker-compose.selfhosted.yml logs backend | grep "listening"
Step 13: Get the Initial Admin Password
On first boot, the backend generates a default admin account with a random password. Retrieve it from the logs:
sudo docker compose -f docker-compose.selfhosted.yml logs backend | grep -i "admin password"
The default admin account email is admin@hawkra.local.
The admin password is printed to the logs only on first boot. If the database volume already exists from a previous start, the password was logged during that initial run. Save it immediately.
Step 14: Access the Platform
Open your browser and navigate to https://your-domain.com (replacing with your actual APP_DOMAIN value).
- If using self-signed certificates, accept the browser security warning.
- Log in with
admin@hawkra.localand the password from Step 13. - You will be redirected to the License Setup page.
- Upload the license file provided with your purchase, or enter your license key.
- Click Complete Setup.
If you pre-placed the license file at /opt/hawkra/license/license.key in Step 2, it will be detected automatically and you can skip the upload.
The platform is now live. Change the default admin password immediately under Account Settings.
Using an IP Address Instead of a Domain
If you are accessing Hawkra by IP address rather than a domain name, adjust the following in your .env file:
APP_DOMAIN=192.168.1.100
FRONTEND_URL=https://192.168.1.100
BACKEND_URL=https://192.168.1.100
CORS_ALLOWED_ORIGINS=https://192.168.1.100
COOKIE_DOMAIN=192.168.1.100
Caddy will generate a self-signed certificate for the IP address. You will need to accept the browser certificate warning each time you access the platform.
Troubleshooting
View Logs
# All services
sudo docker compose -f docker-compose.selfhosted.yml logs -f
# Specific service
sudo docker compose -f docker-compose.selfhosted.yml logs -f backend
sudo docker compose -f docker-compose.selfhosted.yml logs -f caddy
sudo docker compose -f docker-compose.selfhosted.yml logs -f postgres
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| Browser shows "connection refused" | Containers not running | Run docker compose ps to check status. Wait 30-60 seconds after startup. |
| Certificate warning in browser | Self-signed certificates | Expected behavior. Accept the warning to proceed. |
| Caddy fails to start | Missing APP_DOMAIN or non-executable entrypoint | Verify APP_DOMAIN is set in .env. Run chmod +x caddy/docker-entrypoint.sh. |
| 503 on all pages | License setup not complete | Log in as admin and complete the license setup flow. |
| Admin password not in logs | Database volume exists from a prior run | The password is only printed on first boot. Reset with docker compose down -v (destroys all data). |
| Backend cannot connect to database | Wrong POSTGRES_PASSWORD | Verify the password in .env matches. Check: docker compose logs postgres. |
| CORS errors in browser | URL mismatch | Ensure CORS_ALLOWED_ORIGINS exactly matches your browser URL including https://. |
| License upload fails | Permission denied on license directory | Run sudo chmod 755 /opt/hawkra/license and restart. |
| Domain not resolving | Missing /etc/hosts entry | Add the entry on both the server and the client machine. |
| Nmap scans fail | Missing kernel capabilities | Verify the backend service has cap_add: [NET_RAW, NET_ADMIN, NET_BIND_SERVICE]. |
Restart All Services
cd /opt/hawkra
sudo docker compose -f docker-compose.selfhosted.yml down
sudo docker compose -f docker-compose.selfhosted.yml up -d
Full Reset (Destroys All Data)
cd /opt/hawkra
sudo docker compose -f docker-compose.selfhosted.yml down -v
sudo docker compose -f docker-compose.selfhosted.yml up -d
The -v flag removes all Docker volumes including the database, encryption keys, and uploaded files. All data will be permanently and irreversibly lost. Only use this as a last resort.
Next Steps
After completing the installation:
- License Setup -- Activate your instance with your license key.
- Configure AI -- Set up a Gemini API key or local LLM through Admin > Settings > AI Configuration.
- Configure SMTP -- Enable email features through Admin > Settings > Email (SMTP).
- Invite Users -- Add team members through Workspace Settings > Members.
- Back Up -- Set up a backup schedule for the database and
backend_configvolume. See the Overview for critical volume details.