GuidesBusiness and monitoringInstead of FreshBooks/QB

Self-hosted alternatives to FreshBooks, QuickBooks and Zoho Invoice: Invoice Ninja and InvoiceShelf compared

Invoice Ninja and InvoiceShelf on one Debian 13 server, with the same Swedish company, three customers in Sweden, Germany and the US and prices with 25, 12 and 6 % VAT, compared on quotes, credit notes, reminders, reverse charge, the Swedish PDF, VAT reports, Peppol, memory and cost, and why we left out Akaunting.

Tested on Invoice Ninja 5.13.47 and InvoiceShelf 2.4.6 on Debian 13 (trixie) on a Melonslab server Updated October 14, 2026

Recommended server for this guide

SSD RAID 1 · ECC DDR4 · 10 Gbit

VC-P Alloy · 2 vCPU · 8 GB Memory · 60 GB Storage

Month to month, no lock-in 7-day money-back guarantee

€8.99/mo

Deploy now
On this page

The short answer

Pick Invoice Ninja if invoicing is a daily part of your business: it did everything we asked of it, quotes to invoices, credit notes, partial payments, automatic reminders, recurring invoices, a client portal and a VAT report per rate, all in Swedish, and it can make Peppol e-invoices. It needs about 380 MB of memory, 3.2 GB of images and a source-available licence rather than an open-source one. Pick InvoiceShelf if you send a handful of invoices a month and want something small and AGPL-licensed: it ran in about 45 MB, but it has no credit notes and no reminders, its VAT report counts only fully paid invoices, and it let us delete a sent invoice and reuse its number.

Neither is bookkeeping. Both make invoices that you or your accountant then book in a Swedish bookkeeping system, by hand: neither exports SIE.

What we tested

We installed both on the same fresh Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13, each under a user of its own, behind the same Caddy. We have no guide for either, so we ran each app's official image in the pattern of our app guides: rootless Podman, Quadlet units, passwords and keys as Podman secrets, MariaDB or PostgreSQL in the same pod, and our Caddy in front. How we ran Invoice Ninja and How we ran InvoiceShelf have the files.

Then we gave both the same data, mostly through their APIs:

  • our own company, Exempel AB in Stockholm, with an organisation number, a VAT number, a bankgiro number and Swedish kronor;
  • three customers: Nordljus AB in Gothenburg, Bauer Maschinenbau GmbH in Munich, an EU business with a German VAT number, billed in euros with reverse charge, and Northwind Trading Inc. in Seattle, outside the EU;
  • three products: a consulting hour at 1,000 kr with 25 % VAT, a workshop lunch at 150 kr with 12 % and a printed handbook at 450 kr with 6 %;
  • a quote to Nordljus with all three, which we turned into an invoice of 17,660 kr; an invoice to Bauer for 1,080 euros, one to Northwind for 5,000 kr, and an overdue one to Nordljus from September;
  • a partial payment of 10,000 kr, a credit note for one returned handbook, a monthly recurring invoice for a support agreement, and an office expense of 1,250 kr.

We sent every invoice and reminder to a local test mail server, opened the client portal as Nordljus, checked every PDF against what Skatteverket says a Swedish invoice must contain, ran each app's VAT and profit reports, rebooted the server, measured memory at rest and under use, and backed up and restored both.

FreshBooks, QuickBooks Online and Zoho Invoice were not tested. We did not sign up for any of them. What this page says about them comes from their own pricing and help pages, linked where we use them and read on 14 October 2026.

Why not Akaunting

We meant to compare Invoice Ninja with Akaunting, and installed it first. It is maintained, with release 3.2.4 on 17 September 2026 on GitHub, but on 14 October 2026 we did not find it realistic to run on your own server:

  • It could not make an invoice without Akaunting's servers. Before it saves an invoice, Akaunting asks api.akaunting.com for the limits of your plan. With no Akaunting account key, that server answered "Forbidden", and Akaunting refused the invoice with "Not able to create a new user." The check is in Akaunting's own code, the same in 3.1.21, which we ran, and in 3.2.4. You need an account at akaunting.com to invoice at all, and the app stops invoicing if that server is down or refuses you. We did not create an account.
  • The official image is behind. The newest image on Docker Hub, built on 19 March 2026, holds 3.1.21 from December 2025, five releases behind the code.
  • The free plan is small. Akaunting's licence is the Business Source License 1.1, which allows free production use with at most "two users or one company or one thousand invoices"; its on-premise plans list the free Standard plan as 1 company, 1 user plus 1 accountant and 1,000 invoices, and Premium at 72 US dollars a year.
  • Quotes and credit notes are paid apps. Estimates and Credit-Debit Notes cost 60 dollars a year each for a server of your own.

