Getting started
Try Simple Balance on your own machine with Docker and PostgreSQL, create the first account with its setup code, and see what running it for real takes.
Simple Balance runs as a container against a PostgreSQL database. The version we run for you isn’t open yet, so for now “installing it” and “getting an account” are the same step. This page is for running your own copy.
What you need
- A machine that can run Docker, with a few hundred megabytes free.
- PostgreSQL 15 or later, and 18 recommended. It can be managed, a server you already run, or a container beside the app.
- For anybody but you to reach it, a domain and HTTPS. Any address other than
localhosthas to behttps, with something in front of the app handling the certificate, such as Caddy, nginx or a cloud load balancer. Then setTRUST_PROXY=trueso each visitor gets their own sign-in allowance, but only once that proxy replacesX-Forwarded-Forrather than appending to it. Caddy does without being told, nginx does once it’s told to, and a cloud load balancer usually appends, so check yours. Behind a proxy says why it matters.
Nothing else. There’s no queue, no cache and no object store, and the project treats adding one as a change that has to be argued rather than a convenience.
Try it on your own machine
docker run --rm --name simple-balance -p 3000:3000 \
-e DATABASE_URL="postgresql://user:password@host:5432/simple_balance" \
-e AUTH_SECRET="$(openssl rand -hex 32)" \
-e APP_BASE_URL="http://localhost:3000" \
ghcr.io/thtmnisamnstr/simple-balance:latestThis is for trying it out on the machine in front of you: http://localhost
is the one address the server accepts without HTTPS. The command also makes a
new AUTH_SECRET every time it runs, which signs you out on every restart, so
for a copy you keep, generate one once and keep it.
If the database DATABASE_URL names doesn’t exist yet, it’s created, as long
as the role connecting has CREATEDB. Migrations run at startup under an
advisory lock, so starting two copies at once is safe and the second waits.
Nothing answers until they’ve finished, and a migration that fails stops the
process rather than leaving it serving a database it couldn’t finish upgrading.
The first account
Open the address you set as APP_BASE_URL. On an empty deployment you get the
create-account form rather than a sign-in form, because there’s nobody to sign
in as yet.
That form asks for a one-time setup code. While there are no accounts, the
server prints it to its log at startup as First-run setup code: …. With the
command above it’s in the terminal you started it from, and from any other
terminal:
docker logs simple-balance | grep -i setupSet SETUP_TOKEN to a string of at least 16 characters to choose the code
yourself instead of reading it from the log. It stops working as soon as an
account exists, and the claim is serialized, so two people racing for it can’t
both win.
No code is needed when ALLOWED_EMAILS already admits the address signing up.
Leaving ALLOWED_EMAILS unset admits nobody, which is what keeps an
unconfigured deployment private: whoever holds the setup code gets the first
account, and nobody else can register. Set it when you want to let other people
in. Configuration has the forms it takes, and the rest
of the settings the server reads.
Running it for real
The application describes two shapes. What separates them is how many machines there are and what runs on each.
| Profile | What it is |
|---|---|
single | Two machines: one running the app and whatever handles TLS, and one running PostgreSQL, unreachable from the internet. |
ha | A Kubernetes cluster, with PostgreSQL spread across several nodes by Citus. |
Start with single. It’s the supported shape, and the one the
application’s own docs assume. There are two ways to stand it up:
deploy/compose/singleruns the app under Docker Compose, given aDATABASE_URL: a PostgreSQL you already have, or the one itscompose.postgres.ymlruns on a second machine. Itscompose.caddy.ymloverlay adds Caddy, which gets and renews the certificate and setsTRUST_PROXYfor you, and the unit indeploy/systemdstarts it after a reboot. With a proxy of your own instead, setTRUST_PROXY=trueyourself, once the proxy replacesX-Forwarded-Forrather than appending to it (Behind a proxy).deploy/pulumihasoci-singleandaws-single, which stand up both machines on Oracle Cloud or EC2, the app with Caddy under systemd. The app machine’s separate data disk holds the nightly backups and the generated secret. Withsimple-balance:databaseNodeset tofalseyou get only the app machine, and it waits until the stack has theDATABASE_URLof a PostgreSQL you already run. Its login message says how.
On the machines the Pulumi programs build, settings live in the stack, and the program keeps them in the cloud’s own secret store, so nothing gets typed into a file on the machine. Set one and apply it, and the machine picks it up within five minutes:
pulumi config set --path 'simple-balance:env.SB_BILLING_ENABLED' true
pulumi config set --secret --path 'simple-balance:secrets.STRIPE_SECRET_KEY'
pulumi upThe second line names a secret and no value, so Pulumi asks for it, which
keeps it out of your shell history. The programs deploy the pinned release
image, which is 0.2.0. A machine that’s already running moves to a new release
on the machine itself, as the programs’ README describes, not from another
pulumi up.
Deployment profiles compares the two, and the deployment reference covers the settings in more depth, along with the reverse proxy configuration and backups.
What to do next
Import a statement. The importer reads a CSV your bank exported, suggests which column is which, asks you how its dates and amounts are written, and stages the rows. Staged rows affect no balance until you commit them, and a Dry run before staging shows how the file will be read without creating anything.