SSD Nodes Learn 🎉 VPS dès $5.50/mois
Guides Matt ConnorPar Matt Connor · Mis à jour le 2026-08-10

Installer Planka avec Docker Compose sur un VPS

Déployez Planka avec Docker Compose, Postgres et Traefik. Configurez les variables admin et BASE_URL pour éviter l’erreur de connexion après login.

Ce que vous obtenez en auto-hébergeant Planka

L’auto-hébergement de Planka fournit à votre équipe un tableau Kanban avec le modèle de cartes, de listes et d’étiquettes déjà connu des utilisateurs de Trello, exécuté sur un VPS que vous contrôlez. Il n’y a aucune limite de sièges ni facturation par utilisateur, car le seul coût est celui du serveur. Ce guide le déploie avec Docker Compose derrière Traefik, en utilisant Postgres pour les données et un volume nommé pour chaque fichier téléversé par les utilisateurs.

Le guide s’adresse à une équipe de deux à cinq personnes qui quitte l’offre gratuite de Trello. Si vous hésitez encore sur le tableau à utiliser, consultez d’abord la comparaison des alternatives auto-hébergées à Trello. Ce guide part du principe que le choix est déjà fait et couvre uniquement le déploiement.

Vous avez besoin d’un VPS exécutant Docker Engine avec le plugin Compose, ainsi que d’un enregistrement DNS de type A pointant vers ce VPS. Vous avez également besoin d’une instance Traefik qui assure déjà la terminaison TLS (Transport Layer Security) sur ce serveur. Si Traefik n’est pas encore installé, configurez d’abord un reverse proxy Traefik devant plusieurs applications Compose, puis consultez les bases de Docker Compose pour un VPS si le fichier ci-dessous ne vous est pas familier.

De quelles ressources VPS Planka a-t-il besoin ?

Le projet ne publie pas de configuration matérielle minimale. Considérez donc tout chiffre trouvé comme un point de départ, et non comme une mesure. Les valeurs de 2 vCPU et 4 Go souvent reprises par les pages des hébergeurs correspondent à une configuration confortable proposée par un fournisseur, et non à une exigence mesurée par le projet. Elle est généreuse pour un tableau utilisé par cinq personnes.

L’exécution réelle est légère : un processus Node.js sert l’API et le frontend compilé, tandis qu’un processus Postgres stocke les données. Un troisième processus proxy léger s’exécute dans le conteneur Planka pour filtrer ses requêtes sortantes. Une offre avec 1 vCPU et 2 Go suffit pour un tableau utilisé par deux à cinq personnes. La majeure partie de la mémoire disponible sert alors de cache à Postgres.

Dimensionnez le disque avant la mémoire, car ce sont les pièces jointes qui augmentent le plus. Mesurez votre propre instance au lieu de vous fier à ce paragraphe :

docker stats --no-stream
docker system df -v

La première commande affiche l’utilisation actuelle de la mémoire et du CPU par conteneur. La seconde indique l’espace occupé par chaque volume. Effectuez ces deux mesures après une semaine de travail normale, et non le jour de l’installation, car un tableau inactif ne vous apprend rien sur votre équipe.

Rédiger le fichier Compose

Créez le répertoire et attribuez-vous-en la propriété, afin de ne jamais avoir à modifier ces fichiers via sudo.

sudo mkdir -p /opt/planka
sudo chown "$USER":"$USER" /opt/planka
cd /opt/planka

Générez les secrets dans un fichier .env placé à côté du fichier Compose. Compose lit automatiquement ce fichier et remplace les valeurs correspondantes.

umask 077
{
  printf 'SECRET_KEY=%s\n' "$(openssl rand -hex 64)"
  printf 'POSTGRES_PASSWORD=%s\n' "$(openssl rand -hex 24)"
  printf 'ADMIN_PASSWORD=%s\n' "$(openssl rand -hex 12)"
} > .env
chmod 600 .env

openssl rand -hex est un choix délibéré. Une chaîne hexadécimale contient uniquement des chiffres et les lettres de a à f. Elle ne peut donc pas casser la chaîne de connexion DATABASE_URL dans laquelle elle est insérée. Un mot de passe base64 contenant une barre oblique ou un arobase provoque une erreur de connexion qui ressemble à un mauvais nom d’hôte. Vous pouvez alors perdre une heure. La méthode générale est présentée dans garder les secrets hors du fichier Compose.