So we tested InvoiceShelf instead, the maintained successor in the same niche: an AGPL-licensed Laravel app with release 2.4.6 on 25 September 2026. Crater, the other name people suggest, had its last release, 6.0.6, in March 2022, and no commit to its repository since February 2023.

The numbers

Invoice Ninja 5.13.47InvoiceShelf 2.4.6
Containers3: Invoice Ninja with PHP-FPM, nginx, MariaDB 11.82: InvoiceShelf with nginx and PHP-FPM, PostgreSQL 18
Memory at rest after a rebootabout 380 MB (app 242, MariaDB 137, nginx 3)about 43 MB (app 35, PostgreSQL 8)
Memory at the peak of useabout 610 MBabout 75 MB
Images on disk3.25 GB, most of it a Chrome for PDFs0.81 GB
Data after the test435 MB, 249 MB of it a copy of the web files67 MB
From useradd until the app answered2 minutes 40 seconds1 minute 11 seconds, then a 9-step setup wizard
Quotes to invoicesyesyes
Credit notesyesno
Automatic payment remindersyesno
Recurring invoicesyesyes
VAT report per rateyes, by invoice date and by paymentonly fully paid invoices
Client portalyes, in Swedishyes, in English
Peppol e-invoicesmakes the XML; sending needs a paid licence and creditsno
SIE exportnono
Swedish interfaceyesyes
LicenceElastic License 2.0, source-availableAGPL 3.0

Memory is the sum of each app's containers, without our test mail server, measured with podman stats five and seven minutes after a reboot; the two readings were the same. The peak is the highest reading while a script used each app's API for three minutes, about one request a second, every fifth one a PDF. Times are how long it took to download the images and start, not the time you need to read and type the commands. Both apps answered when we first checked, 104 seconds after the reboot command.

Setup

Invoice Ninja's official image, invoiceninja/invoiceninja-debian, runs PHP-FPM with a queue worker and the scheduler under supervisord; it needs a web server in front, so nginx runs next to it in the pod and serves the web files from a shared volume. On its first start, the image creates the database tables and the first account from two settings, an email address and a password, which we gave it as a Podman secret. After that you log in at your address, and a short form asks for the company name. We left out Redis: with the cache on disk and the queue in the database, every email and scheduled job ran.

InvoiceShelf's image, invoiceshelf/invoiceshelf, has nginx and PHP-FPM in one container and runs the scheduler itself. It answered after 71 seconds, and the rest happens in the browser: a wizard in 9 steps, from the language (Svenska is in the list) through checks, the database, the domain, email, the admin account and the company to currency and time zone. It asks for the database password again, although the container already has it. It warns that the company currency cannot be changed later.

Quotes, invoices and credit notes

Both turned our quote into an invoice with one action, and both added up the same 17,660 kr: 14,800 kr before VAT, and 2,500, 144 and 216 kr of VAT at 25, 12 and 6 %. Both let you set the VAT rate per line, which a Swedish invoice with several rates needs. In Invoice Ninja that is Item tax rates under Tax Settings; in InvoiceShelf Tax Per Item under Tax Types.

  • Invoice Ninja numbers invoices 0001, 0002 and so on, with separate series for quotes, credit notes and payments, which Skatteverket allows ("en eller flera serier"). Our credit note for the returned handbook, 477 kr with 6 % VAT, came out as credit note 0001 with its own PDF in Swedish, "Kredit". It refers to invoice 0001 only because we wrote that in its note; the Credit Note action on an invoice, which Invoice Ninja's guide describes, fills the reference in for you.
  • InvoiceShelf numbers invoices INV-000001 and up, which you can change. It has no credit notes. To reverse part of an invoice you would issue a new document by hand.

