Skip to content

Install with Docker

This page walks through a production-style Docker install on one host. It covers the data directory, Compose file, first admin seed, startup checks, and the mistakes that usually cause broken installs.

What you need

  • Docker and Docker Compose installed on the host.
  • A persistent host directory for BillTracker data.
  • A DNS name or local URL you will use to reach the app.
  • A strong admin password for first login.
  • A strong TOKEN_ENCRYPTION_KEY before storing bank or auth secrets.

For the examples below, the host data path is:

/portainer/hosting/bill-tracker/data

Adjust it if your server keeps app data somewhere else.

1. Create the data directories

mkdir -p /portainer/hosting/bill-tracker/data/db
mkdir -p /portainer/hosting/bill-tracker/data/backups

The container stores the SQLite database at /data/db/bills.db and managed backups at /data/backups.

2. Generate a secret-encryption key

Use a long random value and keep it outside the database:

node -e "console.log(require('crypto').randomBytes(48).toString('hex'))"

Set the output as TOKEN_ENCRYPTION_KEY. If you skip this, BillTracker still encrypts secrets, but the encryption key is stored in the same SQLite database as the encrypted data.

3. Write docker-compose.yml

This example publishes the app on host port 3030 and mounts persistent data to /data.

services:
  bill-tracker:
    image: dream.scheller.ltd/null/bill-tracker:dev-v0.37.0
    container_name: bill-tracker-main
    restart: unless-stopped
    ports:
      - "3030:3000"
    environment:
      NODE_ENV: production
      TZ: America/Chicago

      # First start only. Remove or rotate these after setup.
      INIT_ADMIN_USER: admin
      INIT_ADMIN_PASS: replace-with-a-long-password

      # Keep secrets decryptable across container replacement.
      TOKEN_ENCRYPTION_KEY: replace-with-the-random-value-you-generated

      # Recommended production CSRF/cookie posture.
      CSRF_HTTP_ONLY: "true"
      CSRF_SAME_SITE: "strict"
      CSRF_SECURE: "true"

      # Set these when using WebAuthn/FIDO2 hardware-key 2FA on HTTPS.
      # WEBAUTHN_RP_ID: bills.example.com
      # WEBAUTHN_ORIGIN: https://bills.example.com
    volumes:
      - /portainer/hosting/bill-tracker/data:/data

Pin deliberately

Pinning a version makes upgrades predictable. Use latest only if you intentionally want the container to follow the newest published image.

4. Start the app

docker compose up -d
docker compose logs -f bill-tracker

On a healthy start, the logs should show database migrations completing before the web server begins listening on port 3000 inside the container.

5. Open BillTracker

Open the published port:

http://your-server:3030

If you are working directly on the host, this is usually:

http://localhost:3030

Log in with the seeded admin from INIT_ADMIN_USER and INIT_ADMIN_PASS, then continue with First-run setup.

6. Remove or rotate the seed credentials

After the admin login works:

  1. Change the admin password in the UI.
  2. Create at least one regular user for bill tracking.
  3. Comment out INIT_ADMIN_USER and INIT_ADMIN_PASS, or replace them with rotated emergency seed values kept in your password manager.
  4. Restart the container.
docker compose up -d

Why remove seed values?

Seed values can update matching seeded account passwords during startup. Leaving them in Compose can surprise you later when a restart re-applies an old password.

What data must survive upgrades?

Keep this host directory:

/portainer/hosting/bill-tracker/data

At minimum, keep:

/data/db/bills.db
/data/backups

Do not store real data only inside the container filesystem. Containers are replaceable; /data is the durable state.

How are migrations applied?

The Docker entrypoint runs:

node scripts/migrate-db.js

before starting the server. Leave migrations enabled for normal installs. Set RUN_DB_MIGRATIONS=false only for a deliberate recovery operation where you are taking responsibility for migration timing.

Common install problems

Symptom Likely cause What to check
Login page never loads Port is not published or blocked docker ps, firewall rules, reverse proxy target
App starts but data disappears after redeploy /data was not mounted to a host path docker inspect bill-tracker-main and confirm the bind mount
Admin account is missing Seed env vars were not present on first start docker compose logs and INIT_ADMIN_* values
Browser rejects cookies HTTPS/proxy headers are inconsistent CSRF_SECURE, COOKIE_SECURE, X-Forwarded-Proto
WebAuthn fails RP ID/origin do not match the public URL WEBAUTHN_RP_ID, WEBAUTHN_ORIGIN, HTTPS

Next steps