Installation on-premise¶
Prérequis¶
Configuration requise du serveur :
- Serveur Linux : Ubuntu 26.04 ou 24.04 LTS, Debian 12 ou 13, RHEL 9 ou 10, ou tout système avec Docker Engine 24.0+ et le plugin Docker Compose
- Docker Engine 24.0+
- Plugin Docker Compose 2.20 ou plus récent (les versions actuelles sont en 5.x)
- Git
- 4 Go de RAM au minimum, 8 Go recommandés quand PostgreSQL tourne sur le même serveur. Davantage de processus API demandent davantage de mémoire, voir Configuration.
- 10 Go de disque au minimum. Kadratis lui-même occupe environ 1,5 Go (ses images), 2 Go avec l'image des outils de maintenance. La base de données et les fichiers stockés grossissent avec le temps.
- Un accès HTTPS sortant vers
ghcr.ioà l'installation et à chaque mise à jour, pour télécharger les images (voir Règles de pare-feu).
Infrastructure fournie par le client :
| Composant | Exigence |
|---|---|
| PostgreSQL | Version 16+ avec les extensions citext, pgcrypto, uuid-ossp, et un rôle applicatif dédié pour DATABASE_URL |
| Stockage S3 | Tout stockage compatible S3 avec un bucket : AWS S3, Cloudflare R2, Hetzner Object Storage, Garage, RustFS, un MinIO existant, et d'autres. Kadratis a besoin de PutObject, GetObject, HeadObject, DeleteObject, ListObjectsV2 et du GET présigné sur ce bucket. |
| Reverse proxy | Terminaison TLS et routage (nginx, Traefik, Caddy, etc.) |
| Nom et certificat | Un nom que les utilisateurs et le serveur résolvent, et un certificat pour ce nom (voir ci-dessous) |
Un MinIO déjà en service continue de fonctionner avec Kadratis : rien à changer. MinIO ne publie plus de nouveaux téléchargements ni de nouvelles images, c'est pourquoi l'exemple d'installation utilise RustFS.
Facultatif :
- Configuration de l'e-mail sortant : clé d'API Resend ou paramètres d'un relais ou serveur SMTP
- SSO Microsoft Entra (voir SSO Microsoft Entra)
- Un agent IA de programmation capable de mener l'installation pour vous (voir Installation assistée par IA)
Nom et certificat¶
Les utilisateurs ouvrent Kadratis à une adresse HTTPS, par exemple https://kadratis.company.com. Cette adresse a besoin d'un nom qui pointe vers votre serveur et d'un certificat qui lui correspond. Trois cas couvrent la plupart des réseaux :
| Cas | Nom | Certificat |
|---|---|---|
| Nom public | Un enregistrement dans le DNS public qui pointe vers le serveur (ou vers le pare-feu placé devant lui) | Délivré par une autorité publique, par exemple Let's Encrypt avec certbot. Le port 80 doit être joignable depuis internet. |
| Nom interne | Un enregistrement dans le DNS de votre entreprise. Pour un test, une ligne dans /etc/hosts sur le serveur et sur chaque poste client. |
Délivré par l'autorité de certification interne de votre entreprise. Les navigateurs des postes gérés lui font déjà confiance : les utilisateurs ne voient aucun avertissement. |
| Auto-signé | Comme pour le nom interne | Créé sur le serveur. Pour les tests uniquement : chaque navigateur affiche un avertissement que chaque utilisateur doit accepter. |
Beaucoup d'installations n'ont pas de DNS public. Un nom interne avec un certificat interne est une configuration normale et prise en charge. Le certificat du reverse proxy sert les navigateurs. L'API ouvre aussi ses propres connexions, vers SMTP, PostgreSQL ou S3 : si votre autorité a signé les certificats de ces serveurs, voir Certificats d'une autorité interne.
Le serveur doit lui aussi résoudre le nom. Les commandes de vérification de ce guide, ainsi que le test de recette, s'exécutent sur le serveur et appellent Kadratis par son nom. Tant qu'aucun enregistrement DNS n'existe, ajoutez le nom au fichier hosts du serveur :
Remplacez kadratis.company.com par votre nom. Les postes clients ont besoin de leur propre entrée (ou de l'enregistrement DNS) qui pointe vers l'adresse du serveur.
Certificat auto-signé (tests uniquement) :
sudo install -d /etc/ssl/kanap
sudo openssl req -x509 -nodes -days 365 \
-newkey rsa:2048 \
-keyout /etc/ssl/kanap/server.key \
-out /etc/ssl/kanap/server.crt \
-subj "/CN=kadratis.company.com" \
-addext "subjectAltName=DNS:kadratis.company.com"
sudo chmod 600 /etc/ssl/kanap/server.key
Remplacez kadratis.company.com par votre nom. Pour un accès par adresse IP, utilisez IP:192.0.2.10 dans subjectAltName et mettez l'IP dans -subj et dans server_name. Pour utiliser votre autorité interne, demandez un certificat pour le même nom et faites pointer ssl_certificate et ssl_certificate_key du fichier nginx vers les fichiers qu'elle vous remet (la chaîne complète et la clé privée). Rien d'autre ne change.
Let's Encrypt : sudo apt-get install -y certbot, puis sudo certbot certonly --webroot -w /var/www/html -d kadratis.company.com, avec la page nginx par défaut qui répond sur le port 80. Les fichiers du certificat sont /etc/letsencrypt/live/kadratis.company.com/fullchain.pem et privkey.pem. L'exemple d'installation montre la séquence complète, renouvellement compris.
Démarrage rapide¶
# 1. Récupérer Kadratis. La branche "stable" pointe toujours vers la dernière version publiée.
sudo install -d -o "$USER" -g "$USER" /opt/kanap
git clone https://github.com/kanap-hq/KANAP.git /opt/kanap
cd /opt/kanap
git checkout stable
# 2. Configurer avant le premier démarrage
cp infra/.env.onprem.example .env
chmod 600 .env
nano .env # Renseignez DATABASE_URL, les identifiants S3, ADMIN_EMAIL, ADMIN_PASSWORD, JWT_SECRET,
# DEFAULT_TENANT_NAME (le nom de votre organisation, lu au premier démarrage uniquement),
# APP_BASE_URL et CORS_ORIGINS (l'adresse exacte ouverte par les utilisateurs),
# APP_ENV=production (les utilisateurs accèdent à Kadratis en HTTPS) et RATE_LIMIT_TRUST_PROXY=true
# Voir le guide de configuration pour toutes les variables
# 3. Télécharger les images de cette version
docker compose -f infra/compose.onprem.yml pull
# 4. Démarrer les conteneurs
docker compose -f infra/compose.onprem.yml up -d
# 5. Vérifier le démarrage
docker compose -f infra/compose.onprem.yml logs -f api
# Attendez "[entrypoint] Migrations complete", puis "Nest application successfully started"
# Le premier démarrage crée automatiquement le tenant, l'utilisateur administrateur et l'abonnement
# Appuyez sur Ctrl+C pour cesser de suivre le journal
# 6. Configurez votre reverse proxy pour router le trafic vers :
# - /api/* → 127.0.0.1:8080 (le conteneur api)
# - /.well-known/oauth-* → 127.0.0.1:8080, chemin conservé (connexions MCP sans clé)
# - /* → 127.0.0.1:8081 (le conteneur web, port 80 dans le conteneur)
# Assurez-vous que le proxy conserve Host et renseigne X-Forwarded-Proto et X-Forwarded-For.
# Après le premier démarrage, lisez les lignes [ENV], [CONFIG], [CORS], [RATE-LIMIT] et [SECURITY] du journal de l'API.
# 7. Accéder à l'application
# https://kadratis.company.com
# Connexion avec ADMIN_EMAIL / ADMIN_PASSWORD du fichier .env
Important : terminez la configuration (étape 2) avant de démarrer les conteneurs. L'API lit .env au démarrage et crée le tenant et l'utilisateur administrateur au premier démarrage avec ces valeurs. Le fichier contient tous les secrets de l'installation : chmod 600 le rend lisible par son seul propriétaire.
Versions. Kadratis publie une nouvelle version environ une fois par mois (26.10.1 est la première). La branche stable pointe toujours vers la dernière version publiée. Chaque version est livrée avec ses images sur GitHub Container Registry (ghcr.io/kanap-hq/kadratis-api, kadratis-web et kadratis-api-tools), construites à partir du tag de la version. Elles sont publiques : aucun compte n'est nécessaire. À partir de cette version, les images s'appellent kadratis-*. La mise à jour habituelle les télécharge : rien d'autre à faire. Les versions antérieures gardent leurs images kanap-*. infra/compose.onprem.yml nomme les images de la version que vous avez extraite : git checkout stable ou git checkout v26.10.9 décide donc de la version que pull télécharge. Les versions jusqu'à la 26.10.7 n'ont pas d'images publiées : elles se construisent sur le serveur, comme l'explique le guide de cette version. Voir Opérations pour mettre à jour, fixer une version précise et revenir en arrière. La branche main contient chaque modification fusionnée avant sa publication dans une version. Il est possible de la suivre, ce qui n'est pas recommandé pour un serveur de production : il faut alors construire les images vous-même.
Exigence sur le rôle de base de données : DATABASE_URL doit utiliser un rôle applicatif PostgreSQL dédié. Ne le faites pas pointer vers postgres ou un autre rôle d'administration du cluster. Kadratis refuse de démarrer plutôt que de fonctionner sans application effective du RLS.
Adresse et origines : renseignez APP_BASE_URL et CORS_ORIGINS avec l'adresse exacte ouverte par les utilisateurs, avec le port lorsqu'il n'est pas standard. Chaque lien envoyé par Kadratis provient de APP_BASE_URL. Renseignez APP_ENV=production lorsque les utilisateurs accèdent à Kadratis en HTTPS : l'API refuse alors de démarrer sans ces deux valeurs et marque toujours le cookie de session comme Secure. Voir Configuration.
Choix de l'e-mail : les déploiements on-premise peuvent utiliser Resend ou SMTP pour l'e-mail sortant. SMTP est utile lorsque le client dispose déjà d'un relais de messagerie interne ou d'un fournisseur géré comme Microsoft 365. Configurez l'une de ces options si vous voulez que la réinitialisation de mot de passe, les invitations et les notifications par e-mail fonctionnent dès le premier jour.
Construire les images vous-même¶
Les images publiées conviennent à la plupart des installations. Construisez-les sur le serveur pour une fourche, un correctif local, un serveur qui n'atteint pas ghcr.io, ou une API sur un autre hôte. Le fichier infra/compose.onprem.build.yml, ajouté par-dessus le fichier principal, les construit à partir du checkout et les nomme kadratis-api:local, kadratis-web:local et kadratis-api-tools:local :
docker compose -f infra/compose.onprem.yml -f infra/compose.onprem.build.yml build
docker compose -f infra/compose.onprem.yml -f infra/compose.onprem.build.yml up -d
Nommez les deux fichiers dans chaque up et run suivant : avec le fichier principal seul, Compose démarre les images publiées. Le build a besoin du paquet docker-buildx-plugin et de 6 Go de RAM sur le serveur. Il télécharge des paquets npm et Alpine (voir Règles de pare-feu). Pour une API sur un autre hôte, passez son adresse dans le shell quand vous construisez l'image web : VITE_API_URL=https://api.company.com docker compose -f infra/compose.onprem.yml -f infra/compose.onprem.build.yml build web. Compose ne la lit pas dans .env.
Exemple de reverse proxy (nginx)¶
Exigences pour le reverse proxy :
- Terminer TLS sur le port 443
- Router
/api/*directement vers le conteneur API (port 8080 sur127.0.0.1), sans le préfixe/api. N'envoyez pas/api/à travers le conteneur web (port 8081) : sa propre route/api/ne transmet pasX-Forwarded-For, et Kadratis compterait et journaliserait alors chaque requête sous l'adresse du conteneur web. - Router toutes les autres requêtes vers le conteneur web (port 8081 sur
127.0.0.1) - Renseigner
X-Forwarded-Proto: httpset conserverHost. Kadratis construit chaque lien qu'il envoie à partir deAPP_BASE_URL. L'exemple envoie aussiX-Forwarded-Host, avec la même valeur queHost. Lorsque le site utilise un port non standard, ajoutez l'adresse exacte avec son port àCORS_ORIGINS. - Envoyer
X-Forwarded-Foravec l'adresse du client (l'exemple le fait) et renseignerRATE_LIMIT_TRUST_PROXY=true. Kadratis utilise cette adresse pour ses limites de connexion. Le port de l'API doit rester lié à127.0.0.1, comme le faitcompose.onprem.yml. Si rien n'est placé devant l'API, renseignezRATE_LIMIT_TRUST_PROXY=false. - Accepter des corps de requête de 80 Mo (
client_max_body_size 80m) : le chargement d'un fichier de budget envoie jusqu'à 72 Mo (le fichier, jusqu'à 48 Mo, et le résultat de sa vérification), et les pièces jointes vont jusqu'à 20 Mo. - Router
/.well-known/oauth-*vers l'API (port 8080), chemin conservé. Les assistants IA comme Claude le lisent pour se connecter via MCP sans clé. Les autres adresses/.well-known/restent sur le conteneur web. Voir Connecter des assistants IA via MCP.
Comme les conteneurs sont liés à 127.0.0.1, nginx s'exécute sur le même hôte et relaie vers localhost.
Le fichier est écrit pour nginx 1.25.1 et plus récent (Ubuntu 26.04 fournit la 1.28). Ubuntu 24.04 fournit nginx 1.24 : sur ce système, écrivez listen 443 ssl http2; et listen [::]:443 ssl http2; et supprimez la ligne http2 on;.
server {
# HTTP/2 : le navigateur envoie les dizaines de requêtes d'une page sur une seule connexion.
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name kadratis.company.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
# Limite d'envoi : chargements de fichiers de budget jusqu'à 72 Mo, pièces jointes jusqu'à 20 Mo
client_max_body_size 80m;
# Normaliser /api → /api/
location = /api { return 301 /api/; }
# API : retirer le préfixe /api avant de relayer
location ^~ /api/ {
proxy_pass http://127.0.0.1:8080/;
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;
proxy_set_header X-Forwarded-Host $host;
# Compresser les réponses JSON et CSV de l'API (une page de la liste des budgets devient environ 8 fois plus petite).
# Les réponses d'IA en flux (application/x-ndjson) sont exclues volontairement.
gzip on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_vary on;
gzip_types application/json text/csv text/plain;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Requêtes longues (exports, imports)
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_redirect off;
}
# MCP connections without a key: discovery served by the API, path kept
# (no trailing slash on proxy_pass). Other /.well-known addresses stay below.
location ^~ /.well-known/oauth- {
proxy_pass http://127.0.0.1:8080;
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;
proxy_http_version 1.1;
proxy_redirect off;
}
# Tout le reste → SPA
location / {
proxy_pass http://127.0.0.1:8081;
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;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
}
}
server {
listen 80;
listen [::]:80;
server_name kadratis.company.com;
# Renouvellement du certificat avec Let's Encrypt (webroot) ; sans effet sinon
location /.well-known/acme-challenge/ { root /var/www/html; }
location / { return 301 https://$host$request_uri; }
}
Compression et HTTP/2 : l'exemple compresse les réponses de l'API et active HTTP/2. Gardez les deux dans votre propre proxy : une page de la liste des budgets pèse environ 390 Ko de JSON non compressé et 47 Ko compressé. Si votre nginx dispose du module brotli (libnginx-mod-http-brotli-filter sur Debian et Ubuntu), brotli on; brotli_types application/json text/csv text/plain; dans le même bloc location compresse un peu mieux ; gzip suffit.
host.docker.internal : lorsque PostgreSQL ou le stockage S3 s'exécute sur l'hôte Docker (hors conteneur), utilisez host.docker.internal comme nom d'hôte dans DATABASE_URL et S3_ENDPOINT. Le fichier compose.onprem.yml contient le mappage extra_hosts qui rend cela possible. Il pointe vers l'adresse du bridge Docker du serveur (172.17.0.1 par défaut) : les services de l'hôte doivent donc accepter les connexions depuis ce réseau (voir l'exemple d'installation).
Santé. L'API répond à GET /health sur son propre port (http://127.0.0.1:8080/health) et à GET /api/health à travers le proxy. Les deux renvoient {"status":"ok","text_processing":"ready"}. La section Supervision explique les autres valeurs de text_processing. docker compose -f infra/compose.onprem.yml ps affiche healthy pour les conteneurs api et web dès qu'ils répondent.
Architecture réseau¶
┌─────────────────────────────────────────────────────┐
│ Customer Infrastructure │
│ │
Network │ ┌──────────────┐ ┌─────────────────────────┐ │
│ │ │ Your Reverse │ │ Docker Host │ │
│ │ │ Proxy │ │ │ │
┌────▼────┐ │ │ (TLS) │ │ ┌─────┐ ┌─────┐ │ │
│ Browser │──────┼─▶│ :443 │───▶│ │ api │ │ web │ │ │
└─────────┘ │ └──────────────┘ │ │:8080│ │:8081│ │ │
│ │ └─────┘ └─────┘ │ │
│ ┌──────────────┐ └─────────────────────────┘ │
│ │ PostgreSQL │ │
│ │ (yours) │◀──────── DATABASE_URL │
│ └──────────────┘ │
│ ┌──────────────┐ │
│ │ S3 Storage │◀──────── S3_ENDPOINT │
│ │ (yours) │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────┘
Modèle de déploiement : un conteneur API et un conteneur web. L'exécution de plusieurs conteneurs API ou web n'est pas prise en charge. Pour davantage d'utilisateurs simultanés, exécutez plusieurs processus API dans le conteneur API avec API_WORKERS (voir Configuration). Pour la haute disponibilité, appuyez-vous sur les politiques de redémarrage de Docker et sur la redondance de l'infrastructure (haute disponibilité de la base de données, durabilité S3).
Première connexion¶
- Ouvrez
https://<votre-nom> - Connectez-vous avec
ADMIN_EMAILetADMIN_PASSWORDdu fichier.env - Changez le mot de passe administrateur dans votre profil si vous avez démarré avec une valeur que vous ne voulez pas garder. L'avertissement
[SECURITY]du journal de l'API cesse une fois le mot de passe changé (voir ci-dessous). - Ajoutez votre logo et vos couleurs dans Administration → Personnalisation (facultatif)
- Invitez d'autres utilisateurs (si l'e-mail est configuré)
À propos du compte administrateur. Kadratis le crée au premier démarrage à partir de ADMIN_EMAIL et ADMIN_PASSWORD, et uniquement à ce moment-là. Modifier ces deux lignes par la suite ne change rien tant qu'un administrateur actif existe : changez le mot de passe dans l'application. S'il ne reste aucun administrateur actif, le démarrage suivant rétablit le compte ADMIN_EMAIL comme administrateur activé et conserve son mot de passe existant. ADMIN_PASSWORD doit être une valeur qui vous est propre, de 12 caractères ou plus : openssl rand -base64 18 en génère une. Une valeur d'exemple ou une valeur plus courte fait afficher à l'API un avertissement [SECURITY] à chaque démarrage, jusqu'à ce que le mot de passe du compte soit changé. Voir Configuration.
Facultatif : accès aux données par périmètre¶
L'accès aux données limite une personne à une partie du budget et des contrats, par société ou par centre de coûts. Sans cette étape, tout le monde travaille avec toutes les données, comme dans les versions précédentes, et Limité à reste grisé dans la fenêtre de l'utilisateur. Passez-la si personne ne doit être limité.
L'étape crée un rôle PostgreSQL, kanap_data_limited, que Kadratis prend pour les requêtes des personnes limitées. Elle se fait une fois par serveur PostgreSQL, par un superutilisateur ou un rôle doté de CREATEROLE. Elle donne au rôle applicatif le droit de prendre ce rôle sans hériter de ses droits. Kadratis pose ensuite lui-même les droits et les règles d'accès de ce rôle dans sa base, à chaque démarrage et à chaque mise à jour.
1. Lancez le script. Il se trouve dans le dépôt. Passez le rôle applicatif de DATABASE_URL dans app_role. Avec l'exemple d'installation (rôle et base kanap, PostgreSQL sur le serveur) :
cd /opt/kanap
sudo -u postgres psql -X -v ON_ERROR_STOP=1 -v app_role=kanap -v mode=install \
-f /opt/kanap/infra/scripts/data-access-role.sql -d kanap
Avec un service PostgreSQL géré, lancez la même commande psql avec le compte administrateur du service, depuis une machine qui atteint la base.
Le script se termine par un tableau data-access-role: final state. Sur PostgreSQL 16 et suivants, il affiche limited_role_exists = t, app_is_member = t, membership_inherit = false, membership_set = true et app_inherits_limited = f. Le relancer ne change rien.
Sur PostgreSQL 15, le script fait aussi cesser l'héritage des droits des autres rôles par le rôle applicatif, et il doit tourner sur chaque base qui appartient à ce rôle. L'en-tête du script l'explique et donne la boucle à utiliser.
2. Redémarrez l'API, à un moment calme : à son démarrage, elle pose les droits et les règles d'accès du rôle.
cd /opt/kanap
docker compose -f infra/compose.onprem.yml restart api
docker compose -f infra/compose.onprem.yml logs api | grep DATA-ACCESS
Le journal affiche [DATA-ACCESS] data-scoped access available. S'il liste à la place ce qui manque, lisez les lignes qui précèdent. Si l'API a attendu trop longtemps une table occupée, elle le signale et réessaie à son démarrage suivant : redémarrez-la de nouveau un peu plus tard.
Retirer la fonction. Rendez d'abord Toutes les données aux personnes limitées (Administration > Accès aux données les liste) : tant que le rôle manque, elles sont refusées. Lancez ensuite le script avec mode=uninstall, en superutilisateur, puis redémarrez l'API :
cd /opt/kanap
sudo -u postgres psql -X -v ON_ERROR_STOP=1 -v app_role=kanap -v mode=uninstall \
-f /opt/kanap/infra/scripts/data-access-role.sql -d kanap
docker compose -f infra/compose.onprem.yml restart api
Le script retire les droits et les règles d'accès du rôle dans la base, puis le rôle lui-même. Quand d'autres bases du serveur gardent encore quelque chose pour ce rôle, il les nomme : lancez-le sur chacune.
Une restauration de base peut demander de refaire l'étape : voir Restauration.