Lancez maintenant docker-compose.yml. Remplacez kanban.example.com par votre propre nom d’hôte aux deux endroits où il apparaît.

services:
  planka:
    image: ghcr.io/plankanban/planka:2.1.1
    restart: unless-stopped
    volumes:
      - planka-data:/app/data
    environment:
      - BASE_URL=https://kanban.example.com
      - DATABASE_URL=postgresql://planka:${POSTGRES_PASSWORD}@postgres/planka
      - SECRET_KEY=${SECRET_KEY}
      - TRUST_PROXY=true
      - DEFAULT_ADMIN_EMAIL=you@example.com
      - DEFAULT_ADMIN_PASSWORD=${ADMIN_PASSWORD}
      - DEFAULT_ADMIN_NAME=Your Name
      - DEFAULT_ADMIN_USERNAME=admin
    networks:
      - proxy
      - internal
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.planka.rule=Host(`kanban.example.com`)"
      - "traefik.http.routers.planka.entrypoints=websecure"
      - "traefik.http.routers.planka.tls.certresolver=default"
      - "traefik.http.services.planka.loadbalancer.server.port=1337"
    depends_on:
      postgres:
        condition: service_healthy

  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=planka
      - POSTGRES_USER=planka
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    networks:
      - internal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U planka -d planka"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  planka-data:
  db-data:

networks:
  proxy:
    external: true
  internal:

Quatre choix de ce fichier méritent une explication. Ce sont ceux que l’on modifie le plus souvent avant de le regretter.

  • Il n’y a pas de bloc ports: sur le service Planka. Traefik accède au conteneur via le réseau proxy. Le port 1337 n’est donc jamais publié sur l’hôte. Le publier permettrait à n’importe qui de contourner votre proxy et votre certificat.
  • loadbalancer.server.port=1337 désigne le port à l’intérieur du conteneur. Planka écoute sur 1337. L’exemple en amont n’y accède sur 3000 que parce qu’il mappe le port vers l’hôte. Il n’y a pas de mapping vers l’hôte ici. Traefik doit donc connaître le port du conteneur.
  • condition: service_healthy est associé au healthcheck de Postgres. Sans lui, Planka démarre avant que la base de données accepte les connexions, échoue lors de sa première requête, puis s’arrête. Cela ressemble à une boucle de crash. Le fonctionnement est expliqué dans healthchecks Compose et ordre de démarrage.
  • Le service de base de données est nommé postgres à dessein. Planka 2 fait passer ses propres requêtes sortantes par un filtre interne dont la liste de blocage par défaut est localhost,postgres. Si vous renommez le service, vous retirez discrètement votre base de données de cette liste.

Vérifiez que Compose voit vos secrets avant de démarrer quoi que ce soit :

docker compose config | grep -E 'image:|BASE_URL|POSTGRES_USER'

Cette commande affiche le fichier avec les valeurs .env déjà remplacées. Une valeur vide signifie que Compose ne lit pas le fichier .env, généralement parce que vous exécutez la commande depuis un autre répertoire.

Ce que font réellement les variables d’initialisation de l’administrateur

Depuis Planka 1.13, aucun administrateur n’est créé automatiquement. Une nouvelle base de données ne contient donc aucun compte permettant de se connecter. Le groupe DEFAULT_ADMIN_* est l’une des deux méthodes pour résoudre ce problème.

Au démarrage, Planka recherche un utilisateur correspondant à DEFAULT_ADMIN_EMAIL. S’il n’en trouve aucun, il en crée un avec le mot de passe, le nom d’affichage et le nom d’utilisateur définis à côté de cette variable. Cela se produit au premier démarrage avec une base de données vide. Ces variables servent donc à initialiser un compte, pas à le gérer.

DEFAULT_ADMIN_EMAIL a une seconde fonction qui prête souvent à confusion. Tant que cette variable est définie, le compte qu’elle désigne ne peut être ni modifié ni supprimé depuis l’interface, par personne. Il s’agit d’une protection contre le verrouillage du compte. C’est également la raison pour laquelle vous ne pouvez pas renommer ce compte ni modifier son adresse e-mail dans l’interface. Supprimez la variable et redémarrez Planka : le compte redevient un administrateur ordinaire que vous pouvez modifier comme les autres.

