Create invoices, get paid online, track expenses, and own your data — all from a single ~50 MB container. A self-hostable alternative to FreshBooks, Wave, Zoho Invoice & Invoice Ninja.
Live Demo · Inkvoice Cloud · Documentation · Website
⭐ Star history — auto-updated weekly in metrics/stars.svg.
Inkvoice is a lightweight, self-hosted invoicing dashboard for people who'd rather own their billing than rent it. Send professional invoices, get paid online, keep an eye on expenses, and hand clean numbers to your accountant — without per-seat pricing or data lock-in.
- 🗄️ Own your data — everything lives in a single SQLite file you control.
- 📦 One container, runs anywhere — Docker, Coolify, Dokploy; ~50–100 MB RAM.
- ⚡ Modern & fast — Bun + Hono + React, no heavyweight runtime.
- 🌍 Multi-currency, multi-user, multi-language out of the box.
- ☁️ Don't want to self-host? Use the managed Inkvoice Cloud and skip the ops.
📊 Real production usage on a Dokploy host — Inkvoice idles at ~1% CPU and 41.79 MiB RAM (of a 512 MiB cap). The whole app comfortably fits on the cheapest VPS tier.
Try it right now on the live demo — no signup required, sign in with
demo/demo.
Invoicing
- Full lifecycle: draft → send → paid → void, plus one-click duplicate
- Line items, discounts and multiple tax rates with auto-calculation
- Polished PDF export (headless Chrome) with customizable Mustache templates
- Public shareable links with read receipts — see when a client opens an invoice
Quotes & recurring
- Quotes you can share and convert into invoices
- Recurring invoices with automated generation on a schedule
Get paid
- Online card payments via Stripe & PayPal
- Manual and partial payment tracking
German e-invoicing (E-Rechnung)
- Opt-in module, off by default — only German users need it, everyone else never sees it
- Emit EN 16931-compliant e-invoices: ZUGFeRD 2.2 hybrid PDF, XRechnung (UBL) and PEPPOL BIS
- E-invoice inbox — import, parse and process incoming XRechnung / ZUGFeRD files
- Auto-attach the e-invoice to sent invoice emails (per-invoice or globally)
PEPPOL transport
- Send invoices and credit notes over the PEPPOL network (provider-agnostic; peppol.sh driver ships with bring-your-own credentials)
- Receive inbound PEPPOL documents straight into the e-invoice inbox
- Delivery state machine with retries and per-attempt audit trail
- Register your business as a PEPPOL receiver, with conflict detection (no accidental access-point takeover)
Money & books
- Multi-currency with live exchange rates + base-currency consolidated reporting
- Expense tracking — billable expenses, receipts and categories
- Customer account statements (PDF per period)
- Reports — tax summary, A/R aging, revenue by customer & product, profit & loss, expenses by category, currency breakdown, and cash-flow forecast — every report exports to CSV
Platform
- Multi-user with role-based permissions and an activity log
- Built-in i18n (English, Turkish, Spanish, German & French — easy to add more)
- Dark mode, fully responsive
- Single Docker container, native SQLite, tiny footprint
| Invoices | Invoice detail |
![]() |
![]() |
| Reports (Profit & Loss) | Customers |
![]() |
![]() |
| Products & services | Dark mode |
![]() |
![]() |
docker compose up -dOpen http://localhost:3000 and log in with admin / changeme. Change the password from the user menu right away.
bun install
bun run dev # backend (:3000) + frontend (:5173) togetherbun run build # build the frontend
bun run start # serve API + static frontend on :3000Inkvoice ships as a single container — point any Docker host at the bundled Dockerfile, expose port 3000, and mount a volume on /app/data so the SQLite database survives redeploys.
- New resource → Public Repository → paste this repo's URL.
- Build pack: Dockerfile (the repo root
Dockerfile). - Port:
3000. - Persistent storage: add a volume on
/app/data— without it, every deploy wipes the database. - Environment: set at least
JWT_SECRET(≥ 32 chars),ADMIN_PASS, andCOOKIE_SECURE=true. - Domain: attach one and Coolify issues a Let's Encrypt cert; then set
ENABLE_HSTS=true. - Health check: point it at
/health.
Same shape as Coolify — create a Docker service from this repo, expose port 3000, mount a volume on /app/data, and set JWT_SECRET + ADMIN_PASS.
docker build -t inkvoice .
docker run -d \
--name inkvoice \
-p 3000:3000 \
-v inkvoice-data:/app/data \
-e JWT_SECRET="$(openssl rand -base64 48)" \
-e ADMIN_PASS="change-me-please" \
-e COOKIE_SECURE=true \
-e ENABLE_HSTS=true \
inkvoiceCopy .env.example to .env to bootstrap a local config. Most runtime knobs (currency, locale, invoice number pattern, email templates, etc.) live on the in-app Settings page — environment variables are reserved for deploy-time concerns (auth, ports, secrets).
| Variable | Required | Default | Description |
|---|---|---|---|
ADMIN_USER |
yes | admin |
Initial admin username (created on first boot if no users exist). demo when DEMO_MODE=true |
ADMIN_PASS |
yes | changeme |
Initial admin password. demo when DEMO_MODE=true |
JWT_SECRET |
yes | (dev-only fallback) | JWT signing secret — must be ≥ 32 chars in production |
DATABASE_PATH |
no | ./data/invoice.db |
SQLite database file path |
PORT |
no | 3000 |
HTTP listen port |
HOST |
no | 0.0.0.0 |
Bind address |
SESSION_TTL |
no | 3600 |
JWT lifetime in seconds |
COOKIE_SECURE |
no | true |
Set to false for plain-HTTP development |
ENABLE_HSTS |
no | false |
Send HSTS header (set to true behind HTTPS) |
RATE_LIMIT_ENABLED |
no | true |
Enable login rate limiting |
RATE_LIMIT_MAX_ATTEMPTS |
no | 5 |
Max failed logins per window |
RATE_LIMIT_WINDOW |
no | 900 |
Rate-limit window in seconds |
ALLOWED_ORIGINS |
no | localhost:5173,3000 |
Comma-separated CORS allow-list |
CHROME_PATH |
no | (auto-detected) | Override Chrome/Chromium binary path for PDF rendering |
SMTP_HOST |
no | — | SMTP host. Set this group to enable invoice email sending |
SMTP_PORT |
no | 587 |
SMTP port |
SMTP_USER |
no | — | SMTP username |
SMTP_PASS |
no | — | SMTP password |
SMTP_FROM |
no | — | Sender address |
SMTP_SECURE |
no | false |
Use TLS (set true for port 465) |
STRIPE_SECRET_KEY |
no | — | Stripe secret key (sk_…) — enables Stripe online payments |
STRIPE_PUBLISHABLE_KEY |
no | — | Stripe publishable key (pk_…) |
STRIPE_WEBHOOK_SECRET |
no | — | Stripe webhook signing secret (whsec_…) — required for Stripe |
PAYPAL_CLIENT_ID |
no | — | PayPal REST app client ID — enables PayPal online payments |
PAYPAL_SECRET |
no | — | PayPal REST app secret |
PAYPAL_WEBHOOK_ID |
no | — | PayPal webhook ID — required for PayPal (signature verification) |
PAYPAL_ENV |
no | sandbox |
PayPal environment: sandbox or live |
OIDC_ISSUER_URL |
no | — | OIDC issuer URL. Setting it enables SSO (must be https; requires OIDC_CLIENT_ID/OIDC_CLIENT_SECRET) |
OIDC_CLIENT_ID |
no | — | OIDC confidential client id |
OIDC_CLIENT_SECRET |
no | — | OIDC confidential client secret |
OIDC_SCOPE |
no | openid email profile |
Space-separated OIDC scopes (openid is always included) |
OIDC_ALLOWED_DOMAINS |
no | — | Comma-separated email domains allowed to self-register (JIT provisioning gate) |
OIDC_AUTO_PROVISION |
no | true |
Auto-create a read-only Viewer account on first SSO login |
OIDC_PROVIDER_NAME |
no | — | Custom label for the login-page SSO button |
PEPPOL_SH_API_KEY |
no | — | peppol.sh API key — enables the PEPPOL transport driver |
PEPPOL_SH_WEBHOOK_SECRET |
no | — | HMAC secret verifying inbound PEPPOL callbacks (required to receive) |
PEPPOL_SH_BASE_URL |
no | https://api.peppol.sh |
Provider base URL override (sandbox/proxy). Must be https |
DEMO_MODE |
no | false |
Periodically reset DB to seeded demo data (for public demo deploys). Also defaults the admin login to demo/demo and shows it on the sign-in page |
DEMO_RESET_INTERVAL |
no | 86400000 |
Demo reset interval in ms (default 24h) |
Any standards-compliant OIDC provider (Keycloak, Authentik, Authelia, Entra ID, Okta, Google Workspace, …) can act as the single sign-on source. Set OIDC_ISSUER_URL, OIDC_CLIENT_ID and OIDC_CLIENT_SECRET to enable it — everything else is auto-discovered via /.well-known/openid-configuration.
- Register a confidential client at your provider with redirect URI
https://your-domain/api/v1/auth/oidc/callback(use the origin your install is served from, or setPUBLIC_BASE_URL). - First-time logins auto-provision a read-only Viewer account unless
OIDC_AUTO_PROVISION=false— promote users in Users. Restrict who can self-register withOIDC_ALLOWED_DOMAINS. - Existing accounts link automatically on first SSO login only when the provider attests the email (
email_verifiedclaim). Password login remains available and is the admin recovery path.
| Layer | Choice |
|---|---|
| Runtime | Bun |
| Backend | Hono + SQLite (bun:sqlite), Zod validation, JWT auth |
| Frontend | React 19 + Vite, Tailwind CSS + shadcn/ui |
| Headless Chrome + Mustache templates | |
| Packaging | Single Docker container serving API + static SPA |
- Architecture overview — components, data flow, where to add features.
- Template variables — every
{{token}}available in invoice/quote/email templates. - E-Invoicing (E-Rechnung) — German ZUGFeRD/XRechnung/PEPPOL emission and inbox, step by step.
- Contributing — setup, conventions, and PR checklist.
- Full docs: docs.inkvoice.app
PRs are welcome! Please read CONTRIBUTING.md for setup and conventions. Found a bug or have an idea? Open an issue.
The source code is MIT licensed.
The Inkvoice name and logos are trademarks and are not covered by the MIT license — see TRADEMARK.md for what's free to do and what needs a quick word. Running, forking, and "Inkvoice hosting" are all fine; hosting providers wanting an official listing are welcome to get in touch.





