A self-hosted expense, income, and reimbursement tracker for small teams and freelancers.
Akaun is a small web app for keeping track of money in and out of a business or household: expenses, income, who paid for what, and which expenses still need to be reimbursed. You run it yourself — on your own server, NAS, or VPS — and your financial data never leaves your machine.
Instead of typing every receipt in by hand, you can snap a photo or upload a scan and Akaun will read the amount, date, and supplier off it automatically (OCR), suggest a matching contact, and flag anything that looks like a duplicate before it's saved.
- Your data stays yours — everything lives in a single SQLite file and a local folder you control, no third party has access to it.
- No subscription — run it once on hardware you already own.
- Small footprint — designed to run comfortably on a low-power VPS, a Raspberry Pi, or a home NAS.
- Expenses & Income — record transactions with amounts, dates, categories, and linked contacts.
- Receipt/Invoice Import (OCR) — upload a photo or PDF and Akaun extracts the details for you, with fuzzy contact matching and duplicate detection.
- Reimbursement Claims — group a batch of expenses into a claim for approval and repayment.
- Contacts Directory — one shared list of suppliers, customers, and employees, reused across expenses and income.
- Roles & Permissions — invite teammates and control exactly what each person/group can view, add, edit, or delete.
- Real-time Updates — changes made by one person (or in another browser tab) show up instantly for everyone else, no refresh needed.
- PDF Export — generate printable summaries of claims and records.
The easiest way to run Akaun is with Docker. This is the recommended path even if you're not very technical — once Docker is installed, it's three steps.
1. Install Docker (includes Docker Compose on recent versions).
2. Create a folder for Akaun and a docker-compose.yml inside it:
services:
akaun:
image: ghcr.io/akaun-app/akaun:latest
restart: unless-stopped
ports:
- 6969:6969
volumes:
- ./data:/app/data
environment:
- ORIGIN=http://localhost:6969
- PUID=1000 # Set to $(id -u) to match your host user
- PGID=1000 # Set to $(id -g) to match your host group
- BODY_SIZE_LIMIT=15M
# - ADMIN_PASSWORD= # Only read on first boot to seed the admin account password
# - LOG_LEVEL=info
# - SSL_ENABLED=true
# - SSL_KEY_PATH=/app/data/ssl/key.pem
# - SSL_CERT_PATH=/app/data/ssl/cert.pem3. Start it:
docker compose up -dAkaun is now running at http://localhost:6969 (or your server's address, if ORIGIN is updated to match). All data — the database and any uploaded files — is stored in the ./data folder next to your docker-compose.yml, so back up that folder to back up your whole instance.
First login: an admin account is created automatically on first boot. If you didn't set ADMIN_PASSWORD in the compose file, check the container logs once (docker compose logs akaun) for the randomly generated password — it's only printed the first time.
Running behind a different port, domain, or with HTTPS: see the commented-out variables above and .env.example in this repo for details on every option.
If you're fronting Akaun with nginx (including Nginx Proxy Manager) or a similar proxy, two settings matter:
1. SSE endpoints need unbuffered, long-lived connections. Akaun uses Server-Sent Events (/api/*/stream) for real-time updates. Add a location block for these paths with buffering disabled and a long read timeout, or the proxy will buffer/kill the stream:
location ~ ^/api/.*/stream$ {
proxy_pass http://<akaun-host>:<port>;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding off;
proxy_read_timeout 24h;
}2. The default location needs a larger header buffer. SvelteKit sends a Link response header preloading every JS module a page needs. Pages with many components (e.g. Expenses) can produce a Link header bigger than nginx's default proxy_buffer_size (commonly 4k–8k), which causes nginx to reject the response with upstream sent too big header while reading response header from upstream — a 502 on the affected page's direct/fresh load only (client-side in-app navigation to the same page won't reproduce it, since that doesn't trigger a full page render). Size the buffers up for the default location:
proxy_buffer_size 16k;
proxy_buffers 4 16k;
proxy_busy_buffers_size 32k;In Nginx Proxy Manager, add both blocks via the proxy host's Advanced tab. Don't use the "Websockets Support" toggle as a substitute for the first block — it applies proxy_http_version 1.1 with Upgrade/Connection: upgrade handling to the entire host including normal page traffic, which this app's server does not handle correctly and will make the whole site unreachable.
For developers who'd rather run Akaun directly with Bun:
git clone https://github.com/akaun-app/akaun.git
cd akaun
bun install
bun run build
bun server.jsConfigure the app via environment variables (copy .env.example to .env and edit, or export them directly):
| Variable | Default | Purpose |
|---|---|---|
DATABASE_PATH |
./data/akaun.db |
SQLite database file location |
STORAGE_PATH |
./data/storage |
Where uploaded files (receipts, attachments) are stored |
BODY_SIZE_LIMIT |
15M |
Max upload size |
ADMIN_PASSWORD |
(auto-generated) | Initial password for the admin account, first boot only |
LOG_LEVEL |
info |
trace | debug | info | warn | error |
SSL_ENABLED |
false |
Serve over HTTPS |
SSL_KEY_PATH |
(none) | Path to TLS private key, required if SSL_ENABLED=true |
SSL_CERT_PATH |
(none) | Path to TLS certificate, required if SSL_ENABLED=true |
Database migrations are generated with bun run db:generate and applied automatically on startup.
For the curious: Akaun is built with SvelteKit (Svelte 5) and runs on the Bun runtime. Data is stored in SQLite via the Drizzle ORM, styling is Tailwind CSS, receipt scanning uses Tesseract.js for OCR, and live updates are pushed to the browser over Server-Sent Events.
bun install
bun run dev # start the dev server
bun run check # type-check
bun run lint # formatting + lint checks
bun run test # unit testsSee CLAUDE.md for the project's architecture conventions (real-time update pattern, UI component standards, etc.) if you're contributing.
Akaun is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). You're free to self-host, use, and modify it for personal or commercial purposes. The AGPL's key condition is that if you run a modified version of Akaun as a network service for others, you must make the complete source code of your modified version available to those users. See LICENSE for the full terms.