Budget Board on Ubuntu 24.04 on Azure User Guide
Overview
Budget Board is a free, open source, self hosted personal finance application, a privacy first alternative to the discontinued Mint. Set monthly budgets for your spending categories and track real transactions against them, manage financial accounts and assets side by side, follow progress towards savings and debt goals, and visualise net worth and spending trends, with all of your financial data staying private on infrastructure you control. The cloudimg image delivers Budget Board fully installed and configured on Ubuntu 24.04 as its complete three tier stack, so a working budgeting service is running within minutes of launch. Backed by 24/7 cloudimg support.
Budget Board is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). All product and company names are trademarks or registered trademarks of their respective holders. This image repackages the upstream open source release with cloudimg's provisioning and support.
What is included:
- Budget Board 3.6.3 (upstream
teelur/budget-board, AGPL-3.0), deployed as its official Docker Compose stack and pinned by image digest - A web client (React, served by nginx) on port 80, which also reverse proxies the API on the same origin, so a browser is all your users need
- An API server (ASP.NET / .NET 10) and PostgreSQL 16, both kept internal and never exposed on a host port
- Secure by default: new user registration is closed on first boot (zero accounts ship), and a unique PostgreSQL password is generated per VM and stored in a root only file
docker.serviceandbudget-board.serviceas systemd units, enabled and active, with the containers set to restart on failure- 24/7 cloudimg support

