GuidesBusiness and monitoringTime tracking with Kimai

Track time and invoice with Kimai and rootless Podman

Kimai on Debian 13, the open-source time tracker for teams and consultants, with customers, projects, a timer, reports and PDF invoices in SEK, in rootless Podman with MariaDB under a user of its own behind Caddy, in Swedish.

Tested on Kimai 2.69.0 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

What you will set up

Kimai is a time tracker for a team or a consultancy. You keep customers, projects and activities, everyone records their hours with a timer or by hand, and Kimai turns the hours into reports, exports in Excel, CSV or PDF, and invoices with your hourly rates and VAT. It handles any currency, Swedish kronor included, and its interface is translated into Swedish.

Kimai is created by Kevin Papst, an independent developer in Vienna, Austria, and developed with its community in the open on GitHub. It is open source under the AGPL-3.0 licence. Kevin Papst also runs a hosted version, Kimai Cloud, and sells plugins. This guide uses the official container image, kimai/kimai2, which the Kimai project publishes itself.

Here Kimai runs as a pod under a user of its own called kimai, with MariaDB for its data, behind Caddy from the Podman guide.

Every step below was run on a fresh Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13:

  • Kimai 2.69.0 started with MariaDB 11.8, with no account until the first admin was created from the command line, and no way for visitors to sign up.
  • The interface was shown in Swedish, with Swedish dates and number formats and Swedish time, and SEK was set as the currency for new customers.
  • Two customers, a project with a budget, two activities and an hourly rate of 950 kr were created. Hours were recorded by hand and with the timer.
  • An invoice in Swedish was created as a PDF, with 25 % VAT and a total in kronor, and the hours were marked as invoiced. A report was exported as Excel and as PDF.
  • Login attempts were limited per visitor address, over IPv4 and IPv6, and the session cookie was sent over HTTPS only.
  • A password reset email reached a test mail server, with a working login link. Without email, a reset said an email was sent, and none was.
  • In a capture of the server's traffic, Kimai made no outside connections while it started, recorded time, and made invoices and exports. Two admin pages are the exception: System > Plugins fetches Kimai's list of plugins from www.kimai.org, and System > Doctor asks GitHub for the latest release.
  • An update from Kimai 2.68.0 to 2.69.0 went through podman auto-update. Kimai was restored from a backup into empty volumes, with its invoices, and everything came back by itself after a reboot.

The pod used about 200 MB of memory, most of it MariaDB.

Before you start

You need:

  • a server set up as in the Podman guide, with Caddy running;
  • an A record and an AAAA record for time.example.com pointing at your server.

The examples use time.example.com for Kimai, 203.0.113.10 for your server's IPv4 address, which ip -brief address show eth0 shows, Exempel AB for your own company and Nordljus AB for a customer. Replace them throughout.

1. Create the user

As root:

useradd -m -s /bin/bash kimai
loginctl enable-linger kimai
machinectl shell kimai@

Everything up to step 5 runs as kimai.

2. Create the secrets

pw=$(openssl rand -hex 24)
printf %s "$pw" | podman secret create kimai-db-password -
printf %s "mysql://kimai:$pw@127.0.0.1:3306/kimai?charset=utf8mb4&serverVersion=11.8.0-MariaDB" | podman secret create kimai-database-url -
unset pw

The first secret is the database password, for MariaDB. Kimai takes its database details as one address, DATABASE_URL, with the same password in it, so that is the second secret. Both are stored by Podman, not in the files below.

3. Define the pod

mkdir -p ~/.config/containers/systemd
cd ~/.config/containers/systemd

Create kimai.pod:

[Pod]
PodName=kimai
# Only Caddy, on this server, can reach Kimai: the port is not open to the internet.
PublishPort=127.0.0.1:8160:8001

[Install]
WantedBy=default.target

Create kimai-db.container:

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

[Service]
Restart=always

And kimai-app.container:

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

[Container]
ContainerName=kimai-app
Image=docker.io/kimai/kimai2:apache
Pod=kimai.pod
Volume=kimai-data:/opt/kimai/var
Environment=DEFAULT_URI=https://time.example.com TRUSTED_PROXIES=203.0.113.10
Environment=TRUSTED_HOSTS=^(time[.]example[.]com|localhost)$
Secret=kimai-database-url,type=env,target=DATABASE_URL
AutoUpdate=registry

[Service]
Restart=always

