Apps / Docker Images
Kimai in Docker.
Time tracking, production ready.
Kimai is an open source time-tracking application for freelancers and teams: timesheets, projects and activities, customers, rates, exports, and invoicing, with roles and a REST API.
docker pull ghcr.io/remarkablecloud/kimai Current build
2.66.0-r1
Updated Sep 11, 2026
Base
Kimai 2 official, digest-pinned
What's inside
MariaDB pairing, admin auto-provisioning, healthcheck
Upstream license
Kimai: AGPL-3.0
Free to pull and run anywhere. Digest-pinned, updated deliberately; each build is recorded in the changelog.
The image
What our image adds.
Kimai is an open source time-tracking application for freelancers and teams: timesheets, projects and activities, customers, rates, exports, and invoicing, with roles and a REST API.
Digest-pinned base
Built from `kimai/kimai2@sha256:d6747832bafc63b69f95d505471e43430f55f2e8a5ca19d094a166c5c2da2239` (Kimai 2.66.0, PHP 8.3, Apache, Debian 12). Pinning by digest keeps rebuilds reproducible.
Env-driven install
On first start the upstream entrypoint waits for the database, runs migrations, and creates the admin. Our wrapper generates a strong admin password, persists it in the data volume, and prints the login once. You can pin your own with `ADMINPASS`.
Safe database URL
The wrapper builds `DATABASE_URL` from separate host, port, name, user, and password variables, URL-encoding the password so special characters cannot break the connection string.
Persistent app secret
The upstream entrypoint auto-generates a unique `APP_SECRET` and persists it in `var/data`, so it never runs with the public default and sessions stay valid across restarts.
Proxy-aware
`TRUSTED_PROXIES` covers private networks, so Kimai honors `X-Forwarded-Proto` behind a TLS-terminating reverse proxy and generates correct https URLs.
Healthcheck
A container `HEALTHCHECK` confirms the app responds so the platform routes traffic only once Kimai is serving. The current published tag is `ghcr.io/remarkablecloud/kimai:2.66.0-r1`. Kimai is a trademark of its respective owner. RemarkableCloud packages the op
The guide
Run it in production.
Architecture at a glance
- Kimai (Apache and PHP) serves plain HTTP on port
8001. TLS is terminated upstream by a reverse proxy (Traefik on the RemarkableCloud App Platform); withTRUSTED_PROXIESset, Kimai derives https URLs from the forwarded headers. - MariaDB is a separate database container and holds all timesheets and configuration.
- A data volume at
/opt/kimai/var/dataholds theAPP_SECRETand uploaded data. Application code stays in the image.
Docker Compose walkthrough
The standalone stack (Kimai and MariaDB) is defined in the image’s docker-compose.yml:
name: kimai
services:
db:
image: mariadb:11
restart: unless-stopped
environment:
MARIADB_DATABASE: kimai
MARIADB_USER: kimai
MARIADB_PASSWORD: ${DB_PASSWORD:-change-me-db}
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-change-me-root}
volumes: [db-data:/var/lib/mysql]
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 12
kimai:
image: ${KIMAI_IMAGE:-ghcr.io/remarkablecloud/kimai:2.66.0-r1}
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports: ["${HTTP_PORT:-8098}:8001"]
environment:
KIMAI_DB_HOST: db
KIMAI_DB_PORT: "3306"
KIMAI_DB_NAME: kimai
KIMAI_DB_USER: kimai
KIMAI_DB_PASSWORD: ${DB_PASSWORD:-change-me-db}
ADMINMAIL: ${ADMINMAIL:-admin@example.com}
ADMINPASS: ${ADMINPASS:-}
volumes:
- kimai-data:/opt/kimai/var/data
volumes:
db-data:
kimai-data:
Step by step:
- The app waits for the database.
depends_onwithcondition: service_healthyavoids a first-boot race, and Kimai’s entrypoint also waits for the connection. - Install is env-driven. The
KIMAI_DB_*variables drive migrations and admin creation; no web wizard. - The admin password is generated if blank. Leave
ADMINPASSempty and the image creates a strong one on first run and logs it once. - The web port is 8001.
${HTTP_PORT:-8098}:8001maps it for local use; in production the proxy connects to port8001on the internal network. - State lives in volumes.
kimai-data(var/data) anddb-datapersist across restarts and upgrades.
To start it locally:
DB_PASSWORD=$(openssl rand -hex 16) docker compose up -d
Then browse to http://localhost:8098 and read the admin login from docker compose logs kimai. For production, put the container behind a TLS-terminating reverse proxy; Kimai derives the scheme from the forwarded headers.
Environment variable reference
These are wired automatically by the App Platform backend from the managed MariaDB; they are listed here for standalone and self-hosted runs.
| Variable | Required | Value / default | Purpose |
|---|---|---|---|
KIMAI_DB_HOST | yes | <db_host> | MariaDB host. |
KIMAI_DB_PORT | no | 3306 | MariaDB port. |
KIMAI_DB_NAME | yes | <db_name> | Database name. |
KIMAI_DB_USER | yes | <db_user> | Database user. |
KIMAI_DB_PASSWORD | yes | <db_password> | Database password (URL-encoded into DATABASE_URL). |
ADMINMAIL | no | admin@example.com | Admin email created at install. |
ADMINPASS | no | (generated) | Admin password. Leave empty to auto-generate on first run (printed once); set it to pin your own. |
You can also pass a full DATABASE_URL directly instead of the parts. Only the first run reads the admin variables.
Hardening notes
- No default credentials. The admin password is generated at first run or supplied by you, never baked into an image layer.
- Unique, persistent APP_SECRET. Generated on first run and kept in
var/data, never the public default. - Digest-pinned base. Each build is auditable and reproducible.
- Healthcheck gates traffic. The app must respond before the platform routes requests.
- Keep MariaDB private. Do not publish its port to the host or the internet.
- After first login, create per-user accounts, keep Kimai updated, and enable two-factor authentication.
Backups
Capture the database and the data volume together:
- The MariaDB database:
docker compose exec db mariadb-dump -u root -p"$DB_ROOT_PASSWORD" kimai > kimai-db.sql - The data volume at
var/data(APP_SECRETand uploaded data):docker run --rm -v kimai_kimai-data:/data -v "$PWD":/backup alpine \ tar czf /backup/kimai-data.tgz -C /data .
Keep the APP_SECRET (in var/data) with the database backup. On the RemarkableCloud App Platform, database and volume backups follow the platform’s backup schedule.
Upgrades
- New builds ship as new
-rtags. Because the application lives in the image and onlyvar/datais persisted, pulling a new tag updates Kimai; migrations run on start:docker compose pull kimai && docker compose up -d kimai - Back up first, and keep the database dump and the
var/dataarchive from just before the upgrade. - Read the changelog. Every build is listed in
CHANGELOG.mdwith the base image digest and any behavior changes.
FAQ
Questions we get.
Do I need a database?
Yes. Kimai stores timesheets and configuration in MariaDB (or MySQL). The standalone compose file includes MariaDB.
Where does the admin password come from?
If you set `ADMINPASS`, that value is used. If you leave it empty, the image generates a strong one on first run and prints it once to the container log.
Why does the container listen on 8001?
That is Kimai's default Apache port. Map it to any host port locally; in production the reverse proxy connects to `8001` on the internal network.
How does HTTPS work if the container only serves HTTP?
TLS is terminated at the reverse proxy (Traefik on the App Platform). With `TRUSTED_PROXIES` set, Kimai honors `X-Forwarded-Proto` and generates https URLs.
Can I use MySQL instead of MariaDB?
Yes. Kimai connects with the MySQL driver, which works with both MySQL 8 and MariaDB; point `KIMAI_DB_HOST` at your server.
Your server runs. You sleep.
Fully managed hosting from people who have been doing this since 2001.