Prerequisites
An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a good starting point; scale up for larger households or heavy activity. NSG inbound: allow 22/tcp from your management network and 80/tcp (HTTP) from your users. Add 443/tcp if you enable HTTPS.
Step 1 — Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Budget Board by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTP (80). Then Review + create and Create.
Step 2 — Deploy from the Azure CLI
az vm create \
--resource-group <your-rg> \
--name budget-board \
--image <marketplace-image-urn> \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
Open HTTP to reach the web UI:
az vm open-port --resource-group <your-rg> --name budget-board --port 80 --priority 900
Step 3 — Connect to your VM
ssh azureuser@<vm-public-ip>
Step 4 — Confirm the stack is running
Both systemd units should report active, all three containers should be up, and the web UI should answer HTTP 200 on port 80. The block below waits for the stack to finish warming up (the API applies its database migrations on first start):
systemctl is-active docker budget-board
sudo docker ps --format 'table {{.Names}}\t{{.Status}}'
for i in $(seq 1 45); do
code=$(curl -s -o /dev/null -w '%{http_code}' -m 5 http://127.0.0.1/)
[ "$code" = "200" ] && break
sleep 4
done
echo "Web UI on port 80 -> HTTP $code"
Expected: two lines of active, three running containers (budget-board-client, budget-board-server, postgres-db), and Web UI on port 80 -> HTTP 200. PostgreSQL is internal to the Docker network and is never published on a host port, so the database is not reachable from outside the VM.

Step 5 — Confirm the same origin API proxy
The React client and the .NET API are served on the same origin: the browser only ever talks to port 80, and the client's nginx reverse proxies API calls to the internal server. No VM IP is baked into the client, so it works wherever you reach it. The unauthenticated /api/isAuthenticated endpoint proves the client, proxy and server are all wired up:
for i in $(seq 1 45); do
body=$(curl -s -m 5 http://127.0.0.1/api/isAuthenticated || true)
echo "$body" | grep -q 'isAuthenticated' && break
sleep 4
done
echo "API says: $body"
Expected: API says: {"isAuthenticated":false} (false because you are not signed in yet). A JSON response here confirms the browser-to-API path through the reverse proxy is healthy.
Step 6 — Retrieve your per-VM details
On the first boot of every VM, a one shot service (budget-board-firstboot.service) generates a PostgreSQL password that is unique to that VM, writes it into the stack's environment file and into a root only credentials file, and records the instance URL. No shared or default database password ships in the image.
sudo cat /root/budget-board-credentials.txt
The file (mode 0600, root only) contains the instance URL (BUDGET_BOARD_URL) and the per-VM POSTGRES_PASSWORD. You do not normally need the database password — your day to day accounts are managed in the web UI — but it is recorded for backup and recovery.

Step 7 — Create your account (open, then close, registration)
For security, new user registration is closed by default, so no stranger who reaches your VM can create an account. To create your own first account, open registration deliberately, sign up in the web UI, then close registration again. Budget Board has no shared administrator — every account is a private, self contained budget — so nothing is auto privileged.
Check the current state and confirm the registration endpoint is closed:
sudo budget-board-registration status
echo -n 'POST /api/register -> HTTP '
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://127.0.0.1/api/register \
-H 'Content-Type: application/json' -d '{"email":"probe@example.com","password":"probe123"}'
Expected: DISABLE_NEW_USERS=true and POST /api/register -> HTTP 404 — registration is genuinely closed at the API, not just hidden in the UI.

Now open registration, create your account, and close it again. Run these on the VM:
# 1) Open registration
sudo budget-board-registration on
# 2) Browse to http://<vm-public-ip>/ , click "Register here", and create your account
# 3) Close registration again once your account exists
sudo budget-board-registration off
After signing up you are taken to your dashboard, which brings your accounts, this month's spending and your net worth together in one view.

Step 8 — Track spending and set budgets
Open Transactions to record and categorise what you spend. You can add transactions by hand, import them in bulk from a CSV file, or connect a bank sync provider such as SimpleFIN or LunchFlow to have them download automatically; a built in model can then learn from your history to categorise new transactions for you.

The Budgets view is the heart of Budget Board: set a monthly limit for each income and expense category and watch your real spending track against it, with progress bars and a running summary of income, expenses and net cash flow for the month.

Per-VM security model
The image is secure by default on two fronts. First, no known credential ships: a one shot budget-board-firstboot.service runs after the network is up, generates a fresh PostgreSQL password unique to the VM, writes it to the stack's environment file and a root only credentials file, and drops a sentinel so it runs exactly once. The database volume is wiped before the image is captured, so your instance starts from an empty database initialised with its own per-VM password. Second, registration is closed at first boot, so the instance ships with zero accounts and no open sign up.
systemctl is-active budget-board-firstboot.service
sudo stat -c '%a %U:%G %n' /root/budget-board-credentials.txt
sudo budget-board-registration status
Expected: active, then 600 root:root /root/budget-board-credentials.txt, then DISABLE_NEW_USERS=true.
Enabling HTTPS with your own domain
The image serves the web UI over plain HTTP on port 80. For a browser trusted certificate, the simplest approach is to terminate TLS in front of the VM with an Azure Application Gateway or Azure Front Door, pointing the backend at your VM on port 80. Alternatively, run a reverse proxy on the VM itself: point a DNS record at the VM, open port 443 in the NSG, and obtain a certificate (for example with Caddy or nginx + certbot) that proxies to http://127.0.0.1/. Because the client already serves the API on the same origin, no application configuration change is needed.
Backup and maintenance
Your data lives in the PostgreSQL container's named volume. Take a logical backup at any time:
sudo docker exec postgres-db pg_dump -U postgres budgetboard | gzip > budgetboard-$(date +%F).sql.gz
Snapshot the OS disk from the Azure portal for a full point in time copy. Keep the OS current with sudo apt-get update && sudo apt-get upgrade; the image ships with unattended security updates enabled. Review the Budget Board documentation before moving between releases, and always back up before an upgrade.
Support
This image is backed by 24/7 cloudimg support for deployment and initial configuration, opening and closing registration, retrieving the first boot database password, creating your first account, bank sync provider setup, CSV import, TLS termination, backups and data recovery, performance tuning and storage administration. Email support@cloudimg.co.uk or use the live chat in the support portal; critical issues receive a one-hour average response.