Self-hosted expense splitter — shared expenses, fairly balanced, on a server you control.
7.7K
Shared expenses, fairly balanced — on a server you control.
A free, privacy-first expense splitter for trips, households, couples, families, clubs and teams. Exact splits, multiple currencies, receipts, guests without accounts, and exports you can take elsewhere. No advertising, no third-party runtime service, and telemetry off unless an administrator turns it on.
This image is the whole application: the web app, the background worker and the migration runner, picked by the command it is given. Source, issues and the full documentation live at github.com/sebitr/balancia.
| Tag | What it is |
|---|---|
latest | The newest release. Moves under you at every pull. |
0.1.0 | That release, permanently. What to pin an instance you care about to. |
preview | main as it is right now, rebuilt on every merge that passes CI. Not a release. |
Every tag is a manifest list covering linux/amd64 and linux/arm64, so
docker pull serves the right one without being asked.
Images are built by GitHub Actions from the repository, and only once the whole test suite has passed on the same commit. Each one carries SLSA build provenance and an SPDX SBOM, and the manifest list is attested by GitHub in a statement signed through Sigstore — so you can check that a tag was built by the repository's own workflow, and not merely pushed under its name:
gh attestation verify oci://docker.io/sebitro/balancia:latest -R sebitr/balancia
Balancia needs a PostgreSQL database and a couple of secrets, so the shortest honest quick start is Compose — it brings up PostgreSQL 18 alongside the app, and the repository carries the setup script that points it at this image:
curl -fsSLO https://github.com/sebitr/balancia/releases/latest/download/bootstrap.sh
sh bootstrap.sh
bootstrap.sh is the installer. It asks where to install, fetches the Compose
files for its own release into that directory, writes a .env with this
instance's own database password and auth secret, asks which optional features
to switch on, and offers to start the stack. It needs no Git and no checkout,
and it is safe to run again. Then open http://localhost:3000 and create the
first account.
An install made this way pulls this image rather than building anything. To
build from source instead, clone the repository and run
./scripts/bootstrap.sh from inside it.
To run the container yourself against a database you already have:
docker run -d --name balancia \
-p 127.0.0.1:3000:3000 \
-v balancia-uploads:/data/uploads \
-e APP_URL=https://balancia.example.com \
-e DATABASE_URL=postgres://balancia:secret@db:5432/balancia \
-e AUTH_SECRET="$(openssl rand -base64 48)" \
sebitro/balancia:latest
Back up AUTH_SECRET. Losing it signs everyone out.
The port is published on 127.0.0.1 for a reverse proxy on the same host,
which is the only thing that should reach it: rate limiting trusts the
X-Forwarded-For the proxy writes, and a caller who reaches the container
directly writes their own.
Migrations are not a separate step. The entrypoint applies any pending ones before the app serves anything, under a PostgreSQL advisory lock, so several containers starting at once is safe.
| Port | 3000 |
| Volume | /data/uploads — receipt files, when storage is local rather than S3 |
| User | non-root, uid 1001 |
| Healthcheck | GET /api/health/live; /api/health/ready also touches the database |
| Worker | command: ["node", "dist/worker.js"] runs the background jobs in their own container. Left to itself, the web process runs them. |
Everything is environment variables — database, storage, SMTP, Sign in with Apple, exchange rates, receipt scanning, push notifications, telemetry, metrics. They are documented one by one in docs/environment.md, and the deployment side of them in docs/self-hosting.md.
./backup.sh --database-only --keep 10 backups/pre-upgrade
docker compose pull
docker compose up -d
Migrations are forward-only and never destructive without warning, and an
already-applied migration whose file has changed fails startup loudly rather
than running twice. Forward-only is also why the first line is there: the dump
it takes is the way back from an upgrade that went wrong. backup.sh comes
with the standalone install; see
docs/backup-and-restore.md.
Going back to an older image without restoring that dump is refused: a release older than its database will not start, rather than run against a schema it was never written for. Rolling back, and the setting that overrides the refusal, are in docs/self-hosting.md.
AGPL-3.0-or-later. The source for this image is github.com/sebitr/balancia.
Content type
Image
Digest
sha256:65f60255a…
Size
304.1 MB
Last updated
about 2 months ago
docker pull sebitro/balancia