Deleting is where they differ most. When we deleted three test invoices in Invoice Ninja, it kept them with their numbers, renamed "0005_Ta bort" and so on, and the next invoice was 0008: a gap, but one you can explain. InvoiceShelf let us send invoice INV-000006, delete it, and then offered INV-000006 again for the next one. Skatteverket requires "ett unikt löpnummer för varje faktura", so in either app, cancel or credit a sent invoice instead of deleting it.

VAT, reverse charge and export

Neither app knows Swedish VAT rules; you create the rates and choose them per line. We made five: 25 %, 12 %, 6 %, "Omvänd skattskyldighet" at 0 % and "Export" at 0 %.

  • Reverse charge. For Bauer, an EU business, we used the 0 % rate and wrote "Reverse charge" and "Omvänd betalningsskyldighet" in the invoice's note, which Skatteverket says the invoice must state. Neither app adds that text by itself, or checks that the customer has a VAT number.
  • Invoice Ninja's automatic taxes. Invoice Ninja has a tax engine for the EU, the US and a few other regions that is meant to pick the rate from where the customer is. We switched it on with Sweden as the seller, and it set no tax at all on invoices to our three customers, so we went back to rates per line.
  • Export. For Northwind, outside the EU, we used the export rate and a note. Invoice Ninja also has a Tax exempt box on the customer.

The Swedish PDF

Both made a PDF in Swedish, "Faktura" with "Fakturadatum", "Förfallodatum" and the VAT per rate. We checked each against the list on Skatteverket's page on invoicing, read on 14 October 2026:

Required on a full invoiceInvoice NinjaInvoiceShelf
Date of issueyesyes
A unique number in one or more seriesyes; deleted invoices keep their numberyes, but a deleted number is used again
Seller's VAT numberyes, without a label; we added a labelled line in the footernot by default; added with a placeholder in the address format
Buyer's VAT number, for reverse chargeyes, without a labelnot by default; with the placeholder it is labelled "Org.Nr:"
Seller's and buyer's name and addressyes, with "Sverige" and "Tyskland"yes, with country names in English
Quantity and kind of goods or servicesyesyes
Date of supply, if not the invoice dateno field; we wrote it in the descriptionno field; same
Taxable amount per rate, unit price without VATunit price and amount per line; the base per rate is not summedsame
VAT rate and VAT amountyes, per rateyes, per rate
"Omvänd betalningsskyldighet" when the buyer pays the VATonly from our noteonly from our note

What we put in ourselves, in Invoice Ninja's footer and in InvoiceShelf's notes: the organisation number, "Godkänd för F-skatt", the bankgiro number and the payment terms. Invoice Ninja writes amounts as "17.660,00 kr", with a point between thousands where Swedes write a space; InvoiceShelf as "17.660,00kr". InvoiceShelf left "Amount Paid" in English on a part-paid invoice. Invoice Ninja's credit note had no footer until we filled in the separate credit note footer. When you invoice VAT in a foreign currency, Skatteverket also wants the VAT in kronor; our EUR invoice had reverse charge, so there was no VAT to show.

Payments, reminders and recurring invoices

Both took our partial payment of 10,000 kr and showed 7,660 kr left, on screen and on the PDF.

  • Invoice Ninja sends reminders by itself: we set the first one day after the due date and the second after 14 days. Rather than wait a day, we ran the reminder job by hand, and the overdue invoice got "Påminnelse 1" by email, "Påminnelse: Faktura 0004 från Exempel AB", with a link to the invoice; Anna got a note that it was sent. Late fees per reminder can be added. The monthly support invoice was created as invoice 0008 and emailed when we moved its date to today and ran the recurring job.
  • InvoiceShelf has no payment reminders. It marks invoices as overdue once a day, and you send a reminder yourself. Its recurring invoice was created and emailed within a minute of its schedule, by the scheduler inside the container.

Multi-currency

Both billed Bauer in euros, 1,080 € with "€" on the PDF, and both need you to give the exchange rate. Invoice Ninja fetches rates only with a key from Open Exchange Rates; without one, the rate was 1 and the reports added 1,080 euros as 1,080 kronor, until we set the rate on the invoice by hand. InvoiceShelf asks for the rate when the customer's currency differs, and can fetch it from one of four rate services with a key of your own. With 10.9 kr to the euro, it counted the invoice as 11,772 kr.

