¿Por qué alojar Kimai tú mismo?
Una herramienta de control horario concentra datos sensibles: quién trabaja para qué cliente, cuántas horas y a qué tarifa. En un servicio SaaS esa información reside en los servidores del proveedor y el coste crece con cada usuario. Si alojas Kimai en tu propio VPS, la base de datos queda bajo tu control, añades usuarios sin pagar licencias y decides tú cuándo actualizar.
- Licencia MIT — sin tarifas de licencia ni límite de usuarios impuesto por el proveedor.
- Facturación integrada — las facturas se generan directamente desde los partes de horas, sin módulos externos.
- API REST documentada — conecta tus herramientas internas (tickets, RR. HH., paneles) a tus propios datos.
- Soberanía de datos — horas, tarifas y fichas de clientes se quedan en tu base MySQL.
- Stack ligero — un contenedor de aplicación y una base MySQL, holgado en un VPS de entrada.
Arquitectura: qué se ejecuta en el VPS
Kimai es una aplicación PHP construida sobre el framework Symfony. La versión 2 funciona con MySQL o MariaDB; SQLite ya no está soportado. La imagen oficial kimai/kimai2 tiene dos variantes: apache (servidor web incluido, escucha en el puerto 8001) y fpm (pensada para ir detrás de tu propio Nginx). La plantilla de ServOrbit usa kimai/kimai2:apache-latest con MySQL 8.0, de modo que el stack se reduce a dos contenedores.
Las piezas del stack
- Contenedor
kimai— Apache y PHP, la aplicación Symfony en/opt/kimai, puerto 8001 publicado solo en127.0.0.1. - Contenedor
db— MySQL 8.0 con un healthcheckmysqladmin ping: Kimai solo arranca cuando la base está lista. - Volumen
kimai_db— los ficheros de datos de MySQL, es decir, todos tus partes de horas. - Volumen
kimai_data—/opt/kimai/var/data, los ficheros subidos a la aplicación (plantillas de factura, documentos). - Volumen
kimai_public—/opt/kimai/public/avatars, los avatares de los usuarios. - Proxy inverso — Nginx, Caddy o Traefik termina el HTTPS y reenvía las peticiones al puerto local 8001.
Desplegar Kimai en ServOrbit
El Marketplace de ServOrbit aprovisiona el stack completo sobre Ubuntu 24.04: Docker, MySQL 8.0, el contenedor de Kimai, los tres volúmenes y una contraseña de administrador generada. No necesitas un dominio para empezar.
Lanzar el despliegue
En tu área de cliente: Marketplace → ERP y Gestión → Kimai → Desplegar. ServOrbit crea el VPS, instala Docker y arranca el stack (MySQL 8 + Apache/PHP) automáticamente.
Recuperar las credenciales
Espera unos 60 segundos. La tarjeta de la app muestra tus credenciales: usuario
[email protected]y contraseña generada.Conectarse sin dominio
Abre un túnel SSH:
ssh -L 8080:127.0.0.1:8001 root@<ip>y luegohttp://localhost:8080. Añade un dominio desde el panel cuando lo tengas listo.Crear cliente y proyecto
Crea tu primer cliente, luego un proyecto vinculado y al menos una actividad. Fija una tarifa horaria o un presupuesto.
Registrar las primeras horas
Haz clic en ▶ para iniciar el temporizador y elige proyecto y actividad: Kimai registra tus horas en tiempo real.
En el primer inicio de sesión, abre tu perfil para sustituir [email protected] por una dirección real y cambiar la contraseña generada. Después reserva esa cuenta de superadministrador para la administración y crea cuentas nominativas para el trabajo diario.
Instalación manual con Docker Compose
Si prefieres instalar Kimai por tu cuenta en un VPS existente, aquí tienes un docker-compose.yml equivalente a la plantilla, con un volumen adicional para los plugins. Colócalo en un directorio dedicado, por ejemplo /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.your-domain.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:A continuación genera los secretos en un fichero .env, que Compose lee automáticamente, y arranca el stack. En el primer arranque el contenedor crea el esquema de la base y la cuenta de administrador definida por ADMINMAIL y 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 de entorno que conviene conocer
DATABASE_URL— cadena de conexión de Doctrine: usuario, contraseña, hostdb, nombre de la base,charset=utf8mb4yserverVersion. Como la contraseña va dentro de una URL, evita@,/o:; un secreto hexadecimal resuelve el problema.APP_SECRET— clave de Symfony con la que se firman sesiones y tokens. Un valor aleatorio largo, generado una vez y sin tocarlo después: cambiarlo cierra la sesión de todos.ADMINMAILyADMINPASS— la cuenta de superadministrador creada en el primer arranque. Con la instancia en marcha, gestiona las cuentas desde la interfaz o la consola.TRUSTED_HOSTS— lista de nombres de host aceptados, separados por comas. Debe incluir tu dominio público; si no, Symfony rechaza las peticiones.MAILER_URL— transporte de correo. La plantilla de ServOrbit lo fija ennull://localhost, así que no se envía ningún correo. Indica un servidor SMTP para activar la recuperación de contraseña y las notificaciones.
Publicar Kimai en un dominio con HTTPS
Apuntar el DNS
Crea un registro A para
kimai.your-domain.comhacia la IP pública del VPS y comprueba la propagación condig +short kimai.your-domain.com.Configurar el proxy inverso
El puerto 8001 solo escucha en
127.0.0.1, así que únicamente el proxy local puede alcanzarlo. Ejemplo de bloque Nginx:server { listen 80; server_name kimai.your-domain.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; } }Obtener el certificado
Con certbot y su plugin de Nginx, se emite el certificado de Let's Encrypt y el bloque
serverse completa para el puerto 443:apt install -y certbot python3-certbot-nginx certbot --nginx -d kimai.your-domain.comDeclarar el dominio en Kimai
Añade
kimai.your-domain.comaTRUSTED_HOSTSy ejecutadocker compose up -dpara recrear el contenedor. Si los enlaces generados siguen enhttp://, consulta la documentación de Kimai sobre proxies inversos: las cabecerasX-Forwarded-*solo se tienen en cuenta si vienen de un proxy de confianza.Cerrar lo que sobra
En el cortafuegos abre solo los puertos 22, 80 y 443. MySQL no publica ningún puerto y no debe hacerlo.
Crear usuarios desde la línea de comandos
La interfaz basta para gestionar cuentas en el día a día, pero la consola Symfony de Kimai resulta muy útil para automatizar altas o recuperar un acceso. El comando kimai:user:create recibe un nombre de usuario, un correo y un rol, y después pide la contraseña:
docker compose exec kimai /opt/kimai/bin/console kimai:user:create alice [email protected] ROLE_TEAMLEADLa misma consola ofrece kimai:reload para reconstruir la caché tras un cambio de configuración, y kimai:update para aplicar las migraciones de base de datos en instalaciones fuera de Docker. Ejecuta bin/console list kimai dentro del contenedor para ver los comandos disponibles en tu versión.
El modelo de datos: clientes, proyectos, actividades
Kimai organiza el tiempo en tres niveles. Un cliente lleva la moneda, la zona horaria y los datos de facturación. Un proyecto pertenece a un cliente y puede tener fechas de inicio y fin, un número de pedido y un presupuesto. Una actividad describe el tipo de trabajo (desarrollo, reunión, soporte); puede asociarse a un proyecto concreto o ser global y estar disponible en todos. Cada entrada del parte de horas une un usuario, un proyecto, una actividad, un intervalo de tiempo y, si hace falta, una descripción y etiquetas. Diseñar bien esta jerarquía desde el principio ahorra correcciones costosas: un cliente por entidad facturada, un proyecto por encargo o contrato y actividades globales para las tareas transversales.
Tarifas y presupuestos
- Tarifa horaria — se define en el cliente, el proyecto, la actividad o el usuario; se aplica el nivel más específico a cada entrada.
- Tarifa fija — un importe cerrado por entrada, útil para un servicio facturado a precio fijo sea cual sea su duración.
- Tarifa interna — lo que realmente te cuesta una hora, separado del precio facturado, para medir la rentabilidad de cada proyecto.
- Presupuesto en tiempo o en dinero — en un cliente, proyecto o actividad, con un indicador visual de consumo.
- Presupuesto mensual o total — una bolsa de mantenimiento mensual se controla de forma distinta a un proyecto cerrado durante toda su vida.
- Sobrepasos — los paneles muestran cuánto presupuesto se ha consumido y un ajuste de configuración permite autorizar o bloquear las entradas que lo superen.
Roles, equipos y permisos
Kimai trabaja con roles jerárquicos, y la pantalla Roles y permisos permite ajustar con detalle lo que cada rol ve y modifica. Los equipos completan el sistema: un cliente, proyecto o actividad restringido a un equipo solo es visible para sus miembros, y el jefe de equipo revisa y corrige las horas de su equipo sin ver el resto de la empresa. Para un colaborador externo, crea una cuenta de usuario limitada a un equipo dedicado.
ROLE_USER— registra y consulta sus propias horas en los proyectos a los que tiene acceso.ROLE_TEAMLEAD— ve y modifica las horas de los miembros de sus equipos y consulta sus informes.ROLE_ADMIN— gestiona clientes, proyectos, actividades, facturas y usuarios.ROLE_SUPER_ADMIN— acceso completo, incluida la configuración del sistema y los permisos.
Facturar a partir de los partes de horas
El módulo de facturación convierte una selección de horas en un documento. Desde el menú Facturas eliges un cliente, un periodo, opcionalmente un proyecto y después una plantilla. Kimai calcula los importes a partir de las tarifas, muestra una vista previa y guarda la factura con un estado (pendiente, pagada, cancelada). Las entradas facturadas pueden marcarse como exportadas, lo que evita facturarlas dos veces y las protege de cambios posteriores.
Preparar tu primera plantilla de factura
Crear la plantilla
En Facturas → Plantillas, indica el emisor, la dirección, los datos bancarios, el plazo de pago y el texto del pie.
Elegir el formato de salida
Selecciona un formato PDF o HTML para un documento listo para enviar, o un formato ofimático si terminas la factura en otra herramienta.
Definir la numeración
Configura el formato del número de factura (prefijo, año, contador) para obtener una secuencia continua acorde con tu contabilidad.
Configurar el impuesto
Introduce el tipo impositivo aplicable en la plantilla; crea una plantilla por régimen si facturas a clientes con tipos distintos.
IVA y menciones obligatorias
Kimai calcula el impuesto a partir del tipo indicado en la plantilla, pero no conoce tu situación fiscal. Las menciones legales, el tipo aplicable y la conservación de facturas dependen de tu país y de tu forma jurídica: pide a tu asesor que revise la plantilla. Si llevas la contabilidad completa en un ERP como Dolibarr, Kimai puede seguir siendo la herramienta de registro de horas y su exportación servir de base para la factura final.
API REST e integraciones
Kimai expone una API REST que cubre clientes, proyectos, actividades, partes de horas y usuarios. Su documentación interactiva la sirve tu propia instancia en /api/doc, siempre al día con la versión instalada. Para autenticar un script, crea un token de API desde tu perfil de usuario y envíalo en una cabecera:
curl -s -H "Authorization: Bearer TU_TOKEN" \
https://kimai.your-domain.com/api/timesheetsUn token hereda los permisos de su usuario: crea una cuenta de servicio con el rol mínimo necesario en lugar de usar el superadministrador. La API permite, por ejemplo, iniciar un temporizador desde un atajo, volcar las horas en un panel o sincronizar los proyectos con tu herramienta de tickets.
Ampliar Kimai con plugins
Kimai cuenta con una tienda de plugins en su web oficial, con extensiones gratuitas y de pago: gastos, campos personalizados, flujos de aprobación de partes de horas e importaciones desde otras herramientas. Un plugin es un directorio que se coloca en var/plugins/. Con Docker, monta ese directorio en un volumen o desaparecerá al recrear el contenedor; esa es la función de kimai_plugins en el compose anterior. Instalación típica:
docker compose cp NombreDelPluginBundle kimai:/opt/kimai/var/plugins/
docker compose exec kimai /opt/kimai/bin/console kimai:reload --env=prodAlgunos plugins añaden tablas y traen su propio comando de instalación; sigue la ficha del plugin y comprueba su compatibilidad con tu versión de Kimai antes de cada actualización.
Copia de seguridad y restauración
Casi todo está en la base MySQL; los volúmenes kimai_data y kimai_public completan la copia. Un volcado lógico es más portable que copiar en caliente los ficheros de 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 .El nombre real del volumen lleva como prefijo el nombre del proyecto Compose: compruébalo con docker volume ls. Para restaurar, arranca un stack vacío con el mismo .env y vuelve a cargar el volcado:
docker compose exec -T db sh -c 'exec mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"' < kimai-AAAA-MM-DD.sqlPrograma el volcado cada noche con cron, cópialo fuera del VPS (Restic, BorgBackup o almacenamiento de objetos) y prueba una restauración al menos una vez: una copia que nunca se ha restaurado es solo una hipótesis.
Actualizaciones sin sorpresas
- Primero, la copia — un volcado justo antes de actualizar permite volver atrás si falla una migración.
- Actualizar la imagen —
docker compose pull kimai && docker compose up -d kimai, y después siguedocker compose logs -f kimai. Al arrancar, el contenedor aplica las migraciones de base de datos automáticamente. - Fijar una versión — en producción, sustituye
apache-latestpor una etiqueta versionada de la imagen para decidir cuándo subir de versión. - Leer las notas de versión — algunas versiones mayores cambian requisitos o el comportamiento de los plugins.
- Exportar con regularidad — la exportación CSV o XLSX de los partes de horas es una copia legible, útil para una auditoría o una migración.
Resolución de problemas habituales
La mayoría de las incidencias de Kimai en Docker se deben a unas pocas causas conocidas. Empieza siempre por docker compose logs kimai y docker compose ps.
- «Untrusted Host» o error 400 tras añadir un dominio — falta el nombre de host en
TRUSTED_HOSTS. Añádelo y recrea el contenedor. - Error 500 y «Permission denied» en
var/— suele aparecer tras un comando de consola ejecutado con un usuario distinto al del servidor web. Restablece la propiedad condocker compose exec kimai chown -R www-data:www-data /opt/kimai/vary despuéskimai:reload. - Un cambio de configuración no surte efecto — la caché de Symfony sirve la versión anterior:
docker compose exec kimai /opt/kimai/bin/console kimai:reload --env=prod. - Kimai se reinicia en bucle en el primer arranque — la base no está lista o
DATABASE_URLes incorrecta. Revisa el healthcheck dedby las credenciales del.env. - Enlaces en
http://detrás del proxy HTTPS — las cabecerasX-Forwarded-Protono se envían o no se consideran de confianza; revisa la configuración del proxy. - Contraseña olvidada sin correo configurado —
MAILER_URLapunta anull://: restablece la contraseña desde una cuenta de administrador o crea un nuevo administrador por consola.
Kimai frente a Toggl Track, Clockify y Harvest
Desplace la tabla
| Criterio | Kimai (self-hosted) | Toggl Track | Clockify | Harvest |
|---|---|---|---|---|
| Licencia | Open source (MIT) | Propietaria | Propietaria | Propietaria |
| Alojamiento | Tu VPS | SaaS del proveedor | SaaS del proveedor | SaaS del proveedor |
| Ubicación de los datos | Tu base MySQL | Infraestructura del proveedor | Infraestructura del proveedor | Infraestructura del proveedor |
| Modelo de coste | Solo el servidor, sin cuota por usuario | Nivel gratuito limitado y después suscripción | Nivel gratuito y después suscripción | Nivel gratuito limitado y después suscripción |
| Facturación | Módulo integrado (plantillas, numeración, impuestos) | Según el plan | Según el plan | Integrada en el producto |
| API | REST, tokens, documentación incluida | REST | REST | REST |
| Personalización | Código fuente, plugins, configuración | Ajustes e integraciones | Ajustes e integraciones | Ajustes e integraciones |
Dimensionar el VPS
Kimai es austero: la carga procede sobre todo de MySQL y de los informes que abarcan periodos largos. Para un freelance o un equipo pequeño bastan 1 vCPU y de 1 a 2 GB de RAM. A partir de una veintena de usuarios activos, o si generas a menudo informes anuales y exportaciones voluminosas, prevé 2 vCPU y 4 GB, lo que deja margen a la caché InnoDB de MySQL. El disco necesario es modesto: unos pocos gigabytes cubren años de partes de horas, pero reserva espacio para los volcados locales antes de enviarlos fuera del servidor.
Antes de invitar al equipo, fija las reglas en Ajustes: redondeo de duraciones, si se permiten entradas en el futuro y bloqueo de los periodos ya facturados. Es mucho más sencillo decidirlo en una instancia vacía que sobre meses de historial.