What the settings do:

  • DEFAULT_URI is Kimai's public address, which it puts in the links of its emails.
  • TRUSTED_PROXIES is the address Kimai trusts to tell it the visitor's real address, and that the visitor came over HTTPS. Caddy's connections arrive in the pod from the server's own IPv4 address, as the Podman guide explains, so that is the one to name. Kimai blocks a login after five failed attempts from the same address for five minutes; without this line every visitor has the server's address, and one person's mistakes block everyone.
  • TRUSTED_HOSTS lists the names Kimai answers to, and turns away requests for any other name. localhost is for checks from inside the container, such as the one below. Kimai switches off password reset until it is set. Write the dots as [.], as shown.
  • kimai:apache is the official image with its own web server, Apache, and the tag follows every Kimai release. Kimai updates its database by itself when it starts.
  • MariaDB stays on 11.8, a long-term release, and MARIADB_AUTO_UPGRADE updates its system tables after an update within it.
  • The kimai-data volume holds the generated invoices, Kimai's own secret key, which it makes on the first start, and its cache. The customers, hours and settings are in the database.

Start it:

systemctl --user daemon-reload
systemctl --user start kimai-pod
systemctl --user enable --now podman-auto-update.timer

The first start downloads about 1.2 GB of images, and Kimai takes a minute to set up its database. It is ready when this prints 200:

podman exec kimai-app curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8001/en/login

4. Create your admin account

Kimai starts without any account, so nobody can log in until you create one. Use Kimai's own command, with your own user name and email address:

podman exec -it --user www-data kimai-app /opt/kimai/bin/console kimai:user:create anna anna@example.com ROLE_SUPER_ADMIN

It asks for a password, at least 8 characters long, and prints "Success! Created user: anna". ROLE_SUPER_ADMIN gives the account every right, including the system settings. --user www-data runs the command as the user Kimai's web server runs as, so that it does not leave files in the volume that Kimai cannot read.

5. Put Caddy in front

Go back to root with exit, switch to machinectl shell caddy@, and add this block at the end of ~/Caddyfile:

time.example.com {
    reverse_proxy 127.0.0.1:8160
}

Restart Caddy with systemctl --user restart caddy, open https://time.example.com, and log in with the user name and password from step 4.

There is no sign-up page: you create every account yourself under System > Users. Each user can switch on two-factor authentication under their profile menu, top right, > Two-Factor (2FA).

6. Use Kimai in Swedish

The first time you log in, a short wizard asks for your settings. To use Kimai in Swedish, choose:

  • Language: Svenska;
  • Time, date and currency format: Svenska (Sverige), which writes dates as 2026-10-14 and amounts as 1 234,56 kr;
  • Timezone: Europe / Stockholm.

The wizard switches to Swedish as soon as you continue. Everyone can change these later under their profile menu > Preferences.

Kimai's default for new users is English and UTC. To make Swedish the default for everyone you create, open System > Settings, and under User set Timezone to Europe / Stockholm, Language to Svenska and Time, date and currency format to Svenska (Sverige), then Save. In Swedish, the same page is System > Systeminställningar, under Användare, with Spara.

7. Set up your company

The settings page has one form per area, each with its own Save button. Under System > Settings:

  • My company: enter your Company name, which Kimai shows at the top left, and set Currency to Swedish krona, SEK.
  • Customer: set Country to Sweden, Language to Svenska, and Timezone to Europe / Stockholm.
  • Invoices: Invoice number format is {Y}/{cy,3} by default, the year and a counter per customer and year, such as 2026/001. Change it if your numbering differs.

New customers get the currency, country, language and time zone set here. The currency is kept on each customer, so you can bill one customer in euros and the rest in kronor. Kimai writes the names of countries and currencies in the format you chose in step 6, such as "svensk krona" for Swedish.

8. Customers, projects and activities

Hours are recorded on a customer, one of its projects, and an activity. Under Administration:

  1. Customers > Create. First create your own company, Exempel AB: Kimai uses a customer as the sender of an invoice. Fill in Name, and under Address your Company name, address, and Country, and under Invoices your VAT-ID, then Save.
  2. Create the customer you bill, Nordljus AB, the same way. Currency is already SEK.
  3. On the customer's page, choose + next to Prices, enter 950 as Price, and Save. This is the hourly rate for all its projects; leave Fixed price off. A price can also be set on a project, an activity or a single user.
  4. Projects > Create, choose the Customer, give it a Name, such as Ny webbplats, and, if you like, a Budget in kronor, then Save. The project page shows how much of the budget is used.
  5. Activities > Create, such as Utveckling and Möte. Without a Project, an activity can be used in every project.

9. Track time

Under Time Tracking > My times:

  • The play button at the top starts the timer: choose the customer, project and activity, and Save with Duration / End empty. The button turns into a stop button with the running time; choose it when you are done.
  • Create adds hours afterwards: a date, a start time, and a Duration, such as 2:30.

Weekly hours shows a week as a grid to fill in, and Calendar shows your hours as a calendar. Under Reporting, Kimai sums up hours and amounts by user, customer and project.

10. Invoices

