The Docker Compose stack
This folder runs Hardcount on one machine: Postgres, Redis, the Hardcount API, the dashboard, and nginx in front of them.
- Try it on your own computer: run
./quickstart.shhere. It uses the published images and local mode, so sign-in works athttp://localhost:8080in every browser. Walkthrough: quick start. - Install it on a server: follow Production on one VM. It installs a pinned release, then adds HTTPS, email, Stripe, nightly backups and a go-live checklist.
Build from source instead
Section titled “Build from source instead”Production on one VM runs the published release images, set by HARDCOUNT_IMAGE and HARDCOUNT_DASHBOARD_IMAGE in .env. Leave those two lines out, and docker compose up -d builds both images from your checkout. The first build takes a few minutes. Everything else is the same.
To upgrade a stack built from source, run these in deploy/compose. This example moves to v0.9.1.
A Velox install (Hardcount’s name before v0.9.0) cannot be upgraded. Its .env names VELOX_* settings, so docker compose build in step 2 stops with required variable HARDCOUNT_APP_DB_PASSWORD is missing a value. Install Hardcount fresh, with an empty database: a new VM, or a new Compose data volume (why).
-
Take a backup. Production on one VM, step 7 sets up this command. If your checkout is not in
/opt/hardcount, change its two/opt/hardcountpaths.Terminal window sudo hardcount-backupYou see:
Verified: yes. -
Check out the release and build it.
Terminal window git fetch --tagsgit checkout v0.9.1docker compose buildYou see:
HEAD is now at …from git, then the two images build. -
See what the upgrade will change.
migrate statusonly reads.Terminal window docker compose run --rm hardcount-api migrate statusYou see: the plan. If it says
ahead, this install ran newer code than the release: check out a newer release and build again. If it saysno migration record, stop: the upgrade will not run (why). -
Start the new build. It applies the database upgrades as it starts.
Terminal window docker compose up -dYou see: after a few seconds,
curl -fsS http://localhost/health/ready(localhost:8080ifNGINX_HTTP_PORT=127.0.0.1:8080, as in Production on one VM) answers"status":"ok"again.
Read the changelog upgrade notes first, as for any upgrade. If the upgrade stops with an error, see If something goes wrong.
Sign in before HTTPS
Section titled “Sign in before HTTPS”With APP_ENV=production, the default in this stack, the sign-in cookie is always marked Secure. Browsers keep a Secure cookie on HTTPS. At http://localhost, Chrome and Firefox keep it too; Safari does not. At any other plain-HTTP address, such as http://<server-ip>/, no browser keeps it, and the sign-in page says so.
Until you add HTTPS, sign in at http://localhost/ on the server in Chrome or Firefox, or through an SSH tunnel from your laptop:
ssh -L 8080:localhost:80 you@server # localhost:8080 instead if NGINX_HTTP_PORT=127.0.0.1:8080# then open http://localhost:8080/Day-to-day commands
Section titled “Day-to-day commands”| To | Run |
|---|---|
| See logs | docker compose logs -f |
| Restart the API | docker compose restart hardcount-api |
| Stop, keeping data | docker compose stop |
| Stop and delete all data | docker compose down -v |
| See the database’s version and the upgrades still to apply | docker compose run --rm hardcount-api migrate status |
| Open a database shell | docker compose exec postgres psql -U hardcount -d hardcount |
To upgrade, see Upgrade to a new release, or Build from source instead.
If something goes wrong
Section titled “If something goes wrong”To see the settings the API container runs with, ask Docker. The image has no shell and no env command, so docker compose exec hardcount-api env fails.
docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' "$(docker compose ps -aq hardcount-api)"The output holds passwords and keys, so keep it private.
The API exits with HARDCOUNT_ENCRYPTION_KEY is required in production.
Set the key in .env (openssl rand -hex 32) and run docker compose up -d again.
The API exits with APP_DATABASE_URL is required in production or default/guessable password.
Set HARDCOUNT_APP_DB_PASSWORD in .env and start the stack again. Compose builds APP_DATABASE_URL from it.
The API exits with could not open the app database connection.
The hardcount_app user is missing, or its password differs from HARDCOUNT_APP_DB_PASSWORD. Postgres creates that user only on its very first start, on an empty data volume. On an existing volume, set the password yourself:
docker compose exec -e HARDCOUNT_APP_DB_PASSWORD postgres \ psql -U hardcount -d hardcount -v pw="$HARDCOUNT_APP_DB_PASSWORD" \ -c "ALTER ROLE hardcount_app PASSWORD :'pw'"If the user does not exist at all, create it with the SQL in Postgres requirements.
The API exits with role can BYPASS row-level security.
APP_DATABASE_URL points at the admin user, usually because DATABASE_URL was copied into it. Point it at hardcount_app.
The API exits with migration … failed, or names goose_db_version.
A database upgrade stopped. A failed migration leaves nothing to repair. Compose keeps starting the API again, and each start tries the upgrade again, so stop it first with docker compose stop hardcount-api. Fix the cause the error names, then start it with docker compose up -d hardcount-api. The cases are in A database upgrade stopped.
/health/ready shows scheduler: degraded.
The API is up and still answers 200 here, but its background jobs have stopped running. Check the API logs, then see A background job is stuck.
/metrics returns 401.
METRICS_TOKEN is not set, or the scraper did not send it. Set it in .env (openssl rand -hex 32), start the stack again, and have Prometheus send it as a bearer token.
Port 80 is already in use.
Set NGINX_HTTP_PORT=8080, or any free port, in .env and start the stack again.
Sign-in says it does not work at this address, or Safari does not keep you signed in. You opened the dashboard over plain HTTP. See Sign in before HTTPS.