La ligne du mot de passe demande une attention particulière. Tout ce qui se trouve sous environment: est lisible par toute personne pouvant exécuter docker inspect sur le conteneur. DEFAULT_ADMIN_PASSWORD ne doit donc pas y rester définitivement. Connectez-vous, changez votre mot de passe dans l’interface, supprimez cette ligne, puis exécutez de nouveau docker compose up -d.

La méthode la plus propre consiste à ne pas utiliser ces variables. Commentez tout le groupe DEFAULT_ADMIN_*, puis créez le compte de manière interactive :

docker compose run --rm planka npm run db:create-admin-user

La commande demande l’adresse e-mail, le mot de passe, le nom d’affichage et, éventuellement, un nom d’utilisateur. Elle écrit ensuite directement l’utilisateur dans la base de données. Le mot de passe ne passe jamais par le fichier Compose ni par l’environnement du conteneur. Utilisez cette méthode si plusieurs personnes disposent d’un accès shell au VPS. La commande démarre d’abord PostgreSQL grâce à depends_on. Elle fonctionne donc sur une stack qui n’a jamais démarré.

Les deux méthodes vous obligent à gérer manuellement les mots de passe Planka. Si votre équipe a déjà accumulé un quatrième jeu d’identifiants, Planka peut aussi déléguer les connexions à un fournisseur OIDC tel que Authentik utilisé comme votre propre serveur de single sign-on. Conservez alors l’administrateur initial comme compte de secours à utiliser si le fournisseur est indisponible.

Pourquoi BASE_URL bloque les connexions lorsque sa valeur ne correspond pas au nom d’hôte

BASE_URL est l’adresse exacte que les utilisateurs saisissent dans le navigateur, avec le schéma et sans slash final. Pour cette stack, il s’agit de https://kanban.example.com. Planka construit ses propres liens et sa connexion WebSocket à partir de cette valeur. Une valeur incorrecte de BASE_URL ne produit donc pas d’erreur explicite. La page se charge, puis le chargement ne se termine jamais.

Le cas le plus fréquent est le suivant : vous copiez l’exemple fourni en amont, vous laissez BASE_URL=http://localhost:3000 en place et vous accédez au site en HTTPS avec votre vrai domaine. Le formulaire de connexion est envoyé et vos identifiants sont acceptés. Le tableau n’apparaît jamais. Ouvrez la console de développement du navigateur. Vous verrez des requêtes vers /socket.io/ échouer, car le client a reçu l’instruction d’ouvrir sa connexion temps réel vers localhost:3000. Sur votre ordinateur portable, cette adresse n’existe pas.

TRUST_PROXY=true constitue l’autre partie du même problème. Planka se trouve derrière Traefik. Chaque requête lui parvient donc depuis l’adresse du proxy, en HTTP non chiffré, à l’intérieur du réseau Docker. Sans TRUST_PROXY, l’application ignore les en-têtes X-Forwarded-Proto et X-Forwarded-For définis par Traefik. Elle considère alors la connexion comme non sécurisée et traite tous les clients comme s’ils utilisaient une seule et même adresse IP. Lorsque cette option est activée, l’application lit ces en-têtes et utilise le même schéma que le navigateur.

Traefik relaie les WebSockets sans configuration supplémentaire. C’est l’une des raisons de le privilégier dans ce cas. Avec nginx, socket.io a besoin de son propre bloc location contenant proxy_set_header Upgrade $http_upgrade et proxy_set_header Connection "upgrade". Sinon, vous obtenez le même indicateur de chargement bloqué, mais pour une autre raison.

Déplacer le tableau vers un nouveau nom d’hôte signifie ensuite modifier deux éléments ensemble : la valeur BASE_URL et la règle Host() de Traefik. Si vous modifiez l’un sans modifier l’autre, l’indicateur de chargement réapparaît. Servir Planka depuis un sous-chemin tel que https://example.com/planka fonctionne à partir de la version 2.1.0, publiée en March 2026. Avec les tags plus anciens, utilisez un sous-domaine dédié.