Expenses and reports

  • Invoice Ninja has expenses with vendors, categories and VAT: our 1,250 kr included 250 kr of VAT. Its Tax summary report lists VAT per rate both by invoice date and by payment, and invoice by invoice; ours showed 3,500 kr at 25 %, 144 kr at 12 % and 216 kr at 6 %. It did not subtract our credit note's 27 kr, and it did not list the VAT on expenses, which only appears in the profit and loss report. Reports come as CSV.
  • InvoiceShelf has expenses with categories but no VAT field, so the 250 kr of VAT you can claim back is only text in the note. Its Tax summary report counts only invoices that are fully paid: with the 17,660 kr invoice part-paid it showed 0 kr, and after we paid the September invoice in full, 1,000 kr at 25 %. That matches neither the invoice method nor the cash method Swedish companies report VAT by. Reports come as PDFs: sales by customer or item, expenses, profit and loss, and VAT.

The client portal

Invoice Ninja's emails carry a personal link: Lena at Nordljus opened it and saw her invoices, quotes, payments and credit notes in Swedish, with Visa PDF. A password can be required. Both the emails and the portal say "Skapad av Invoice Ninja" until you buy the white-label licence. InvoiceShelf's emails link to the invoice, and with a password for the customer, Lena logged in at /exempel-ab/customer/login and saw what she owed, in English.

Payment gateways

Neither is connected to a real account in our test. Invoice Ninja has Stripe, PayPal, Mollie, GoCardless and many others built in under Settings > Online payments: with Stripe connected, the client portal shows a Pay now button for cards and other methods Stripe offers, and invoices can be charged automatically. InvoiceShelf needs its free Payments module for Stripe, PayPal or Razorpay, which you download from GitHub and install with a command; it works on InvoiceShelf 2 only. In Sweden, many customers simply pay by bankgiro, so the number on the invoice matters more.

E-invoices and Peppol

Under the e-invoicing act (2018:1277), Swedish public buyers must receive e-invoices in Peppol BIS Billing 3, and the European Commission's page on Sweden says "There is no business-to-business (B2B) eInvoicing mandate." The EU's VAT in the Digital Age package, adopted on 11 March 2025, brings digital reporting based on e-invoices for cross-border B2B sales from 1 July 2030.

  • Invoice Ninja made e-invoices from our invoice, set under Settings > E-Invoice: a Peppol UBL file, and in the other formats, CII files for EN 16931 and XInvoice. Sending over the Peppol network goes through Invoice Ninja's own access point, with Sweden among the supported countries. A self-hosted server needs the white-label licence for that, and prepaid credits, which Invoice Ninja's announcement priced at 50 dollars for 500 documents in December 2024. We did not send any.
  • InvoiceShelf has no e-invoicing. To invoice a Swedish municipality or agency, you would need another way.

Invoicing is not bookkeeping

A Swedish company must keep its accounts under the Bookkeeping Act (1999:1078): every business event needs a voucher, events are booked in order of entry and by account, the system must be documented, and the records kept "fram till och med det sjunde året" after the end of the financial year. Bokföringsnämnden's guidance to BFNAR 2013:2 explains the details. This is not legal advice; ask your accountant.

Neither app is a bookkeeping system in that sense. They have no accounts, no journal or general ledger, no VAT return and no year-end. Neither exports SIE, the file format Swedish bookkeeping programs exchange; we searched both code bases and found none. What you get is invoices, payments and expenses that you book elsewhere: Invoice Ninja exports them as CSV and reports, InvoiceShelf as PDF reports. Both work as a place to make and send invoices, not as your books, and the seven years of keeping apply to the invoices you send too, so keep the PDFs or your backups.

API

Both have a REST API, and we created all our data through it. Invoice Ninja's API at /api/v1 covers everything the app does, with a token per user and documentation. InvoiceShelf's API at /api/v1 is the one its web pages use, with tokens from a login call; there is API documentation on GitHub.

Swedish

