Build HitKeep Cloud
YESreplaces $17/mosaves $204/yrback to the verdict
A single-container deployment of an MIT-licensed analytics server, where the work is not the application but the proxy configuration, the off-box backup, the tested restore, the pinned upgrade and knowing within a day that collection has stopped.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the published image and manages the named volumes.
Get it Install from your distribution's repository or docker.com. open ↗
Verify
docker compose version prints a version - have readya few pounds a month
Why Analytics has to collect while nobody is watching, which is exactly the property a laptop does not have.
Get it Any small virtual machine, or a home server with a static power supply.
Verify
The host survives a reboot with the service coming back automatically - have readythe price of a domain
Why The public URL value has to match the address people use exactly, and the tracker script is loaded from it.
Get it A subdomain pointed at the host, served by the proxy you already run.
Verify
The hostname resolves and serves a valid certificate - have readypennies per month
Why The container's own snapshots sit on the same disk as the database, which covers a mistake but not a dead machine.
Get it Object storage, another server, or a drive that is not in the same box.
Verify
A snapshot copied there has a matching checksum and has been restored once - decidefree
Why These are the subscription you are replacing, and leaving them undecided is how a self-hosted analytics box quietly stops collecting.
Get it Write down how long data is kept, at what disk level you want warning, and whose phone rings.
Verify
All three appear in the runbook and in the alert configuration
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- compose.yml · one service, restart unless-stopped, the image pinned by digest rather than by a moving tag - volume hitkeep_data mounted at the data path · the live database and the counts you care about - volume hitkeep_archive mounted at the archive path · retention archives, which grow and need a ceiling - volume hitkeep_backups mounted at the backup path · the automatic snapshots, on the same disk as the data they protect - .env holding the public URL and the session signing secret, with a committed .env.example carrying placeholders only - backup-manifest · for each off-box copy: file, size, checksum, the snapshot time, and the date it was last restore-tested - runbook.md · the upgrade, the rollback and the restore, written before you need any of them
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
HITKEEP_PUBLIC_URL | required | https://analytics.example.com | Exactly the address people open, scheme included. A mismatch breaks sessions. |
HITKEEP_JWT_SECRETsecret | required | generated-locally-never-committed | Session signing. Generated on the host, stored in your password manager, never in git. |
HITKEEP_TRUSTED_PROXIES | required | 172.18.0.0/16 | The proxy's network range only. Wider means forged client addresses. |
HITKEEP_BACKUP_INTERVAL | required | 60 | Snapshot interval in minutes. Set it on purpose rather than accepting the default. |
HITKEEP_BACKUP_RETENTION | required | 72 | Local retention in hours. Short by design, which is why the off-box copy exists. |
HITKEEP_IMAGE | required | pascalebeier/hitkeep@sha256:...redacted | Pinned by digest. The update script changes this line and records the old one. |
BACKUP_REMOTE_TARGET | required | s3://backups.example.com/hitkeep/ | Where the off-box copies land, with more generations than the container keeps. |
The build, in order
The published image, running and reachable
The official container starts, reports ready, and keeps its state in volumes you named.
A tag moves, and it moves at the worst possible time.
Files
compose.ymlterminaldocker image inspect --format '{{index .RepoDigests 0}}' pascalebeier/hitkeep:latestData, archive and backups are separate concerns with separate growth.
Files
compose.ymlA signing secret that reaches a git history has to be rotated, which logs everyone out.
Files
.env.example.gitignoreterminalopenssl rand -hex 32
done when · tick each as it passeswatch out- Checking health against the liveness endpoint alone gives you a container marked healthy while the database it needs has not opened, so the dashboard answers and records nothing; the published guide distinguishes the endpoint that confirms the process is alive from the one that confirms the shared database and every open tenant database are ready (checked). Use the readiness endpoint, and treat a healthy container with no incoming events as an incident rather than a quiet week.
A public URL and honest client addresses
TLS terminates at your proxy and the counts reflect visitors rather than the proxy.
Two certificate stores is one more than anyone maintains.
Files
proxy/hitkeep.confA mismatch here shows up as sessions that will not stick.
Files
.envThen verify the recorded address from a machine outside your network.
Files
compose.ymlterminalcurl -H 'X-Forwarded-For: 1.2.3.4' https://analytics.example.com/
done when · tick each as it passeswatch out- Leaving the trusted proxy setting empty records every visit as coming from the proxy, so every visitor looks like one visitor and the geography is your own data centre; setting it to accept any address instead lets anyone forge a client address by sending a header. Set it to the proxy's own range and check the recorded address from a second machine.
A backup you have actually restored
A snapshot exists on another machine and has been brought back up at least once.
The defaults are for a demonstration, not for a year of history.
Files
compose.yml.envA backup nobody has checksummed is a file of unknown provenance.
Files
backup.shbackups/manifest.csvThis is the step everyone documents and nobody performs.
Files
restore.shrunbook.md
done when · tick each as it passeswatch out- The built-in snapshots live in a volume on the same disk as the database, so they survive a bad upgrade and not a failed disk, a deleted volume or an accidental compose down with the volume flag; and a retention measured in hours means a corruption noticed on Tuesday has already aged out by Wednesday. Copy off the box on a schedule, keep more generations there than the container keeps locally, and record when each was last restored.
Upgrades you can undo
A new image can be tried and reversed without losing a day of data.
In that order, because the third step is the only reversible one.
Files
update.shA rollback first attempted during an outage is a second outage.
Files
rollback.shrunbook.mdA one-way migration is always mentioned, and always by someone nobody read.
Files
runbook.md
done when · tick each as it passeswatch out- Running an unattended pull of a moving tag on a schedule eventually applies a release that migrates the stored format in one direction, and since the previous image can no longer open the migrated files the only way back is the snapshot you did not take. Snapshot first, record the digest you are leaving, and never let an automatic pull touch this box.
Knowing it is still collecting, and being able to leaveproduct builder
Silence is detected within a day, disk growth is bounded, and the data can walk out.
A page returning two hundred proves the page returns two hundred.
Files
checks/synthetic.shThe two failures that are silent until they are expensive.
Files
checks/alerts.ymlrunbook.mdThe exit is worth building while you are not in a hurry to leave.
Files
export.sh
done when · tick each as it passeswatch out- Monitoring the dashboard with a plain page check keeps reporting green while a broken tracker tag, a blocked script or a proxy misconfiguration means nothing is being recorded, and the gap is discovered a month later when someone asks about last quarter. Send a synthetic event and assert it lands, so the check tests the thing you actually want.
That is the whole plan for HitKeep Cloud. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Any reimplementation of the application.
- Modifications to the published image.
- An external database, queue or cache.
- Accounts, billing or an uptime promise.
- managed upgrades and monitoring
- encrypted regional backups
- managed email delivery
- vendor support and incident response
- A second site on the same instance, once the restore drill has been run with real data in it
- Shipping the export into a warehouse, so long-term analysis never depends on this box being up
- A staging stack fed from a restored snapshot, which makes every upgrade a rehearsal first
Need the files? The project pack on the verdict page hands your agent the whole brief · more analytics.