B0
Application Infrastructure Azure

Blnk on Ubuntu 24.04 on Azure User Guide

| Product: Blnk on Ubuntu 24.04 on Azure

Overview

This guide covers the deployment and use of Blnk on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Blnk is an open-source double-entry ledger and financial core: an HTTP JSON API that records money movements between balances as immutable, balanced transactions, enforces double-entry invariants so every posting nets to zero, and supports multi-currency balances, precise integer amounts and reconciliation.

This image ships the standalone Blnk ledger on a single self-contained VM. Blnk runs as two Go processes on the same VM — the API server and an async accounting worker — backed by a local PostgreSQL 16 database and a local Redis 7 queue. The Blnk binary is built from the pinned upstream source at build time with a pinned Go toolchain. The optional Typesense search engine is not shipped: the core ledger — creating ledgers and balances, recording transactions, and reading balances — runs fully without it.

Blnk has no web interface. It is an API server: HTTP requests in, JSON out. Everything in this guide is done with curl and standard HTTP clients, which is what a financial API is meant for. Point your own application at it.

Security by design, deny by default. Because a ledger holds financial records, this appliance refuses first:

  • The Blnk API runs in secure mode: every route requires the per-VM secret key, sent in the X-Blnk-Key header. Every request without it — read or write — is refused with HTTP 401.
  • PostgreSQL and Redis are bound to the loopback interface only and are never reachable off the VM. The API on TCP 5001 is the only routable interface.
  • Nothing is baked into the image. On first boot each VM generates a unique API secret key, a unique password for the postgres superuser, a unique password for the ledger database role, and a unique Redis password, and writes them to the root-only file /root/blnk-credentials.txt. The shipped image contains no configuration and an empty ledger database.

What is included:

  • Blnk v0.15.2 from the official upstream source (github.com/blnkfinance/blnk, Apache-2.0), built from the pinned tag with a pinned Go toolchain, running under systemd as blnk-server.service (the API) and blnk-worker.service (the async accounting worker)

  • PostgreSQL 16 from the official PostgreSQL PGDG repository, bound to loopback, running as postgresql.service

  • Redis 7 as the intake queue between the API and the worker, bound to loopback with a per-VM password, running as redis-server.service

  • Per-VM secrets generated on first boot and written to a root-only credentials file, with no default login

  • A small demo ledger seeded on first boot so the appliance is immediately live

  • blnk-selfcheck, a helper that verifies the whole appliance — loopback binding, refusal of anonymous and wrong-key requests, correct double-entry balances, and the sum-zero invariant — end to end

  • Unattended security upgrades left enabled so the appliance keeps receiving patches

Blnk is a trademark of Blnk Finance. This image is produced by cloudimg and is not affiliated with, endorsed by, or sponsored by Blnk Finance. The name is used only to identify the open-source software that runs in the image.

Prerequisites

  • Active Azure subscription, an SSH public key, and a VNet and subnet in the target region

  • Subscription to this listing on Azure Marketplace

  • A Network Security Group allowing TCP 22 for administration and TCP 5001 for the Blnk API. In production, restrict 5001 to your application subnet — the ledger holds financial data.

Recommended virtual machine size: Standard_B2s with 2 vCPU and 4 GB RAM for development and light workloads. For higher transaction rates, choose a larger size such as Standard_D2s_v5 or above.

Deploy the virtual machine

Deploy from the Azure Portal by selecting the image from Azure Marketplace, choosing your VM size, and supplying your SSH public key for the azureuser account. Or deploy from the Azure CLI. These commands run on your own workstation, not on the VM:

az vm create \
  --resource-group my-resource-group \
  --name my-blnk \
  --image <marketplace-image-urn> \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

# then open the API port to your client network (tighten the source range in production)
az vm open-port --resource-group my-resource-group --name my-blnk --port 5001

SSH in as azureuser:

ssh azureuser@<vm-ip>

Confirm the services are healthy

On the first boot the appliance mints its per-VM secrets, runs the ledger schema migrations against the empty database, starts the API and worker, and seeds a small demo ledger. Confirm all four services are active:

sudo systemctl is-active postgresql redis-server blnk-server blnk-worker

All four report active. The Blnk API listens on 0.0.0.0:5001 while PostgreSQL on 127.0.0.1:5432 and Redis on 127.0.0.1:6379 are loopback only.

PostgreSQL, Redis, the Blnk API server and the Blnk worker all reporting active under systemd, with the API on 5001 routable and PostgreSQL on 5432 and Redis on 6379 bound to loopback only

Retrieve your per-VM credentials

Every secret is generated on first boot and written to a root-only file. Read it with:

sudo cat /root/blnk-credentials.txt

The api.secret_key is the key you send in the X-Blnk-Key header to authenticate to the API on port 5001. The file also holds the PostgreSQL and Redis passwords, which are for loopback administration only. Keep this file safe; the secrets are unique to your VM and are shown in plain text only here.

