Docker image
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.
# Kimai in Docker, base pinned by digest docker pull ghcr.io/remarkablecloud/kimai:2.66.0-r1 docker compose up -d
- 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
The image
What our image adds.
What changes from upstream Kimai, and why it matters in production.
Digest-pinned base
Built from kimai/kimai2@sha256:d6747832bafc… (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.
The guide
Run it in production.
Compose file, environment reference, hardening, backups and upgrades, written by the people who build the image.
On this page
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.
Changelog
Each build is pinned by digest and recorded here, so you can see what changed before you pull a new tag.
-
2.66.0-r1Current Sep 11, 2026
Kimai and other product names are trademarks of their respective owners. RemarkableCloud is not affiliated with or endorsed by them.
FAQ
Kimai in Docker, answered.
Short answers from the people who build and run this image.
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.
Pull it, or let us run it.
The same images our managed apps use, free on GHCR.