Both come in Swedish. Invoice Ninja took the company's language, Swedish, for the admin pages, the PDFs, the emails and the client portal; each customer can have a language of its own. A few words are odd, "Försäljare" for vendors and "Beställning" for purchase orders. InvoiceShelf is in Swedish when you choose Svenska in the wizard, and so is the PDF, but the client portal stayed in English in our test, the PDF wrote "Sweden" and "Germany", and the label for VAT numbers is swapped: the company form asks for "Momsnummer" in the field the PDF calls the tax ID and "Organisationsnummer" in the one it calls the VAT ID.

Resources

Invoice Ninja keeps PHP-FPM, two queue workers and the scheduler running, about 240 MB, plus MariaDB. Its image is 2.8 GB because it brings a Chrome to make PDFs, and making them took it to about 610 MB. InvoiceShelf used about 43 MB at rest and 75 MB at the peak, the smallest app we have tested. Together they used about 425 MB at rest, so either fits a small server.

Updates and backups

Invoice Ninja releases often, four times in the week before our test. Our files pin the version, 5.13.47; you update by changing the tag, and the image updates the database and copies the new web files into its volume when it starts. InvoiceShelf pins 2.4.6, and its Docker guide says to update by the image tag; the updater inside the app is off in Docker. Its stable line 2 gets security fixes only until 1 September 2027, according to its README, and version 3 was an alpha on our test day. We did not test an update of either.

Each backup is a database dump and a volume export: 0.55 MB and 4.2 MB for Invoice Ninja, 0.15 MB and 0.9 MB for InvoiceShelf. We restored both into empty volumes, in about 40 seconds each, and all invoices, payments and settings came back.

How we ran Invoice Ninja

Invoice Ninja's Docker files use Docker Compose with the app, nginx, MySQL and Redis. We turned that into a pod with MariaDB 11.8 and no Redis. Things we learned:

  • The image runs PHP-FPM on port 9000 and serves no web pages itself; nginx in the pod does, from the invoiceninja-public volume the app fills on every start.
  • On the first start, the image creates the first account from IN_USER_EMAIL and IN_PASSWORD, and stops if they are missing.
  • TRUSTED_PROXIES is the server's own IPv4 address, as in the Podman guide. With it, the activity log showed each visitor's real address.
  • APP_KEY encrypts data in the database. Keep it: without it a restore cannot be read.

As a user invoiceninja, created like kimai in step 1 of the Kimai guide, create the secrets:

printf %s "base64:$(openssl rand -base64 32)" | podman secret create invoiceninja-app-key -
openssl rand -hex 24 | tr -d '\n' | podman secret create invoiceninja-db-password -
printf %s 'your-admin-password' | podman secret create invoiceninja-admin-password -
printf %s 'your-smtp-password' | podman secret create invoiceninja-smtp-password -

In ~/.config/containers/systemd, invoiceninja.pod:

[Pod]
PodName=invoiceninja
PublishPort=127.0.0.1:8148:8080

[Install]
WantedBy=default.target

invoiceninja-db.container:

[Container]
ContainerName=invoiceninja-db
Image=docker.io/library/mariadb:11.8
Pod=invoiceninja.pod
Volume=invoiceninja-db:/var/lib/mysql
Environment=MARIADB_DATABASE=ninja MARIADB_USER=ninja MARIADB_RANDOM_ROOT_PASSWORD=1 MARIADB_AUTO_UPGRADE=1
Secret=invoiceninja-db-password,type=env,target=MARIADB_PASSWORD
HealthCmd=healthcheck.sh --connect --innodb_initialized
HealthInterval=10s
Notify=healthy
AutoUpdate=registry

[Service]
Restart=always

invoiceninja.env, without secrets:

APP_URL=https://invoice.example.com
APP_ENV=production
APP_DEBUG=false
REQUIRE_HTTPS=true
TRUSTED_PROXIES=203.0.113.10
PDF_GENERATOR=snappdf
FILESYSTEM_DISK=debian_docker
IS_DOCKER=true
SCOUT_DRIVER=null
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_CONNECTION=database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=ninja
DB_USERNAME=ninja
IN_USER_EMAIL=anna@example.com
MAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_ENCRYPTION=tls
MAIL_USERNAME=invoices@example.com
MAIL_FROM_ADDRESS=invoices@example.com
MAIL_FROM_NAME="Exempel AB"