The API is deny by default

Every request to the API needs the per-VM key. An anonymous request, or one with the wrong key, is refused with HTTP 401 before it does anything:

# anonymous write is refused
curl -s -o /dev/null -w '%{http_code}\n' -X POST -H 'Content-Type: application/json' \
  -d '{"name":"x"}' http://<vm-ip>:5001/ledgers
# a wrong key is refused
curl -s -o /dev/null -w '%{http_code}\n' -H 'X-Blnk-Key: wrong-key' \
  -X POST -H 'Content-Type: application/json' -d '{"name":"x"}' http://<vm-ip>:5001/ledgers

Both print 401. With the per-VM key the same request succeeds:

curl -s -o /dev/null -w '%{http_code}\n' -H 'X-Blnk-Key: <api.secret_key>' \
  http://<vm-ip>:5001/ledgers

This prints 200.

An anonymous write and a wrong-key write to the API both refused with HTTP 401, the authenticated request with the per-VM key returning HTTP 200, and the root-only 0600 credentials file

Create a ledger, balances and a double-entry transaction

A ledger is an isolated set of balances and transactions. Create one:

curl -s -H 'X-Blnk-Key: <api.secret_key>' -H 'Content-Type: application/json' \
  -X POST http://<vm-ip>:5001/ledgers -d '{"name":"Demo Ledger"}'

The response carries a ledger_id (for example ldg_...). Now create two balances in that ledger — one to represent the external world (the source of funds) and one for a user, alice:

curl -s -H 'X-Blnk-Key: <api.secret_key>' -H 'Content-Type: application/json' \
  -X POST http://<vm-ip>:5001/balances -d '{"ledger_id":"<ledger_id>","currency":"USD"}'

Each response carries a balance_id (for example bln_...). Now post a transaction. In double-entry terms, money entering the system is debited from an external source and credited to a destination. The source is an ordinary balance allowed to go negative with allow_overdraft, which is the "world" leg. This moves USD 1000.00 (amount 1000 at precision 100) from the world balance into alice:

curl -s -H 'X-Blnk-Key: <api.secret_key>' -H 'Content-Type: application/json' \
  -X POST http://<vm-ip>:5001/transactions \
  -d '{"amount":1000,"precision":100,"currency":"USD","source":"<world_balance_id>","destination":"<alice_balance_id>","allow_overdraft":true,"reference":"fund-alice-1","description":"fund alice from world"}'

The transaction is queued to Redis and applied to PostgreSQL by the worker within a second or two. Read the balances back:

curl -s -H 'X-Blnk-Key: <api.secret_key>' http://<vm-ip>:5001/balances/<alice_balance_id>
curl -s -H 'X-Blnk-Key: <api.secret_key>' http://<vm-ip>:5001/balances/<world_balance_id>

alice shows a balance of 100000 (minor units, so USD 1000.00) and world shows -100000. Double-entry means the destination gained exactly what the source lost, and the sum across the balances is zero.

Creating a ledger and two USD balances, posting a transaction that funds alice with USD 1000 from an overdraft world source, and reading the resulting double-entry balances of alice plus 100000, world minus 100000, summing to zero

Verify the whole appliance

You can re-prove the entire appliance — loopback binding of PostgreSQL and Redis, refusal of anonymous and wrong-key requests, a correct double-entry funding and transfer, and the sum-zero invariant — in one command:

sudo blnk-selfcheck

The appliance self-check reporting loopback-only backing services, anonymous and wrong-key requests refused with 401, a correct double-entry funding of USD 1000 and a transfer of USD 250 between accounts, and the sum of all balances equal to zero

Where to go next

  • Model your own money flows: create a ledger per product line, a balance per account, and post transactions for every movement, using allow_overdraft only on the balances that represent the outside world.
  • Add meta_data to ledgers, balances and transactions to carry your domain identifiers.
  • Read balances, transactions and the immutable transaction log through the API at /balances/{id}, /transactions and /transactions/{id}.
  • The full API is documented by the upstream project at docs.blnkfinance.com.

Security notes

  • The API is deny by default. It runs in secure mode, so every request must carry the per-VM key in the X-Blnk-Key header. PostgreSQL and Redis are loopback only and never reachable off the VM.
  • No default credentials. The API secret key, the postgres superuser password, the ledger database password and the Redis password are all generated uniquely on first boot. The shipped image has no configuration and an empty database.
  • Rotate the API key by generating a new value and updating secret_key in /etc/blnk/blnk.json, then sudo systemctl restart blnk-server blnk-worker.
  • In production, restrict TCP 5001 to your application subnet with the Azure NSG, and consider terminating TLS at a load balancer or reverse proxy in front of the API.
  • Back up PostgreSQL (sudo -u postgres pg_dump blnk) as you would any system of record; the ledger's data lives entirely in the blnk database.
  • Unattended security upgrades are left enabled so the OS keeps receiving patches.