Emplacement des pièces jointes et des avatars dans Planka

Planka 2 stocke tout ce qu’un utilisateur envoie sous un seul chemin dans le conteneur : /app/data. Les pièces jointes, les avatars des utilisateurs et les images d’arrière-plan des tableaux s’y trouvent tous. La version 1 utilisait trois répertoires distincts. Un fichier Compose copié depuis un ancien tutoriel monte donc des chemins qui n’existent plus, et le véritable répertoire de données n’est pas monté.

Ce montage unique fait la différence entre un tableau qui survit à une mise à niveau et une mauvaise surprise. Si /app/data n’est pas sur un volume, les fichiers envoyés sont écrits dans la couche inscriptible du conteneur. Cette couche est supprimée lorsque le conteneur est recréé. Le conteneur est recréé chaque fois que vous modifiez le tag de l’image. Le tableau réapparaît correctement, les cartes sont toujours là et tous les liens vers les pièces jointes sont morts, car les lignes de la base de données pointent encore vers des fichiers qui n’existent plus.

Le volume nommé du fichier Compose ci-dessus évite ce problème. Un bind mount fonctionne également et facilite la sauvegarde des fichiers avec les outils habituels, mais il nécessite une étape supplémentaire. Le processus Node dans le conteneur s’exécute avec l’UID 1000. Un répertoire hôte appartenant à root provoque donc une erreur de permissions lors du premier envoi :

sudo chown -R 1000:1000 /opt/planka/data

La différence entre les deux options est expliquée dans bind mounts et volumes nommés.

Si les pièces jointes dépassent l’espace disque disponible dans votre offre, Planka peut les écrire dans un stockage compatible S3 à l’aide de S3_ENDPOINT, S3_BUCKET et des variables de clé correspondantes. Cette configuration peut pointer vers un bucket hébergé ou vers un object store MinIO auto-hébergé sur une autre machine. Décidez-le avant que l’équipe ne remplisse le tableau, car ce réglage s’applique aux nouveaux envois.

Démarrez la stack et vérifiez son bon fonctionnement

docker compose pull
docker compose up -d
docker compose ps

docker compose ps doit afficher postgres comme healthy et planka comme running. Si Planka redémarre en boucle, vérifiez d’abord la connexion à la base de données, pas l’application.

docker compose logs -f planka

Un premier démarrage réussi exécute les migrations de la base de données, puis indique que le serveur écoute sur le port 1337. Vérifiez que le schéma a bien été créé en interrogeant directement Postgres, plutôt que de vous fier au journal :

docker compose exec postgres psql -U planka -d planka -c '\dt'

Une liste de tables contenant board et card signifie que les migrations ont été exécutées. « Did not find any relations » signifie que Planka ne s’est jamais connecté. Comparez donc DATABASE_URL avec les valeurs POSTGRES_USER et POSTGRES_PASSWORD de votre .env.

Vérifiez ensuite la route depuis votre propre machine, et non depuis le VPS :

curl -I https://kanban.example.com

HTTP/2 200 signifie que Traefik détient un certificat et atteint le conteneur. Une erreur 404 renvoyée par Traefik signifie que les labels du router ne correspondent pas, le plus souvent parce que le conteneur n’est pas connecté au réseau proxy. Ouvrez maintenant le site et connectez-vous avec le compte administrateur.

Effectuez un pg_dump avant chaque changement de version

Deux stockages distincts contiennent votre board. La sauvegarde doit donc couvrir les deux : la base de données Postgres et le volume planka-data. Effectuez le dump de la base de données pendant que la stack est en cours d’exécution.

docker compose exec -T postgres pg_dump -U planka -d planka > "planka-db-$(date +%F).sql"

Le -T est indispensable. Sans lui, Compose alloue un pseudo-terminal et la couche du terminal réécrit les fins de ligne dans le flux. Vous obtenez donc un fichier de dump qui échoue en cours de restauration. L’échec apparaît plusieurs semaines plus tard, au pire moment possible.

Sauvegardez ensuite les uploads. Commencez par trouver le nom réel du volume, car Compose le préfixe avec le nom du répertoire du projet.

