Skip to main content

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
Debian Users

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"
danger

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
Important

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:

VariableRequiredDefaultDescription
APP_DOMAINYes--Domain, hostname, or IP address for accessing Hawkra
POSTGRES_PASSWORDYes--PostgreSQL database password
JWT_SECRETYes--JWT token signing key (generate with openssl rand -hex 32)
MASTER_ENCRYPTION_KEYYes--Master encryption key for DEKs (generate with openssl rand -hex 32)
FRONTEND_URLYes--Full URL for the frontend (e.g., https://your-domain.com)
BACKEND_URLYes--Full URL for the backend (e.g., https://your-domain.com)
CORS_ALLOWED_ORIGINSYes--Allowed CORS origins, must match FRONTEND_URL
COOKIE_DOMAINYes--Domain for auth cookies, must match APP_DOMAIN
COOKIE_SECURENotrueSet Secure flag on cookies (always true for HTTPS)
JWT_EXPIRY_SECONDSNo3600Access token lifetime (1 hour)
JWT_REFRESH_EXPIRY_SECONDSNo604800Refresh token lifetime (7 days)
LETS_ENCRYPTNofalseEnable automatic Let's Encrypt certificates
VERSIONNolatestPin container images to a specific release
RUST_LOGNoinfo,hawkra_backend=infoBackend log level
API_RATE_LIMIT_PER_MINUTENo500API 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

ServiceImageMemory LimitPurpose
postgrespostgres:16-alpine--Application database with health check
redisredis:7-alpine--Caching and job queue with health check
backendghcr.io/reconhawk/hawkra-backend--API server with nmap capabilities
frontendghcr.io/reconhawk/hawkra-frontend--Web application
caddycaddy:2-alpine--Reverse proxy with automatic HTTPS
Backend Capabilities

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.

TalonStrike and Docker Socket

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
How the Caddyfile Template Works

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:

  1. Ensure ports 80 and 443 are reachable from the internet
  2. Add the following to your .env file:
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.

warning

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-alpine
  • redis:7-alpine
  • ghcr.io/reconhawk/hawkra-backend
  • ghcr.io/reconhawk/hawkra-frontend
  • caddy:2-alpine
Version Pinning

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:

  1. PostgreSQL and Redis start first
  2. Backend starts after PostgreSQL and Redis are healthy
  3. Frontend starts after the backend
  4. 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.

danger

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

  1. If using self-signed certificates, accept the browser security warning.
  2. Log in with admin@hawkra.local and the password from Step 13.
  3. You will be redirected to the License Setup page.
  4. Upload the license file provided with your purchase, or enter your license key.
  5. Click Complete Setup.
tip

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

IssueCauseSolution
Browser shows "connection refused"Containers not runningRun docker compose ps to check status. Wait 30-60 seconds after startup.
Certificate warning in browserSelf-signed certificatesExpected behavior. Accept the warning to proceed.
Caddy fails to startMissing APP_DOMAIN or non-executable entrypointVerify APP_DOMAIN is set in .env. Run chmod +x caddy/docker-entrypoint.sh.
503 on all pagesLicense setup not completeLog in as admin and complete the license setup flow.
Admin password not in logsDatabase volume exists from a prior runThe password is only printed on first boot. Reset with docker compose down -v (destroys all data).
Backend cannot connect to databaseWrong POSTGRES_PASSWORDVerify the password in .env matches. Check: docker compose logs postgres.
CORS errors in browserURL mismatchEnsure CORS_ALLOWED_ORIGINS exactly matches your browser URL including https://.
License upload failsPermission denied on license directoryRun sudo chmod 755 /opt/hawkra/license and restart.
Domain not resolvingMissing /etc/hosts entryAdd the entry on both the server and the client machine.
Nmap scans failMissing kernel capabilitiesVerify 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
danger

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:

  1. License Setup -- Activate your instance with your license key.
  2. Configure AI -- Set up a Gemini API key or local LLM through Admin > Settings > AI Configuration.
  3. Configure SMTP -- Enable email features through Admin > Settings > Email (SMTP).
  4. Invite Users -- Add team members through Workspace Settings > Members.
  5. Back Up -- Set up a backup schedule for the database and backend_config volume. See the Overview for critical volume details.