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:

services:
hitkeep:
# Also available as: ghcr.io/pascalebeier/hitkeep:latest
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
environment:
- HITKEEP_JWT_SECRET=${HITKEEP_JWT_SECRET}
command:
- "-public-url=http://localhost:8080"
- "-archive-path=/var/lib/hitkeep/archive"
volumes:
hitkeep_data: {}
hitkeep_archive: {}

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

Terminal window
echo "HITKEEP_JWT_SECRET=$(openssl rand -hex 32)" > .env

Start it:

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

The database file lives inside the hitkeep_data volume. Back it up by copying the file out of the volume or using docker cp.

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

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
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: mail@example.com
hitkeep:
image: pascalebeier/hitkeep:latest
restart: unless-stopped
networks:
- caddy
volumes:
- hitkeep_data:/var/lib/hitkeep/data
- hitkeep_archive:/var/lib/hitkeep/archive
environment:
- HITKEEP_JWT_SECRET=${HITKEEP_JWT_SECRET:?set in .env}
- HITKEEP_TRUSTED_PROXIES=${HITKEEP_TRUSTED_PROXIES:?set to your caddy network CIDR}
command:
- "-public-url=https://analytics.example.com"
- "-archive-path=/var/lib/hitkeep/archive"
labels:
caddy: analytics.example.com
caddy.reverse_proxy: "{{upstreams 8080}}"
caddy.encode: "zstd gzip"
volumes:
caddy_data: {}
caddy_config: {}
hitkeep_data: {}
hitkeep_archive: {}
networks:
caddy:
external: true

Create a .env file:

Terminal window
{
echo "HITKEEP_JWT_SECRET=$(openssl rand -hex 32)"
echo "HITKEEP_TRUSTED_PROXIES=$(docker network inspect caddy --format '{{(index .IPAM.Config 0).Subnet}}')"
} > .env

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

Need managed hosting without giving up data sovereignty? HitKeep Cloud → runs the same Docker image in your chosen sovereign region — EU (Frankfurt, GDPR) or US (Virginia).