invoiceninja-app.container:

[Unit]
Requires=invoiceninja-db.service
After=invoiceninja-db.service

[Container]
ContainerName=invoiceninja-app
Image=docker.io/invoiceninja/invoiceninja-debian:5.13.47
Pod=invoiceninja.pod
Volume=invoiceninja-public:/var/www/html/public
Volume=invoiceninja-storage:/var/www/html/storage
EnvironmentFile=%h/.config/containers/systemd/invoiceninja.env
Secret=invoiceninja-app-key,type=env,target=APP_KEY
Secret=invoiceninja-db-password,type=env,target=DB_PASSWORD
Secret=invoiceninja-admin-password,type=env,target=IN_PASSWORD
Secret=invoiceninja-smtp-password,type=env,target=MAIL_PASSWORD

[Service]
Restart=always
TimeoutStartSec=900

invoiceninja-web.container:

[Unit]
Requires=invoiceninja-app.service
After=invoiceninja-app.service

[Container]
ContainerName=invoiceninja-web
Image=docker.io/library/nginx:alpine
Pod=invoiceninja.pod
Volume=%h/.config/containers/systemd/invoiceninja-nginx.conf:/etc/nginx/conf.d/default.conf:ro
Volume=invoiceninja-public:/var/www/html/public:ro
Volume=invoiceninja-storage:/var/www/html/storage:ro
AutoUpdate=registry

[Service]
Restart=always

And invoiceninja-nginx.conf:

server {
    listen 8080;
    root /var/www/html/public;
    index index.php;
    charset utf-8;
    client_max_body_size 10M;
    server_tokens off;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known) {
        deny all;
    }
}

Run systemctl --user daemon-reload and systemctl --user start invoiceninja-pod. The first start downloads 3.2 GB and takes a few minutes; it is ready when curl -s http://127.0.0.1:8148/health prints {"status":"ok","message":"API is healthy"}. The block in Caddy's ~/Caddyfile:

invoice.example.com {
    reverse_proxy 127.0.0.1:8148
}

Log in with anna@example.com and the admin password, then under Settings: Company Details for name, address, VAT number and organisation number (ID Number), Localization for Swedish, Swedish Krona, Europe/Stockholm and the date format yyyy-MM-dd, Tax Settings to create the rates and switch on Item tax rates, and Templates & Reminders for reminders.

To back up, run podman exec invoiceninja-db sh -c 'mariadb-dump -u ninja -p"$MARIADB_PASSWORD" --single-transaction ninja' > ninja-db.sql and podman volume export invoiceninja-storage > ninja-storage.tar, and keep the app key secret. To restore, stop the pod, remove the three volumes, create and import invoiceninja-storage, start invoiceninja-db, load the dump with podman exec -i invoiceninja-db sh -c 'mariadb -u ninja -p"$MARIADB_PASSWORD" ninja' < ninja-db.sql, and start the pod; the app refills the public volume.

Sending through a real email provider was not tested; our test sent to a local test server instead.

How we ran InvoiceShelf

InvoiceShelf's Docker guide has Compose files for SQLite, MySQL and PostgreSQL. We ran the PostgreSQL one as a pod with PostgreSQL 18. Things we learned:

  • The setup wizard asks for the database password, although the container has it. Read it with podman secret inspect --showsecret --format '{{.SecretData}}' invoiceshelf-db-password.
  • InvoiceShelf keeps its email settings, the password included, in its database: you enter them in the wizard or under Settings > Mail Configuration, not as a secret.
  • The container writes its .env file from the environment each time it starts. We rebooted and restored it, and the settings, users and sign-ins came back each time.
  • To print the VAT numbers, add {COMPANY_TAX} and {CONTACT_TAX_ID} to the address formats under Settings > Customization > Invoices.

As a user invoiceshelf, create the secrets:

openssl rand -hex 24 | tr -d '\n' | podman secret create invoiceshelf-db-password -
printf %s "base64:$(openssl rand -base64 32)" | podman secret create invoiceshelf-app-key -

In ~/.config/containers/systemd, invoiceshelf.pod:

[Pod]
PodName=invoiceshelf
PublishPort=127.0.0.1:8149:8080

[Install]
WantedBy=default.target

invoiceshelf-db.container:

[Container]
ContainerName=invoiceshelf-db
Image=docker.io/library/postgres:18-alpine
Pod=invoiceshelf.pod
Volume=invoiceshelf-db:/var/lib/postgresql
Environment=POSTGRES_USER=invoiceshelf POSTGRES_DB=invoiceshelf
Secret=invoiceshelf-db-password,type=env,target=POSTGRES_PASSWORD
HealthCmd=pg_isready -U invoiceshelf
HealthInterval=10s
Notify=healthy
AutoUpdate=registry

[Service]
Restart=always

invoiceshelf.env:

APP_NAME=InvoiceShelf
APP_ENV=production
APP_DEBUG=false
APP_URL=https://books.example.com
SESSION_DOMAIN=books.example.com
SANCTUM_STATEFUL_DOMAINS=books.example.com
TRUSTED_PROXIES=203.0.113.10
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=invoiceshelf
DB_USERNAME=invoiceshelf
CACHE_STORE=file
SESSION_DRIVER=file
SESSION_LIFETIME=240
AUTORUN_ENABLED=true
AUTORUN_LARAVEL_MIGRATION=false
AUTORUN_LARAVEL_OPTIMIZE=false
PHP_OPCACHE_ENABLE=1

invoiceshelf-app.container:

[Unit]
Requires=invoiceshelf-db.service
After=invoiceshelf-db.service

[Container]
ContainerName=invoiceshelf-app
Image=docker.io/invoiceshelf/invoiceshelf:2.4.6
Pod=invoiceshelf.pod
Volume=invoiceshelf-storage:/var/www/html/storage
Volume=invoiceshelf-modules:/var/www/html/Modules
EnvironmentFile=%h/.config/containers/systemd/invoiceshelf.env
Secret=invoiceshelf-app-key,type=env,target=APP_KEY
Secret=invoiceshelf-db-password,type=env,target=DB_PASSWORD

[Service]
Restart=always
TimeoutStartSec=600

Run systemctl --user daemon-reload and systemctl --user start invoiceshelf-pod, add this block to Caddy's ~/Caddyfile, and open https://books.example.com for the wizard. Choose smtp for email, with your provider's host, port 587 and tls.

books.example.com {
    reverse_proxy 127.0.0.1:8149
}

To back up, run podman exec invoiceshelf-db pg_dump -U invoiceshelf invoiceshelf > invoiceshelf-db.sql and podman volume export for invoiceshelf-storage and invoiceshelf-modules. To restore, stop the pod, remove the three volumes, create and import the two, start invoiceshelf-db, load the dump with podman exec -i invoiceshelf-db psql -U invoiceshelf invoiceshelf < invoiceshelf-db.sql, and start the pod.

Licence and cost

We read the licence texts and pricing pages on 14 October 2026.

  • Invoice Ninja is made by Invoice Ninja LLC. Version 5 is under the Elastic License 2.0, which lets you use, change and run it, but not "provide the software to third parties as a hosted or managed service", not get around its licence key, and not remove its notices. Its README calls the code "source-available": it is not an open-source licence. Invoice Ninja's own reading is that invoicing your own customers with it is free, and that a web host may install it for clients, but reselling it as your own invoicing service needs a commercial licence. Every feature on this page is in the free self-hosted version; the README offers a white-label licence for 40 US dollars a year that removes Invoice Ninja's branding from the client portal, emails and PDFs and is required for Peppol. The hosted service, on its pricing page, is free for up to 5 clients, Pro costs 14 dollars a month (140 a year), and Enterprise, with Peppol and 250 Peppol credits, from 18 dollars a month.
  • InvoiceShelf is open source under the AGPL 3.0, and its website, operated by Ideologix Media DOOEL, lists no paid plan. Its modules, such as Payments and White Label, are free on GitHub under the AGPL.

For a company invoicing its own customers, both cost nothing beyond the server.

FreshBooks, QuickBooks Online and Zoho Invoice today

