Pourquoi self-hoster Kimai ?
Un outil de suivi du temps concentre des données sensibles : qui travaille pour quel client, combien d'heures, à quel taux. Sur un service SaaS, ces informations vivent chez l'éditeur et le coût augmente avec le nombre de comptes. En hébergeant Kimai sur votre propre VPS, vous gardez la base de données sous la main, vous ajoutez des utilisateurs sans frais de licence et vous choisissez vous-même le rythme des mises à jour.
- Licence MIT — aucun frais de licence, aucune limite d'utilisateurs imposée par l'éditeur.
- Facturation intégrée — les factures se génèrent directement depuis les feuilles de temps, sans module externe.
- API REST documentée — branchez vos outils internes (tickets, RH, tableaux de bord) sur vos propres données.
- Souveraineté des données — heures, taux et fiches clients restent dans votre base MySQL.
- Pile légère — un conteneur applicatif et une base MySQL, à l'aise sur un VPS d'entrée de gamme.
Architecture : ce qui tourne sur le VPS
Kimai est une application PHP construite sur le framework Symfony. La version 2 s'appuie sur MySQL ou MariaDB ; SQLite n'est plus pris en charge. L'image officielle kimai/kimai2 existe en deux variantes : apache (serveur web intégré, écoute sur le port 8001) et fpm (à placer derrière votre propre Nginx). Le template ServOrbit utilise kimai/kimai2:apache-latest avec MySQL 8.0, ce qui ramène la pile à deux conteneurs.
Les briques de la pile
- Conteneur
kimai— Apache et PHP, l'application Symfony dans/opt/kimai, port 8001 publié uniquement sur127.0.0.1. - Conteneur
db— MySQL 8.0 avec un healthcheckmysqladmin ping: Kimai ne démarre qu'une fois la base prête. - Volume
kimai_db— les fichiers de données MySQL, c'est-à-dire toutes vos feuilles de temps. - Volume
kimai_data—/opt/kimai/var/data, les fichiers déposés dans l'application (modèles de facture, documents). - Volume
kimai_public—/opt/kimai/public/avatars, les avatars des utilisateurs. - Reverse proxy — Nginx, Caddy ou Traefik termine le HTTPS et transmet les requêtes au port local 8001.
Déployer Kimai sur ServOrbit
Le Marketplace ServOrbit provisionne la pile complète sur Ubuntu 24.04 : Docker, MySQL 8.0, le conteneur Kimai, les trois volumes et un mot de passe administrateur généré. Aucun nom de domaine n'est nécessaire pour commencer.
Lancer le déploiement
Dans votre espace client : Marketplace → ERP & Gestion → Kimai → Déployer. ServOrbit crée le VPS, installe Docker et lance la pile (MySQL 8 + Apache/PHP) automatiquement.
Récupérer les identifiants
Attendez environ 60 secondes. La carte de l'app affiche vos identifiants : login
[email protected], mot de passe généré.Se connecter sans domaine
Ouvrez un tunnel SSH :
ssh -L 8080:127.0.0.1:8001 root@<ip>puishttp://localhost:8080. Ajoutez un domaine depuis le tableau de bord quand vous êtes prêt.Créer client et projet
Créez votre premier client, puis un projet lié à ce client, et au moins une activité. Fixez un taux horaire ou un budget.
Saisir les premières heures
Cliquez ▶ pour lancer le chronomètre, choisissez le projet et l'activité : Kimai enregistre vos heures en temps réel.
Dès la première connexion, ouvrez votre profil pour remplacer l'adresse [email protected] par une adresse réelle et changer le mot de passe généré. Conservez ensuite ce compte super-administrateur pour l'administration et créez des comptes nominatifs pour le travail quotidien.
Installation manuelle avec Docker Compose
Si vous préférez installer Kimai vous-même sur un VPS existant, voici un docker-compose.yml équivalent au template, avec un volume de plus pour les plugins. Placez-le dans un dossier dédié, par exemple /opt/kimai.
services:
db:
image: mysql:8.0
restart: unless-stopped
environment:
MYSQL_DATABASE: kimai
MYSQL_USER: kimai
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- kimai_db:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "--silent"]
interval: 10s
retries: 12
kimai:
image: kimai/kimai2:apache-latest
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "127.0.0.1:8001:8001"
environment:
DATABASE_URL: "mysql://kimai:${DB_PASSWORD}@db/kimai?charset=utf8mb4&serverVersion=8.0.34"
APP_SECRET: ${APP_SECRET}
ADMINMAIL: [email protected]
ADMINPASS: ${ADMIN_PASSWORD}
TRUSTED_HOSTS: "localhost,127.0.0.1,kimai.votre-domaine.com"
volumes:
- kimai_data:/opt/kimai/var/data
- kimai_plugins:/opt/kimai/var/plugins
- kimai_public:/opt/kimai/public/avatars
volumes:
kimai_db:
kimai_data:
kimai_plugins:
kimai_public:Générez ensuite les secrets dans un fichier .env lu automatiquement par Compose, puis démarrez la pile. Au premier lancement, le conteneur crée le schéma de base et le compte administrateur défini par ADMINMAIL et ADMINPASS.
cat > .env <<EOF
DB_PASSWORD=$(openssl rand -hex 16)
DB_ROOT_PASSWORD=$(openssl rand -hex 16)
APP_SECRET=$(openssl rand -hex 32)
ADMIN_PASSWORD=$(openssl rand -hex 12)
EOF
chmod 600 .env
docker compose up -d
docker compose logs -f kimaiVariables d'environnement à connaître
DATABASE_URL— chaîne de connexion Doctrine : utilisateur, mot de passe, hôtedb, nom de base,charset=utf8mb4etserverVersion. Le mot de passe étant dans une URL, évitez les caractères@,/ou:; un secret hexadécimal règle la question.APP_SECRET— clé Symfony utilisée pour signer sessions et jetons. Une valeur aléatoire longue, générée une fois et jamais modifiée sans raison : la changer déconnecte tout le monde.ADMINMAILetADMINPASS— compte super-administrateur créé au premier démarrage. Une fois l'instance en place, gérez les comptes depuis l'interface ou la console.TRUSTED_HOSTS— liste des noms d'hôte acceptés, séparés par des virgules. Elle doit contenir votre domaine public, sinon Symfony refuse les requêtes.MAILER_URL— transport d'e-mail. Le template ServOrbit le fixe ànull://localhost: aucun mail n'est envoyé. Renseignez un serveur SMTP pour activer la réinitialisation de mot de passe et les notifications.
Mettre Kimai derrière un domaine en HTTPS
Pointer le DNS
Créez un enregistrement A
kimai.votre-domaine.comvers l'IP publique du VPS et vérifiez la propagation avecdig +short kimai.votre-domaine.com.Configurer le reverse proxy
Le port 8001 n'écoute que sur
127.0.0.1: seul le proxy local peut l'atteindre. Exemple de bloc Nginx :server { listen 80; server_name kimai.votre-domaine.com; location / { proxy_pass http://127.0.0.1:8001; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }Obtenir le certificat
Avec certbot et son plugin Nginx, le certificat Let's Encrypt est émis et le bloc
serverest complété pour le port 443 :apt install -y certbot python3-certbot-nginx certbot --nginx -d kimai.votre-domaine.comDéclarer le domaine à Kimai
Ajoutez
kimai.votre-domaine.comàTRUSTED_HOSTS, puisdocker compose up -dpour recréer le conteneur. Si les liens générés restent enhttp://, consultez la documentation Kimai sur les reverse proxies : les en-têtesX-Forwarded-*ne sont pris en compte que depuis un proxy déclaré de confiance.Fermer les accès inutiles
Dans le pare-feu, n'ouvrez que 22, 80 et 443. MySQL n'a aucun port publié et ne doit pas en avoir.
Créer des utilisateurs en ligne de commande
L'interface suffit pour gérer les comptes au quotidien, mais la console Symfony de Kimai est précieuse pour automatiser un onboarding ou récupérer un accès. La commande kimai:user:create prend un identifiant, une adresse e-mail et un rôle, puis demande le mot de passe :
docker compose exec kimai /opt/kimai/bin/console kimai:user:create alice [email protected] ROLE_TEAMLEADLa même console expose kimai:reload pour reconstruire le cache après un changement de configuration, et kimai:update pour appliquer les migrations de base lors d'une installation hors Docker. Tapez bin/console list kimai dans le conteneur pour voir les commandes disponibles sur votre version.
Le modèle de données : clients, projets, activités
Kimai organise le temps sur trois niveaux. Un client porte la devise, le fuseau horaire et les coordonnées de facturation. Un projet appartient à un client et peut avoir des dates de début et de fin, un numéro de commande et un budget. Une activité décrit la nature du travail (développement, réunion, support) ; elle peut être rattachée à un projet ou être globale, donc utilisable partout. Chaque entrée de feuille de temps relie un utilisateur, un projet, une activité, une plage horaire et, si besoin, une description et des étiquettes. Bien concevoir cette hiérarchie au départ évite de longues corrections : un client par entité facturée, un projet par mission ou contrat, des activités globales pour les tâches transverses.
Taux et budgets
- Taux horaire — définissable sur le client, le projet, l'activité ou l'utilisateur ; le niveau le plus précis s'applique à l'entrée.
- Taux fixe — un montant forfaitaire par entrée, utile pour une intervention facturée au forfait quelle que soit sa durée.
- Taux interne — le coût réel d'une heure pour vous, distinct du prix facturé, pour mesurer la rentabilité d'un projet.
- Budget en temps ou en argent — sur un client, un projet ou une activité, avec un suivi visuel de la consommation.
- Budget mensuel ou global — un forfait de maintenance mensuel se suit différemment d'un projet au forfait sur toute sa durée.
- Dépassement — les tableaux de bord signalent la consommation du budget, et un réglage de configuration permet d'autoriser ou de refuser la saisie au-delà.
Rôles, équipes et permissions
Kimai distingue des rôles hiérarchiques, et l'écran Rôles et permissions permet d'ajuster finement ce que chacun voit et modifie. Les équipes complètent ce dispositif : un client, un projet ou une activité réservé à une équipe n'apparaît qu'à ses membres, et le chef d'équipe voit et valide les heures de son équipe sans accéder au reste de l'entreprise. Pour un intervenant externe, créez un compte utilisateur limité à une équipe dédiée.
ROLE_USER— saisit et consulte ses propres heures sur les projets qui lui sont accessibles.ROLE_TEAMLEAD— voit et modifie les heures des membres de ses équipes, consulte leurs rapports.ROLE_ADMIN— gère clients, projets, activités, factures et utilisateurs.ROLE_SUPER_ADMIN— accès complet, y compris la configuration système et les permissions.
Facturer à partir des feuilles de temps
Le module de facturation transforme une sélection d'heures en document. Depuis le menu Factures, vous choisissez un client, une période, éventuellement un projet, puis un modèle. Kimai calcule les montants à partir des taux, affiche un aperçu et enregistre la facture avec un statut (en attente, payée, annulée). Les entrées facturées peuvent être marquées comme exportées, ce qui évite de les facturer deux fois et les protège contre une modification ultérieure.
Préparer votre premier modèle de facture
Créer le modèle
Dans Factures → Modèles, renseignez l'émetteur, l'adresse, les coordonnées bancaires, le délai de paiement et le texte de pied de page.
Choisir le format de rendu
Sélectionnez un rendu PDF ou HTML pour un document prêt à envoyer, ou un format bureautique si vous retravaillez la facture ailleurs.
Définir la numérotation
Configurez le format du numéro de facture (préfixe, année, compteur) pour obtenir une séquence continue conforme à vos habitudes comptables.
Paramétrer la taxe
Indiquez le taux de taxe applicable dans le modèle ; créez un modèle par régime si vous facturez des clients soumis à des taux différents.
TVA et mentions obligatoires
Kimai calcule une taxe à partir du taux saisi dans le modèle, mais il ne connaît pas votre situation fiscale. Les mentions légales, le taux applicable et la conservation des factures dépendent de votre pays et de votre statut : faites valider votre modèle par votre comptable. Si vous tenez une comptabilité complète dans un ERP comme Dolibarr, Kimai peut rester l'outil de saisie du temps et l'export servir de base à la facture finale.
API REST et intégrations
Kimai expose une API REST couvrant clients, projets, activités, feuilles de temps et utilisateurs. Sa documentation interactive est servie par votre propre instance à l'adresse /api/doc, à jour avec la version installée. Pour authentifier un script, créez un jeton d'API depuis votre profil utilisateur, puis passez-le en en-tête :
curl -s -H "Authorization: Bearer VOTRE_JETON" \
https://kimai.votre-domaine.com/api/timesheetsUn jeton hérite des permissions de son utilisateur : créez un compte de service avec le rôle minimal nécessaire plutôt que d'utiliser le super-administrateur. L'API permet par exemple de démarrer un chronomètre depuis un raccourci, de remonter les heures dans un tableau de bord ou de synchroniser les projets avec votre outil de tickets.
Étendre Kimai avec des plugins
Kimai dispose d'une boutique de plugins sur son site officiel, avec des extensions gratuites et payantes : notes de frais, champs personnalisés, circuits de validation des feuilles de temps, imports depuis d'autres outils. Un plugin est un dossier placé dans var/plugins/. Dans Docker, montez ce dossier sur un volume, sinon il disparaît à la recréation du conteneur — c'est le rôle de kimai_plugins dans le compose ci-dessus. Installation type :
docker compose cp NomDuPluginBundle kimai:/opt/kimai/var/plugins/
docker compose exec kimai /opt/kimai/bin/console kimai:reload --env=prodCertains plugins ajoutent des tables et fournissent une commande d'installation dédiée ; suivez la fiche du plugin et vérifiez sa compatibilité avec votre version de Kimai avant chaque mise à jour.
Sauvegarde et restauration
L'essentiel tient dans la base MySQL ; les volumes kimai_data et kimai_public complètent la sauvegarde. Un dump logique est plus portable qu'une copie à chaud des fichiers MySQL :
docker compose exec -T db sh -c 'exec mysqldump --single-transaction -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"' > kimai-$(date +%F).sql
docker run --rm -v kimai_kimai_data:/data -v "$PWD":/backup alpine tar czf /backup/kimai_data-$(date +%F).tgz -C /data .Le nom réel du volume est préfixé par le nom du projet Compose : vérifiez-le avec docker volume ls. Pour restaurer, démarrez une pile vide avec le même .env, puis réinjectez le dump :
docker compose exec -T db sh -c 'exec mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"' < kimai-AAAA-MM-JJ.sqlPlanifiez le dump chaque nuit via cron, copiez-le hors du VPS (Restic, BorgBackup ou stockage objet) et testez une restauration au moins une fois : une sauvegarde jamais restaurée n'est qu'une hypothèse.
Mises à jour sans surprise
- Sauvegarder d'abord — un dump juste avant la mise à jour permet de revenir en arrière si une migration échoue.
- Mettre à jour l'image —
docker compose pull kimai && docker compose up -d kimai, puis suivezdocker compose logs -f kimai. Au démarrage, le conteneur applique les migrations de base automatiquement. - Épingler une version — en production, remplacez
apache-latestpar une étiquette versionnée de l'image pour décider du moment de la montée de version. - Lire les notes de version — certaines versions majeures changent des prérequis ou des comportements de plugins.
- Exporter régulièrement — l'export CSV ou XLSX des feuilles de temps reste une copie lisible, utile pour un audit ou une migration.
Dépannage courant
La plupart des incidents Kimai en Docker se rattachent à quelques causes connues. Commencez toujours par docker compose logs kimai et docker compose ps.
- « Untrusted Host » ou erreur 400 après l'ajout d'un domaine — le nom d'hôte manque dans
TRUSTED_HOSTS. Ajoutez-le puis recréez le conteneur. - Erreur 500 et « Permission denied » sur
var/— souvent après une commande console lancée avec un autre utilisateur que celui du serveur web. Rétablissez la propriété avecdocker compose exec kimai chown -R www-data:www-data /opt/kimai/var, puiskimai:reload. - Changement de configuration sans effet — le cache Symfony sert l'ancienne version :
docker compose exec kimai /opt/kimai/bin/console kimai:reload --env=prod. - Kimai redémarre en boucle au premier lancement — la base n'est pas prête ou
DATABASE_URLest erroné. Vérifiez le healthcheck dedbet les identifiants du.env. - Liens en
http://derrière le proxy HTTPS — les en-têtesX-Forwarded-Protone sont pas transmis ou pas pris en compte ; revoyez la configuration du proxy. - Mot de passe oublié sans e-mail configuré —
MAILER_URLpointe versnull://: réinitialisez le mot de passe depuis un compte administrateur ou créez un nouvel administrateur en console.
Kimai face à Toggl Track, Clockify et Harvest
Faites défiler le tableau
| Critère | Kimai (self-hosted) | Toggl Track | Clockify | Harvest |
|---|---|---|---|---|
| Licence | Open source (MIT) | Propriétaire | Propriétaire | Propriétaire |
| Hébergement | Votre VPS | SaaS de l'éditeur | SaaS de l'éditeur | SaaS de l'éditeur |
| Emplacement des données | Votre base MySQL | Infrastructure de l'éditeur | Infrastructure de l'éditeur | Infrastructure de l'éditeur |
| Modèle de coût | Serveur uniquement, sans frais par utilisateur | Palier gratuit limité, puis abonnement | Palier gratuit, puis abonnement | Palier gratuit limité, puis abonnement |
| Facturation | Module intégré (modèles, numérotation, taxe) | Selon l'offre souscrite | Selon l'offre souscrite | Intégrée au produit |
| API | REST, jetons, documentation embarquée | REST | REST | REST |
| Personnalisation | Code source, plugins, configuration | Paramètres et intégrations | Paramètres et intégrations | Paramètres et intégrations |
Dimensionner le VPS
Kimai est sobre : la charge vient surtout de MySQL et de la génération de rapports sur de longues périodes. Pour un freelance ou une petite équipe, 1 vCPU et 1 à 2 Go de RAM suffisent. Au-delà d'une vingtaine d'utilisateurs actifs, ou si vous produisez régulièrement des rapports annuels et des exports volumineux, prévoyez 2 vCPU et 4 Go, ce qui laisse de la marge au cache InnoDB de MySQL. L'espace disque reste modeste : quelques gigaoctets couvrent des années de feuilles de temps, mais gardez de la place pour les dumps locaux avant leur envoi hors serveur.
Avant d'inviter l'équipe, fixez les règles du jeu dans Paramètres : arrondi des durées, saisie dans le futur autorisée ou non, verrouillage des périodes déjà facturées. Ces choix sont plus simples à poser sur une instance vide que sur des mois d'historique.