docker volume ls | grep planka
docker run --rm -v planka_planka-data:/data -v "$PWD":/backup alpine \
  tar czf /backup/planka-files-$(date +%F).tgz -C /data .

Le projet fournit également docker-backup.sh et docker-restore.sh dans son dépôt. La documentation officielle recommande de les exécuter avec une tâche cron quotidienne. Les deux approches conviennent. En revanche, une sauvegarde que vous n’avez jamais restaurée ne convient pas. Restaurez-en une sur un VPS de test, puis vérifiez que vous pouvez vous connecter et ouvrir une pièce jointe.

Exécutez le dump immédiatement avant chaque changement de version. Une sauvegarde de la nuit précédente n’est pas équivalente à une sauvegarde effectuée avant la migration que vous allez lancer.

Épinglez les tags et consultez les notes de version

Les deux tags d’image de ce fichier sont épinglés volontairement.

ghcr.io/plankanban/planka:2.1.1 correspond à une version précise, actuelle en août 2026. latest change chaque fois que l’upstream publie une nouvelle version. Un docker compose pull habituel peut donc appliquer une migration de schéma à un moment que vous n’avez pas choisi. Consultez les notes de version avant de modifier ce numéro, car les changements incompatibles et les correctifs de sécurité y sont décrits. La version 2.0.3 a été publiée comme version de sécurité. C’est exactement le type de changement qu’il faut lire avant de l’appliquer, plutôt que de l’absorber par accident.

postgres:16-alpine est épinglé sur une version majeure pour une raison plus importante. Postgres écrit son répertoire de données dans un format lié à la version majeure. Le serveur refuse d’ouvrir un répertoire écrit par une autre version. Écrivez postgres:latest, laissez le tag passer à 17, et le conteneur ne démarrera pas :

FATAL:  database files are incompatible with server
DETAIL:  The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17.

Aucune donnée n’est perdue, et un redémarrage ne résout pas non plus le problème. Passer à une nouvelle version majeure de Postgres nécessite un dump depuis l’ancienne version, puis une restauration dans un nouveau répertoire de données avec la nouvelle version. Il s’agit d’une opération planifiée, effectuée avec la stack arrêtée, et non d’un effet secondaire d’un pull d’image.

Si vous migrez une installation Planka 1.x existante au lieu de repartir de zéro, cette mise à niveau possède sa propre procédure documentée dans la documentation du projet. Il n’existe aucun moyen de revenir à la version 1 sans avoir préalablement créé une sauvegarde.

Modes d’échec et chaînes affichées

Planka redémarre en boucle et le journal mentionne la base de données. Les identifiants de DATABASE_URL ne correspondent pas à l’environnement Postgres. Notez que POSTGRES_PASSWORD est appliquée uniquement lors de la première initialisation du répertoire de données. Corriger la variable après un premier démarrage incorrect ne change donc rien. Vous devez supprimer le volume db-data, puis redémarrer.

La connexion réussit, mais le tableau ne se charge jamais. BASE_URL ne correspond pas à l’adresse affichée dans la barre du navigateur, ou TRUST_PROXY est absente. La console du navigateur affiche des requêtes en échec vers /socket.io/.

Les téléversements échouent alors que tout le reste fonctionne. Un bind mount appartient à root. Exécutez sudo chown -R 1000:1000 sur le répertoire de l’hôte, puis redémarrez le conteneur.

Les pièces jointes ont disparu après une mise à niveau. /app/data n’était pas associé à un volume. Les fichiers se trouvaient donc dans la couche du conteneur remplacée par la mise à niveau. Restaurez les fichiers depuis une sauvegarde, puis ajoutez le volume avant de modifier à nouveau le tag de l’image.

Traefik renvoie une erreur 404. Le conteneur n’est pas connecté au réseau proxy, ou la règle Host() ne correspond pas à votre enregistrement DNS. docker compose config affiche les labels après substitution. C’est à cet endroit que les fautes de frappe deviennent visibles.

Les notifications ou les webhooks n’arrivent jamais. Planka 2 envoie ses requêtes HTTP sortantes via un filtre interne. La liste de blocage par défaut couvre localhost et postgres. Un webhook destiné à un autre conteneur sur le même hôte peut être bloqué intentionnellement. Modifiez OUTGOING_ALLOWED_HOSTS au lieu de supprimer le filtre.

