Build HitKeep Cloud

YESreplaces $17/mosaves $204/yrback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

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.

estimated effort one sittingthe files for this build are in the project pack

ApplicationThe published pascalebeier/hitkeep image, unmodified, pinned by digestStorageNamed volumes for the data, archive and backup paths the image expectsDatabaseWhatever the image embeds, left aloneNetworkBound to localhost with your existing reverse proxy terminating TLSBackupsThe built-in snapshots plus a copy on another machine, restore-testedUpgradesPinned digest, snapshot first, release notes read, rollback rehearsed

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

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

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

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

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

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

VariableNeededExampleWhere the value comes from
HITKEEP_PUBLIC_URLrequiredhttps://analytics.example.comExactly the address people open, scheme included. A mismatch breaks sessions.
HITKEEP_JWT_SECRETsecretrequiredgenerated-locally-never-committedSession signing. Generated on the host, stored in your password manager, never in git.
HITKEEP_TRUSTED_PROXIESrequired172.18.0.0/16The proxy's network range only. Wider means forged client addresses.
HITKEEP_BACKUP_INTERVALrequired60Snapshot interval in minutes. Set it on purpose rather than accepting the default.
HITKEEP_BACKUP_RETENTIONrequired72Local retention in hours. Short by design, which is why the off-box copy exists.
HITKEEP_IMAGErequiredpascalebeier/hitkeep@sha256:...redactedPinned by digest. The update script changes this line and records the old one.
BACKUP_REMOTE_TARGETrequireds3://backups.example.com/hitkeep/Where the off-box copies land, with more generations than the container keeps.

The build, in order

  1. The published image, running and reachable

    The official container starts, reports ready, and keeps its state in volumes you named.

    1. A tag moves, and it moves at the worst possible time.

      Files compose.yml

      terminal
      docker image inspect --format '{{index .RepoDigests 0}}' pascalebeier/hitkeep:latest
    2. Data, archive and backups are separate concerns with separate growth.

      Files compose.yml

    3. A signing secret that reaches a git history has to be rotated, which logs everyone out.

      Files .env.example.gitignore

      terminal
      openssl rand -hex 32
    done when · tick each as it passes
    watch 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.
  2. A public URL and honest client addresses

    TLS terminates at your proxy and the counts reflect visitors rather than the proxy.

    1. Two certificate stores is one more than anyone maintains.

      Files proxy/hitkeep.conf

    2. A mismatch here shows up as sessions that will not stick.

      Files .env

    3. Then verify the recorded address from a machine outside your network.

      Files compose.yml

      terminal
      curl -H 'X-Forwarded-For: 1.2.3.4' https://analytics.example.com/
    done when · tick each as it passes
    watch 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.
  3. A backup you have actually restored

    A snapshot exists on another machine and has been brought back up at least once.

    1. The defaults are for a demonstration, not for a year of history.

      Files compose.yml.env

    2. A backup nobody has checksummed is a file of unknown provenance.

      Files backup.shbackups/manifest.csv

    3. This is the step everyone documents and nobody performs.

      Files restore.shrunbook.md

    done when · tick each as it passes
    watch 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.
  4. Upgrades you can undo

    A new image can be tried and reversed without losing a day of data.

    1. In that order, because the third step is the only reversible one.

      Files update.sh

    2. A rollback first attempted during an outage is a second outage.

      Files rollback.shrunbook.md

    3. A one-way migration is always mentioned, and always by someone nobody read.

      Files runbook.md

    done when · tick each as it passes
    watch 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.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more analytics.