What follows is from the services' own pages, read on 14 October 2026, not from our tests. Prices are in US dollars a month, as the pages showed them to us.

FreshBooks costs 23 dollars for Lite with 5 billable clients, 43 for Plus with 50 and 70 for Premium with no limit, with introductory discounts, and 11 dollars a month for each extra team member; Select has no public price. Every plan has recurring invoices, automatic payment reminders, estimates and bank connections. Accountant access and double-entry accounting reports start at Plus. The page does not say where data is stored.

QuickBooks Online showed us a Free plan with 2 invoices a month and one bank connection, Simple Start at 38 dollars for one user, Essentials at 85 for three, Plus at 140 for five and Advanced at 340 for 25, with 50 % off for three months. Paid plans include 2 accountants, Advanced 3. These are Intuit's US plans; we found no Swedish edition on its site.

Zoho Invoice is free: up to two users, 500 invoices a year, recurring invoices, automatic payment reminders, a customer portal and online payments. The page does not say where data is stored.

None of the three pages mentions Swedish bookkeeping or SIE.

What you give up compared with FreshBooks and QuickBooks

On your own server, your customers, prices and invoices stay in Sweden, the cost is the server, not a price per client or user, and nobody changes the plans under you. But be honest about what you leave behind:

  • Bookkeeping. QuickBooks is accounting software, and FreshBooks has double-entry reports from Plus. Invoice Ninja and InvoiceShelf only invoice: you still need a bookkeeping system and someone to book the invoices, and neither sends them there for you.
  • Bank feeds. FreshBooks connects banks in every plan and QuickBooks from its Free plan. Invoice Ninja can import EU bank transactions through GoCardless, but a self-hosted server needs GoCardless keys of its own, and GoCardless stopped taking new Bank Account Data sign-ups in July 2025, according to Actual Budget's documentation; a CSV file from your bank works instead. InvoiceShelf has no bank import.
  • Your accountant's access. FreshBooks and QuickBooks have an accountant role. In ours, you give your accountant a user account, which we did not test, or send exports: CSV from Invoice Ninja, PDF reports from InvoiceShelf.
  • Swedish details you fix yourself. Neither app adds the reverse charge text, sums the taxable amount per rate, or has a field for the date of supply, and InvoiceShelf hides VAT numbers until you change its layout.
  • Running it. Updates, backups and the server are yours, and you need an email account for sending invoices and reminders.

Which one to pick

A consultancy or small business that invoices every week: Invoice Ninja. It has quotes, credit notes, partial payments, reminders, recurring invoices, a Swedish client portal, a VAT report per rate, expenses with VAT, a full API and a way to Peppol. Its weak points are the memory, about 380 MB at rest and 610 MB making PDFs, 3.2 GB of images, a licence that is not open source, branding in the client emails without the white-label licence, exchange rates you set yourself, and a VAT report that left out our credit note. We have no guide for it yet: the files in How we ran Invoice Ninja are what we ran, on top of the Podman guide. A VC-P Alloy runs it with room to spare.

A freelancer with a few invoices a month: InvoiceShelf. It is small, about 43 MB, AGPL-licensed with nothing to buy, in Swedish, and does quotes, invoices, recurring invoices, partial payments and PDF reports. Its weak points are no credit notes, no reminders, no e-invoicing, a VAT report that counts only fully paid invoices, expenses without VAT, a client portal in English, VAT numbers you must add to the layout, invoice numbers that can be used twice if you delete, and security fixes for version 2 only until September 2027. Its files are in How we ran InvoiceShelf; a VC-P Alloy suits it, next to other apps.

Whichever you choose, pair it with a Swedish bookkeeping program, and if you bill by the hour, see Kimai and Solidtime for time tracking. Together our two apps used about 425 MB at rest, so one VC-P Alloy runs both, as ours did.

Run it on your own server

SSD RAID 1 · ECC DDR4 · 10 Gbit

VC-P Alloy

€8.99/mo

vCPU
2
Memory
8 GB
Storage
60 GB
Transfer
10 TB
Performance
SSD · RAID 1
  • Full root access
  • Native /64 IPv6
  • RAID-protected storage
  • Malmö, Sweden
  • Month to month, no lock-in
  • 7-day money-back guarantee
All guides