Une fois le service opérationnel, la charge d’exploitation reste faible. Consultez les notes de version et exportez la base de données avant chaque mise à niveau. Après un redémarrage, la stack redémarre automatiquement grâce à restart: unless-stopped, à condition que le service Docker soit lui-même activé au démarrage. La section Stacks Compose qui redémarrent après un redémarrage couvre les cas où ce n’est pas le cas.

FAQ

Pourquoi Planka charge-t-il indéfiniment après la connexion ?

Les identifiants ont été acceptés, mais la connexion temps réel a échoué. Planka construit l’URL WebSocket à partir de BASE_URL. Si cette variable contient encore http://localhost:3000 alors que vous accédez au site via https://kanban.example.com, le navigateur tente d’ouvrir une socket vers une adresse qui n’existe pas sur votre machine. La console du navigateur affiche des requêtes en échec vers /socket.io/. Définissez BASE_URL avec l’adresse publique exacte, sans slash final, ajoutez TRUST_PROXY=true pour que l’application respecte l’en-tête X-Forwarded-Proto transmis par votre reverse proxy, puis exécutez docker compose up -d.

Comment créer le premier utilisateur administrateur Planka ?

Depuis la version 1.13, aucun administrateur n’est créé automatiquement. Définissez DEFAULT_ADMIN_EMAIL ainsi que les variables correspondantes pour le mot de passe, le nom et le nom d’utilisateur, puis démarrez la stack. Vous pouvez aussi exécuter docker compose run --rm planka npm run db:create-admin-user et répondre aux invites. La commande interactive est plus sûre sur un serveur partagé, car le mot de passe n’entre jamais dans l’environnement du conteneur, où docker inspect peut le lire. Si vous laissez DEFAULT_ADMIN_EMAIL défini ensuite, ce compte ne pourra plus être modifié ni supprimé depuis l’interface.

Où Planka stocke-t-il les pièces jointes et les avatars ?

Dans Planka 2, tous les fichiers envoyés sont stockés sous /app/data dans le conteneur, notamment les pièces jointes, les avatars des utilisateurs et les arrière-plans des tableaux. Montez ce chemin sur un named volume. S’il n’est pas monté, les fichiers restent dans la couche inscriptible du conteneur et sont supprimés lors de sa prochaine recréation, ce qui se produit à chaque mise à niveau de l’image. Un bind mount convient également, mais le processus Node s’exécute avec l’UID 1000. Exécutez donc sudo chown -R 1000:1000 sur le répertoire de l’hôte, sinon les envois échoueront avec une erreur de permissions.

De combien de RAM Planka auto-hébergé a-t-il besoin ?

Le projet ne publie aucune configuration matérielle minimale. La configuration de 2 vCPU et 4 GB souvent indiquée sur les pages des hébergeurs correspond à leur offre par défaut, pas à une mesure, et elle est généreuse pour un petit tableau. La charge se limite à un processus Node et un processus Postgres. Une offre avec 1 vCPU et 2 GB suffit donc pour une équipe de deux à cinq personnes. Exécutez docker stats --no-stream après une semaine d’utilisation normale et dimensionnez le serveur à partir de vos propres mesures. Surveillez davantage l’espace disque que la mémoire, car les pièces jointes sont ce qui augmente le plus rapidement.

Comment mettre Planka à niveau sans perdre de données ?

Effectuez le dump de la base de données et archivez le volume des fichiers envoyés immédiatement avant la mise à niveau, et non selon la sauvegarde de la nuit précédente. Utilisez docker compose exec -T postgres pg_dump -U planka -d planka > planka-db.sql en conservant -T afin que le pseudo-terminal ne corrompe pas la sortie redirigée. Lisez les release notes de chaque version intermédiaire, remplacez le tag de l’image par une release précise plutôt que par latest, puis exécutez docker compose pull et docker compose up -d et surveillez le log de la migration. Laissez le tag de Postgres fixé sur sa version majeure, car le serveur refuse d’ouvrir un répertoire de données écrit par une autre version majeure.