Skip to content
Start in Cloud

Docker Compose

Docker Compose gives you a reproducible, version-controlled deployment with persistent storage volumes — your analytics data stays in a named Docker volume on your server, including the shared hitkeep.db and any tenant-local databases created under tenants/*/hitkeep.db.

HitKeep images are published to two registries on every release:

RegistryImage
Docker Hubpascalebeier/hitkeep
GitHub Container Registryghcr.io/pascalebeier/hitkeep

Both registries carry identical, multi-platform images (linux/amd64, linux/arm64) with signed provenance attestations. Use whichever registry suits your network or pull-rate requirements.

Create a compose.yml. This baseline keeps the live data, retention archive, and automatic backup snapshots in separate named volumes.

services:
hitkeep:
image: pascalebeier/hitkeep:latest
container_name: hitkeep
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- hitkeep_data:/var/lib/hitkeep/data
- hitkeep_archive:/var/lib/hitkeep/archive
- hitkeep_backups:/var/lib/hitkeep/backups
environment:
HITKEEP_PUBLIC_URL: ${HITKEEP_PUBLIC_URL:-http://localhost:8080}
HITKEEP_JWT_SECRET: ${HITKEEP_JWT_SECRET:?set HITKEEP_JWT_SECRET in .env}
HITKEEP_DB_PATH: /var/lib/hitkeep/data/hitkeep.db
HITKEEP_DATA_PATH: /var/lib/hitkeep/data
HITKEEP_ARCHIVE_PATH: /var/lib/hitkeep/archive
HITKEEP_BACKUP_PATH: /var/lib/hitkeep/backups
HITKEEP_BACKUP_INTERVAL: ${HITKEEP_BACKUP_INTERVAL:-60}
HITKEEP_BACKUP_RETENTION: ${HITKEEP_BACKUP_RETENTION:-24}
HITKEEP_SPAM_FILTER_AUTO_UPDATE: "true"
HITKEEP_SPAM_FILTER_UPDATE_INTERVAL: ${HITKEEP_SPAM_FILTER_UPDATE_INTERVAL:-1440}
HITKEEP_SPAM_FILTER_PATH: /var/lib/hitkeep/data/spam-filter.json
HITKEEP_MCP_ENABLED: "true"
HITKEEP_MCP_PATH: /mcp
HITKEEP_MCP_MAX_RANGE_DAYS: ${HITKEEP_MCP_MAX_RANGE_DAYS:-366}
# Optional AI model route for Opportunity enrichment. Provider credentials
# use the selected goAI provider's own env vars, such as OPENAI_API_KEY.
HITKEEP_AI_ENABLED: ${HITKEEP_AI_ENABLED:-false}
HITKEEP_AI_PROVIDER: ${HITKEEP_AI_PROVIDER:-}
HITKEEP_AI_MODEL: ${HITKEEP_AI_MODEL:-}
HITKEEP_AI_BASE_URL: ${HITKEEP_AI_BASE_URL:-}
HITKEEP_AI_REGION: ${HITKEEP_AI_REGION:-}
HITKEEP_AI_API_KEY: ${HITKEEP_AI_API_KEY:-}
HITKEEP_AI_REQUEST_LIMIT: ${HITKEEP_AI_REQUEST_LIMIT:-100}
HITKEEP_AI_TOKEN_LIMIT: ${HITKEEP_AI_TOKEN_LIMIT:-100000}
HITKEEP_AI_BUDGET_WINDOW: ${HITKEEP_AI_BUDGET_WINDOW:-1440}
HITKEEP_MAIL_HOST: ${HITKEEP_MAIL_HOST:-}
HITKEEP_MAIL_PORT: ${HITKEEP_MAIL_PORT:-587}
HITKEEP_MAIL_USERNAME: ${HITKEEP_MAIL_USERNAME:-}
HITKEEP_MAIL_PASSWORD: ${HITKEEP_MAIL_PASSWORD:-}
HITKEEP_MAIL_ENCRYPTION: ${HITKEEP_MAIL_ENCRYPTION:-tls}
HITKEEP_MAIL_FROM_ADDRESS: ${HITKEEP_MAIL_FROM_ADDRESS:-hitkeep@localhost}
HITKEEP_MAIL_FROM_NAME: ${HITKEEP_MAIL_FROM_NAME:-HitKeep}
volumes:
hitkeep_data: {}
hitkeep_archive: {}
hitkeep_backups: {}

Create a .env file alongside it (add to .gitignore):

Terminal window
{
echo "HITKEEP_JWT_SECRET=$(openssl rand -hex 32)"
echo "HITKEEP_PUBLIC_URL=http://localhost:8080"
echo "HITKEEP_MAIL_HOST="
echo "HITKEEP_MAIL_PORT=587"
echo "HITKEEP_MAIL_USERNAME="
echo "HITKEEP_MAIL_PASSWORD="
echo "HITKEEP_MAIL_FROM_ADDRESS=hitkeep@localhost"
echo "HITKEEP_MAIL_FROM_NAME=HitKeep"
} > .env

Start it:

Terminal window
docker compose up -d
docker compose logs -f hitkeep

The database file lives inside the hitkeep_data volume. Retention archives live in hitkeep_archive, and automatic database snapshots are written to hitkeep_backups. MCP is exposed at /mcp, but clients still need scoped API client bearer tokens.

MCP is optional in the product and is enabled in this compose example for teams that want read-only assistant/reporting access. Remove HITKEEP_MCP_ENABLED, HITKEEP_MCP_PATH, and HITKEEP_MCP_MAX_RANGE_DAYS if you do not plan to publish an MCP endpoint.

AI provider enrichment is optional and disabled by default. Keep HITKEEP_AI_ENABLED=false until you have chosen a provider/model, configured the selected goAI provider’s credential environment, and set local budget caps. See Opportunity Recommendations and the AI model configuration guide for the exact fields.

Run HitKeep behind a reverse proxy for production HTTPS. Configure Trusted Proxies so real client IPs are used for analytics and rate limiting.

HITKEEP_PUBLIC_URL may include a path prefix. For example, set HITKEEP_PUBLIC_URL=https://www.example.net/hitkeep/ when HitKeep is mounted below an existing site. The proxy should publish the same prefix, such as /hitkeep/*, and forward it to the HitKeep container. HitKeep then serves the dashboard base href, API requests, hk.js, hk-vitals.js, and ingest endpoints below that prefix.

caddy-docker-proxy handles automatic HTTPS (Let’s Encrypt) and generates Caddy config directly from Docker labels. Best practice is to use a dedicated ingress network and trust only that network CIDR in HitKeep.

services:
caddy:
image: lucaslorentz/caddy-docker-proxy:2.9-alpine
container_name: caddy-proxy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
environment:
CADDY_INGRESS_NETWORKS: caddy
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- caddy_data:/data
- caddy_config:/config
networks:
- caddy
labels:
caddy.email: ${CADDY_EMAIL:?set in .env}
hitkeep:
image: pascalebeier/hitkeep:latest
container_name: hitkeep
restart: unless-stopped
networks:
- caddy
volumes:
- hitkeep_data:/var/lib/hitkeep/data
- hitkeep_archive:/var/lib/hitkeep/archive
- hitkeep_backups:/var/lib/hitkeep/backups
environment:
HITKEEP_NODE_NAME: leader
HITKEEP_PUBLIC_URL: ${HITKEEP_PUBLIC_URL:?set in .env}
HITKEEP_JWT_SECRET: ${HITKEEP_JWT_SECRET:?set in .env}
HITKEEP_TRUSTED_PROXIES: ${HITKEEP_TRUSTED_PROXIES:?set to your caddy network CIDR}
HITKEEP_DB_PATH: /var/lib/hitkeep/data/hitkeep.db
HITKEEP_DATA_PATH: /var/lib/hitkeep/data
HITKEEP_ARCHIVE_PATH: /var/lib/hitkeep/archive
HITKEEP_BACKUP_PATH: /var/lib/hitkeep/backups
HITKEEP_BACKUP_INTERVAL: ${HITKEEP_BACKUP_INTERVAL:-60}
HITKEEP_BACKUP_RETENTION: ${HITKEEP_BACKUP_RETENTION:-24}
HITKEEP_SPAM_FILTER_AUTO_UPDATE: "true"
HITKEEP_SPAM_FILTER_UPDATE_INTERVAL: ${HITKEEP_SPAM_FILTER_UPDATE_INTERVAL:-1440}
HITKEEP_SPAM_FILTER_PATH: /var/lib/hitkeep/data/spam-filter.json
HITKEEP_MCP_ENABLED: "true"
HITKEEP_MCP_PATH: /mcp
HITKEEP_MCP_MAX_RANGE_DAYS: ${HITKEEP_MCP_MAX_RANGE_DAYS:-366}
HITKEEP_MAIL_HOST: ${HITKEEP_MAIL_HOST:-}
HITKEEP_MAIL_PORT: ${HITKEEP_MAIL_PORT:-587}
HITKEEP_MAIL_USERNAME: ${HITKEEP_MAIL_USERNAME:-}
HITKEEP_MAIL_PASSWORD: ${HITKEEP_MAIL_PASSWORD:-}
HITKEEP_MAIL_ENCRYPTION: ${HITKEEP_MAIL_ENCRYPTION:-tls}
HITKEEP_MAIL_FROM_ADDRESS: ${HITKEEP_MAIL_FROM_ADDRESS:-hitkeep@localhost}
HITKEEP_MAIL_FROM_NAME: ${HITKEEP_MAIL_FROM_NAME:-HitKeep}
labels:
caddy: ${HITKEEP_HOSTNAME:?set in .env}
caddy.reverse_proxy: "{{upstreams 8080}}"
caddy.encode: "zstd gzip"
volumes:
caddy_data: {}
caddy_config: {}
hitkeep_data: {}
hitkeep_archive: {}
hitkeep_backups: {}
networks:
caddy:
external: true

Create a .env file:

Terminal window
{
echo "HITKEEP_JWT_SECRET=$(openssl rand -hex 32)"
echo "HITKEEP_PUBLIC_URL=https://analytics.example.com"
echo "HITKEEP_HOSTNAME=analytics.example.com"
echo "HITKEEP_TRUSTED_PROXIES=$(docker network inspect caddy --format '{{(index .IPAM.Config 0).Subnet}}')"
echo "HITKEEP_MAIL_HOST=smtp.example.com"
echo "HITKEEP_MAIL_PORT=587"
echo "HITKEEP_MAIL_USERNAME=postmaster@example.com"
echo "HITKEEP_MAIL_PASSWORD=change-me"
echo "HITKEEP_MAIL_FROM_ADDRESS=analytics@example.com"
echo "HITKEEP_MAIL_FROM_NAME=HitKeep"
} > .env

HITKEEP_TRUSTED_PROXIES must be the CIDR of your reverse-proxy network (not 0.0.0.0/0).

For a subdirectory mount, keep HITKEEP_PUBLIC_URL and the reverse-proxy route prefix aligned. The equivalent Caddy route is:

Terminal window
HITKEEP_PUBLIC_URL=https://www.example.net/hitkeep/
HITKEEP_HOSTNAME=www.example.net
www.example.net {
handle /hitkeep* {
reverse_proxy hitkeep:8080
}
encode zstd gzip
}

The examples above use local Docker volumes. To write backup snapshots or retention archives to object storage, replace the local paths with s3:// URLs and add the S3 settings to .env:

Terminal window
HITKEEP_BACKUP_PATH=s3://my-analytics-bucket/hitkeep/backups
HITKEEP_ARCHIVE_PATH=s3://my-analytics-bucket/hitkeep/archive
HITKEEP_S3_REGION=eu-central-1
HITKEEP_S3_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
HITKEEP_S3_SECRET_ACCESS_KEY=change-me

For MinIO, Cloudflare R2, Backblaze B2, or another S3-compatible endpoint, also set HITKEEP_S3_ENDPOINT and, when needed, HITKEEP_S3_URL_STYLE=path. See S3 Backups for provider-specific examples.

Need managed hosting with explicit region choice? HitKeep Cloud → runs HitKeep in your chosen managed region: EU (Frankfurt) or US (Virginia).