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:
| Registry | Image |
|---|---|
| Docker Hub | pascalebeier/hitkeep |
| GitHub Container Registry | ghcr.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.
Quick Start
Section titled “Quick Start”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):
{ 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"} > .envStart it:
docker compose up -ddocker compose logs -f hitkeepThe 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.
Reverse Proxy Configurations
Section titled “Reverse Proxy Configurations”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: trueCreate a .env file:
{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"} > .envHITKEEP_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:
HITKEEP_PUBLIC_URL=https://www.example.net/hitkeep/HITKEEP_HOSTNAME=www.example.netwww.example.net {handle /hitkeep* { reverse_proxy hitkeep:8080}encode zstd gzip}For existing Traefik stacks, expose HitKeep with labels:
services:hitkeep: image: pascalebeier/hitkeep:latest restart: unless-stopped 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:?set in .env} HITKEEP_JWT_SECRET: ${HITKEEP_JWT_SECRET:?set in .env} HITKEEP_TRUSTED_PROXIES: ${HITKEEP_TRUSTED_PROXIES:?set to your traefik 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: - "traefik.enable=true" - "traefik.http.routers.hitkeep.rule=Host(`analytics.example.com`)" - "traefik.http.routers.hitkeep.entrypoints=websecure" - "traefik.http.routers.hitkeep.tls.certresolver=myresolver" - "traefik.http.services.hitkeep.loadbalancer.server.port=8080"
volumes:hitkeep_data: {}hitkeep_archive: {}hitkeep_backups: {}Use S3-compatible backups or archives
Section titled “Use S3-compatible backups or archives”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:
HITKEEP_BACKUP_PATH=s3://my-analytics-bucket/hitkeep/backupsHITKEEP_ARCHIVE_PATH=s3://my-analytics-bucket/hitkeep/archiveHITKEEP_S3_REGION=eu-central-1HITKEEP_S3_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLEHITKEEP_S3_SECRET_ACCESS_KEY=change-meFor 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.
Related
Section titled “Related”Need managed hosting with explicit region choice? HitKeep Cloud → runs HitKeep in your chosen managed region: EU (Frankfurt) or US (Virginia).