An invoice needs a template first. Under Invoices > Invoice template > Create:

  • Name, such as Standard, and Title, the heading of the invoice, such as Faktura.
  • Invoice issuer: your own company from step 8.
  • Terms of payment, such as Betalning inom 30 dagar., Bank account, such as your bankgiro number, and Payment term in days.
  • Tax rate: 25 for 25 % VAT.
  • Language: Svenska.
  • Invoice template: leave it at Invoice, the default, which makes a PDF. The list has a second Invoice, under HTML, which makes an HTML file instead. Grouping of invoice lines gives one line per entry by default.

Then, under Invoices > Create invoice, choose the Customer, check the Time range, and choose Search. The preview shows the customer, the hours and the total with VAT. Preview shows the invoice itself, and Save creates it: it gets the next number, is stored under Invoice history, where Download gives you the PDF, and its hours are marked as exported, so they are not invoiced twice.

Our test invoice, in Swedish, had the sender and the customer with their VAT numbers, a due date 30 days ahead, one line per entry at 950,00 kr an hour, the net amount, 25 % VAT and the total in kronor. In Invoice history you mark an invoice as paid, or cancel it.

11. Export a report

Time Tracking > Export gives you the hours for a customer or a period as a file, without making an invoice, for example to send with an invoice from your accounting system. Choose the filters and Search, then CSV, Excel or PDF. The PDF opens in a new tab, with a summary per customer and project and a full list of entries. Mark as exported marks the hours as exported, like an invoice does.

12. Email

Kimai sends email for password resets, from I forgot my password on the login page. This guide does not set up email, and without it:

  • a reset request says "An email has been sent with a link to reset your password", and no email is sent;
  • after a reset request, Kimai asks that user for a new password at their next login.

Logins with a password keep working, and you can set a new password for anyone under System > Users, in the menu next to the user, > Password.

To send email, point Kimai at your email provider's SMTP server. Kimai takes the server, the account and its password as one address. As kimai, store it as a secret, with your own account, password and server:

printf %s 'smtp://time%40example.com:the-smtp-password@mail.example.com:587' | podman secret create kimai-mailer-url -

The @ in the account name is written %40. Write other characters in the password the same way, such as %2F for / and %3A for :, or choose a password of letters and digits. Then add these lines to kimai-app.container, with your sender address:

Environment=MAILER_FROM=time@example.com
Secret=kimai-mailer-url,type=env,target=MAILER_URL

And restart:

systemctl --user daemon-reload
systemctl --user restart kimai-app

Port 587 uses STARTTLS. For port 465, which uses TLS from the start, begin the address with smtps:// and end it with :465. We tested sending against a test mail server on the same server, without TLS; sending through a real email provider was not tested.

13. Back up

As kimai:

umask 077
mkdir -p ~/backup
podman exec kimai-db sh -c 'mariadb-dump -ukimai -p"$MARIADB_PASSWORD" --single-transaction kimai' > ~/backup/kimai-db.sql
podman volume export kimai-data > ~/backup/kimai-data.tar

The dump holds the customers, projects, hours, users and settings, and runs while Kimai keeps working. The volume holds the PDF invoices Kimai has made and its secret key. Copy ~/backup to another machine, for example with restic, and store it safely: it holds all your hours and your customers' details.

To restore into an empty pod, as kimai, with the backup in ~/backup:

systemctl --user stop kimai-pod
podman volume rm kimai-db kimai-data
podman volume create kimai-data
podman volume import kimai-data ~/backup/kimai-data.tar
systemctl --user start kimai-db
podman exec -i kimai-db sh -c 'mariadb -ukimai -p"$MARIADB_PASSWORD" kimai' < ~/backup/kimai-db.sql
systemctl --user start kimai-app

Starting only the database first loads the backup before Kimai sets up an empty one. On a new server, follow steps 1 to 3 first, but restore before you create an account in step 4.

14. Update

podman auto-update checks once a day for new images of Kimai and MariaDB, and restarts the pod when one has changed. Kimai updates its database when it starts. To update now:

podman auto-update

To decide yourself when to update, replace apache in kimai-app.container with a version, such as 2.69.0 from the image's list of tags, which is the same image, remove the AutoUpdate=registry line, and change the version when you want to update. Either way, keep your backups current, and read the release notes on Kimai's GitHub page before larger updates.

Troubleshooting

"Too many failed login attempts, please try again in 5 minutes." for everyone at once. TRUSTED_PROXIES is missing or names the wrong address, so Kimai sees every visitor as the server. It must be the server's IPv4 address, not 127.0.0.1. Fix it in kimai-app.container, then daemon-reload and restart kimai-app.

Every page gives an error 400. The name in the browser's address bar does not match TRUSTED_HOSTS. Check the name in kimai-app.container, with each dot written [.].

I forgot my password gives an error 503. TRUSTED_HOSTS is not set; Kimai's log, podman logs kimai-app, says "Password reset is deactivated". Add the line from step 3.

Kimai asks for a new password after login. Someone requested a password reset for that account. Set a new password, and it does not ask again.

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