The open-source back office for a CPA or bookkeeping firm: five proven,
unmodified open-source applications, one docker compose up.
Published by maxed-oss. We run this exact stack for our own back office and publish the recipe. There is no account, no telemetry to us, and nothing here depends on us: every application below is the official upstream image, pinned to a supported version, wired together with configuration only.
| Application | What it does for a firm | Port | Image (pinned) | Upstream license |
|---|---|---|---|---|
| ERPNext | Firm ledger, invoicing, accounts payable, practice management | 8081 | frappe/erpnext:v15 |
GPL-3.0 |
| Kimai | Time tracking and timesheets | 8082 | kimai/kimai2:2.61.0 |
AGPL-3.0 |
| Nextcloud | Client document storage and sharing | 8080 | nextcloud:33-apache |
AGPL-3.0 |
| Documenso | E-signature (engagement letters, internal docs) | 8083 | documenso/documenso:v2.14.0 |
AGPL-3.0 |
| Metabase | Dashboards and analytics over your data | 8084 | metabase/metabase:v0.62.3.5 |
AGPL-3.0 |
Supporting services: PostgreSQL 16, MariaDB 10.11 LTS, Redis 7.4. Data lives in named Docker volumes; nothing is stored inside containers.
- Unmodified upstream. Every image is the official one, untouched. You get upstream security updates, upstream docs, and upstream community support.
- Pinned versions. No
:latestroulette. Update deliberately (see "Updating" below). - API-first. Each application ships a full REST API, so any practice tooling you use (or build) can integrate without vendor lock-in.
- Config only. This repository is a compose file, an env template, and documentation. There is no code to trust.
- A Linux host (Ubuntu 24.04 tested) with 16 GB+ RAM and 160 GB+ disk.
- Docker 24+ with the Compose v2 plugin.
- For production: a domain, a reverse proxy for TLS, and an SMTP account for Documenso's signing invitations.
git clone https://github.com/maxed-oss/cpa-stack.git
cd cpa-stack
# 1. Configure secrets (fill in every REPLACE_ME).
cp .env.example .env
nano .env
# 2. Documenso needs a PKCS#12 signing certificate at ./cert.p12.
# Self-signed is fine to evaluate:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem \
-days 365 -nodes -subj "/CN=yourfirm.example"
openssl pkcs12 -export -out cert.p12 -inkey key.pem -in cert.pem -passout pass:
rm key.pem cert.pem
# 3. Start everything.
docker compose up -d
# 4. Watch ERPNext finish its one-time site creation (a few minutes).
docker compose logs -f erpnext-create-siteFirst logins:
- Nextcloud (http://localhost:8080): the admin user/password from
.env. - ERPNext (http://localhost:8081): user
Administrator, theERPNEXT_ADMIN_PASSWORDfrom.env. - Kimai (http://localhost:8082): the admin email/password from
.env. - Documenso (http://localhost:8083): sign up the first account in the UI.
- Metabase (http://localhost:8084): the setup wizard runs on first visit.
Most services carry container-level healthchecks (docker compose ps shows
them). From the host:
curl -sf http://localhost:8080/status.php # Nextcloud
curl -sf http://localhost:8081/api/method/ping # ERPNext
curl -sf -o /dev/null http://localhost:8082/ # Kimai
curl -sf -o /dev/null http://localhost:8083/ # Documenso
curl -sf http://localhost:8084/api/health # MetabaseThe stack's state is nine named volumes plus your .env and cert.p12:
nextcloud_data nextcloud_db_data erpnext_sites erpnext_logs erpnext_db_data
erpnext_redis_queue_data kimai_db_data documenso_db_data metabase_db_data
Minimum viable backup, run on a schedule and shipped off the host:
# Logical database dumps (consistent, restorable independently).
docker compose exec -T nextcloud-db pg_dump -U nextcloud nextcloud | gzip > nextcloud.sql.gz
docker compose exec -T documenso-db pg_dump -U documenso documenso | gzip > documenso.sql.gz
docker compose exec -T metabase-db pg_dump -U metabase metabase | gzip > metabase.sql.gz
docker compose exec -T erpnext-db mariadb-dump -uroot -p"$ERPNEXT_DB_ROOT_PASSWORD" --all-databases | gzip > erpnext.sql.gz
docker compose exec -T kimai-db mariadb-dump -uroot -p"$KIMAI_DB_ROOT_PASSWORD" kimai | gzip > kimai.sql.gz
# File volumes (documents, ERPNext site files).
docker run --rm -v cpa-stack_nextcloud_data:/data -v "$PWD":/backup alpine \
tar czf /backup/nextcloud_data.tar.gz -C /data .
docker run --rm -v cpa-stack_erpnext_sites:/data -v "$PWD":/backup alpine \
tar czf /backup/erpnext_sites.tar.gz -C /data .Three rules that matter more than the tooling: encrypt backups before they leave the host, keep at least one copy a compromised host cannot delete (object storage with retention/immutability), and rehearse a restore on a clean machine before you trust any of it.
- Snapshot first (backups above).
- Bump one image pin at a time in
docker-compose.yml, reading that application's upstream release notes (major upgrades of ERPNext and Nextcloud have their own documented migration paths). docker compose pull && docker compose up -d, then re-run the health checks.
- Put a reverse proxy (Caddy, Traefik, nginx) with TLS in front of the five web ports; do not expose the raw ports to the internet.
- The databases and redis instances bind no host ports by design; keep it that way.
- Set real hostnames in
NEXTCLOUD_TRUSTED_DOMAINS,KIMAI_TRUSTED_HOSTS, andDOCUMENSO_WEBAPP_URLonce the proxy is up. - Use a unique generated secret for every
REPLACE_ME; never reuse one across services. - For legally sensitive e-signatures, replace the self-signed
cert.p12following Documenso's certificate documentation.
This repository (the compose file, env template, and docs) is Apache-2.0. Each application it deploys is upstream software under its own license, listed in the table above; the images are pulled unmodified from their official registries.