Développement12 min de lecture

Déployer Strapi sur un VPS : le guide complet

Strapi est le CMS headless open source de référence en Node.js. Le self-héberger sur un VPS vous donne la propriété totale de vos contenus, de votre base de données et de vos uploads — sans quota d'abonnement, sans dépendance à Strapi Cloud, et sans plateforme tierce entre vos équipes et vos données. Ce guide couvre l'intégralité du déploiement : génération du projet, Dockerfile multi-étages, docker-compose avec PostgreSQL, secrets de production, Nginx, HTTPS, stockage S3, sauvegardes automatisées et mises à jour sans casse.

Strapi Cloud ou auto-hébergement : ce que vous perdez, ce que vous gagnez

Strapi Cloud est pratique pour démarrer, mais il impose des contraintes qui deviennent vite bloquantes en production réelle. Le plan Free limite les types de contenu et les utilisateurs admin ; les plans payants facturent au nombre de requêtes API et d'uploads. Surtout, vos données résident sur l'infrastructure de Strapi Inc. — ce qui est incompatible avec les exigences de localisation de données de nombreux clients (RGPD strict, contrats sectoriels, données sensibles). En self-hébergeant sur un VPS, vous récupérez la propriété complète de la base PostgreSQL, le choix du stockage (disque local ou bucket compatible S3), la liberté d'installer n'importe quel plugin de la communauté, et des volumes de contenu qui ne dépendent plus d'une grille tarifaire. La contrepartie est opérationnelle — vous gérez les mises à jour, les sauvegardes et le monitoring. Ce guide vous montre exactement comment faire.

Bénéfices de l'auto-hébergement de Strapi sur un VPS

  • Propriété des données — votre base PostgreSQL et vos uploads restent sur votre VPS, sous votre contrôle exclusif
  • Aucun quota de plan — le nombre d'entrées, de types de contenu et de comptes admin n'est plus fixé par un abonnement, mais par les ressources de votre VPS
  • Plugins communautaires — tout plugin npm s'installe librement, sans validation ni liste blanche de la plateforme
  • API sur votre domaine — REST et GraphQL exposés sur cms.mondomaine.com, sans intermédiaire ni limite de débit imposée
  • Pile unifiée — Strapi et un frontend Nuxt ou Next.js cohabitent sur le même VPS, derrière un reverse proxy unique
  • Localisation des données — essentielle pour la conformité RGPD et les exigences contractuelles de vos clients
  • Coût prévisible — le VPS a un tarif fixe mensuel, indépendant du volume de contenu ou du trafic API
  • Contrôle des migrations — vous choisissez quand et comment appliquer les mises à jour de Strapi

Prérequis : matériel, logiciels et domaine

Strapi est plus gourmand que la plupart des frameworks car le build de l'admin React consomme plus de 2 Go de RAM à lui seul. Un VPS de 1 Go de RAM échouera systématiquement en OOM pendant la phase de build — ne sous-dimensionnez pas. Cibles minimales : 2 vCPU, 4 Go de RAM. En production stable, une fois le build terminé, l'application tourne confortablement avec 512 Mo à 1 Go. Sur le plan logiciel : Docker 24+ et Docker Compose v2 (docker compose, pas docker-compose), Git pour récupérer votre code sur le serveur, et Certbot pour le TLS. Côté base de données : PostgreSQL 16 est recommandé en production — SQLite est fonctionnel en développement mais trop limité sous charge concurrente et incompatible avec certains plugins. Côté réseau : pointez cms.mondomaine.com vers l'IP de votre VPS avant de commencer, et prévoyez 20 Go de disque minimum (node_modules, uploads, dumps de sauvegarde).

Préparer le projet avant de toucher au serveur

01

Générer le projet Strapi

Sur votre poste, générez le projet avec la commande officielle npx create-strapi-app@latest mon-cms --dbclient=postgres. Choisissez TypeScript si votre équipe le maîtrise. Initialisez le dépôt Git, poussez-le sur votre forge, puis clonez-le sur le VPS dans /srv/mon-cms. Tout ce qui suit se prépare dans ce dépôt et non sur le serveur : le VPS ne doit recevoir que du code déjà versionné.

02

Brancher la configuration de base de données sur PostgreSQL

Dans config/database.js — ou config/database.ts en TypeScript —, la fonction exportée reçoit env et renvoie un objet connection. Déclarez client: 'postgres', puis six clés lues depuis l'environnement dans le sous-objet connection : host: env('DATABASE_HOST', '127.0.0.1'), port: env.int('DATABASE_PORT', 5432), database: env('DATABASE_NAME', 'strapi'), user: env('DATABASE_USERNAME', 'strapi'), password: env('DATABASE_PASSWORD', '') et ssl: env.bool('DATABASE_SSL', false). Le second argument de env() n'est qu'un repli de développement : aucune valeur réelle ne doit figurer dans ce fichier, il part dans Git.

03

Générer les cinq secrets de production

Strapi refuse de démarrer en production sans APP_KEYS, API_TOKEN_SALT, ADMIN_JWT_SECRET, JWT_SECRET et TRANSFER_TOKEN_SALT. Générez chacun avec openssl rand -base64 32 ; APP_KEYS en attend plusieurs, séparées par des virgules, donc produisez-en au moins deux. Écrivez-les dans /srv/mon-cms/.env sur le VPS — jamais dans Git — avec DATABASE_HOST, DATABASE_NAME, DATABASE_USERNAME, DATABASE_PASSWORD, NODE_ENV=production et URL=https://cms.mondomaine.com. Une clé manquante se traduit par un refus de démarrage, pas par un avertissement.

04

Écrire le Dockerfile multi-étages

Deux étages suffisent à garder l'image de production légère. L'étage builder part de node:20-alpine, fixe WORKDIR /app, copie package*.json, lance npm ci, copie le reste du code, puis compile l'admin avec NODE_ENV=production npm run build. L'étage runner repart de la même image de base et ne recopie du builder que trois choses : le dossier de build, node_modules et package.json. Terminez par EXPOSE 1337 et un CMD qui appelle npm run start — c'est le script de démarrage que Strapi installe, n'essayez pas de lancer un fichier serveur à la main. Si le build meurt en OOM, posez NODE_OPTIONS=--max-old-space-size=4096 avant npm run build.

Déployer la pile sur le VPS

01

Décrire les services dans docker-compose.yml

Le fichier déclare deux services et un volume nommé. Le service postgres utilise l'image postgres:16-alpine, reçoit POSTGRES_DB, POSTGRES_USER et POSTGRES_PASSWORD dans son bloc environment, monte le volume pgdata sur /var/lib/postgresql/data et passe en restart: unless-stopped. Le service strapi se construit depuis le Dockerfile local avec build: ., lit ses variables par env_file: .env, déclare depends_on: postgres, monte ./public/uploads sur /app/public/uploads et publie son port avec ports: 127.0.0.1:1337:1337. Cette liaison sur la boucle locale est le point important : Nginx devient le seul point d'entrée public, le port 1337 n'est jamais exposé à l'extérieur. Un service redis reste optionnel, pour le cache de sessions ou les files de traitement.

02

Construire l'image et démarrer la pile

Lancez docker compose build, puis docker compose up -d. Suivez le démarrage avec docker compose logs -f strapi. Strapi applique ses migrations de schéma au premier démarrage dès que NODE_ENV=production est posé. Comptez deux à cinq minutes : la construction de l'admin React est de loin l'étape la plus lente. Attendez la ligne Strapi started successfully avant de continuer.

03

Placer Nginx en reverse proxy

Créez le vhost /etc/nginx/sites-available/cms.mondomaine.com. Il écoute en listen 80 sur server_name cms.mondomaine.com, porte un client_max_body_size 50M — indispensable pour les uploads de médias — et un unique bloc location / qui fait proxy_pass http://127.0.0.1:1337. Ajoutez-y les quatre en-têtes que Strapi attend derrière un proxy : Host, X-Real-IP, X-Forwarded-For et X-Forwarded-Proto, chacun posé par une directive proxy_set_header. Activez le vhost par un lien symbolique vers sites-enabled, puis validez et rechargez avec nginx -t && systemctl reload nginx.

04

Activer HTTPS et fixer la variable URL

Obtenez le certificat avec certbot --nginx -d cms.mondomaine.com : Certbot réécrit le vhost pour rediriger HTTP vers HTTPS. Vérifiez ensuite que URL=https://cms.mondomaine.com figure bien dans le .env, sans barre oblique finale. C'est cette variable que Strapi utilise pour construire les liens des médias et les redirections de l'admin ; sans elle, les fichiers téléversés sortent avec des adresses fausses et le panneau d'administration se comporte mal derrière le proxy. Redémarrez le conteneur après toute modification du .env : les variables sont lues au démarrage.

05

Vérifier que l'instance répond vraiment

Ouvrez https://cms.mondomaine.com/admin et créez le premier compte administrateur — Strapi le demande au premier accès et ne le redemandera plus. Contrôlez ensuite trois points : docker compose ps montre les deux conteneurs en état running, l'API publique répond sur /api, et l'envoi d'un fichier de test depuis la médiathèque aboutit. Un 502 Bad Gateway à ce stade signale presque toujours un conteneur strapi arrêté ou encore en cours de build : relisez docker compose logs strapi avant de toucher à Nginx.

Sortir les médias du disque : le provider S3

Par défaut, Strapi range les fichiers téléversés dans public/uploads, sur le disque du VPS. Le montage ./public/uploads du docker-compose les fait survivre à une recréation de conteneur, mais ils grossissent avec la médiathèque, entrent dans chaque sauvegarde et disparaissent avec le serveur. Le provider officiel règle les trois problèmes : npm install @strapi/provider-upload-aws-s3. Déclarez-le ensuite dans config/plugins.js, sous la clé upload puis config : provider: 'aws-s3' et un objet providerOptions qui lit quatre valeurs de l'environnement — accessKeyId: env('AWS_ACCESS_KEY_ID'), secretAccessKey: env('AWS_ACCESS_SECRET'), region: env('AWS_REGION') et params: { Bucket: env('AWS_BUCKET') }. N'importe quel stockage compatible S3 convient : Scaleway Object Storage, Wasabi, Cloudflare R2. Deux pièges méritent d'être connus avant la bascule. Les médias déjà téléversés ne migrent pas tout seuls — basculez le provider avant la mise en ligne, ou copiez le contenu de public/uploads vers le bucket à la main. Et les URLs des médias changent de domaine : si votre frontend les met en cache ou les réécrit, vérifiez-le après la bascule.

Automatiser la sauvegarde de la base

01

Créer le dossier de destination

Sur le VPS, mkdir -p /srv/backups/strapi. Gardez ce dossier hors du dépôt Git et hors de tout chemin monté dans un conteneur : un dump ne doit jamais partir dans une image ni dans une release.

02

Repérer le nom réel du conteneur PostgreSQL

docker compose ps donne le nom exact, de la forme mon-cms-postgres-1. Il dérive du nom du dossier de projet : ne le recopiez pas d'un guide, lisez-le sur votre machine. Un script de sauvegarde qui vise un conteneur inexistant échoue en silence une fois placé dans le cron.

03

Écrire la commande de dump

Une seule ligne suffit : docker exec mon-cms-postgres-1 pg_dump -U strapi strapi | gzip > /srv/backups/strapi/strapi-$(date +%Y%m%d).sql.gz. Le pg_dump s'exécute dans le conteneur, la compression et l'écriture se font sur l'hôte. Lancez-la une première fois à la main et vérifiez la taille du fichier produit : un dump de quelques octets signale une erreur d'authentification avalée par le tube.

04

Purger les dumps trop anciens

Ajoutez à la suite find /srv/backups/strapi -name '*.sql.gz' -mtime +7 -delete. Sans cette ligne, le disque se remplit en quelques semaines : c'est la panne la plus banale d'une sauvegarde quotidienne — la base tombe parce que la sauvegarde a saturé le volume.

05

Planifier, puis sortir les dumps du serveur

Placez les deux commandes dans un script /etc/cron.daily/strapi-backup, avec #!/bin/bash en première ligne et un chmod +x pour le rendre exécutable. Ajoutez-y un rsync vers un stockage externe, ou un envoi vers le même bucket S3 que les médias. Une sauvegarde qui reste sur la machine qu'elle sauvegarde ne protège de rien ; testez une restauration complète au moins une fois avant d'en dépendre.

Mettre à jour Strapi sans casser les migrations

Une mise à jour se pilote depuis le dépôt, pas sur le serveur : changez la version dans package.json, poussez, puis, sur le VPS, enchaînez git pull et docker compose build && docker compose up -d. Strapi détecte et applique les migrations de schéma au démarrage tant que NODE_ENV=production est posé ; confirmez-le avec docker compose logs strapi | grep -i migrat. Trois précautions valent le détour. Prenez un pg_dump juste avant la montée de version, et pas seulement celui de la nuit précédente — une migration ratée se répare par une restauration, jamais par un second essai. Lisez les notes de version des plugins que vous avez installés : une montée majeure de Strapi casse plus souvent un plugin communautaire que le cœur lui-même. Enfin, si une migration échoue, ne relancez pas la pile en boucle : chaque redémarrage rejoue la même migration sur une base déjà à moitié modifiée. Arrêtez les conteneurs, lisez le journal complet, restaurez le dump si nécessaire, puis corrigez.

Dépannage : les erreurs les plus fréquentes

OOM pendant le build (Killed ou JavaScript heap out of memory) : le build de l'admin React dépasse la mémoire disponible. Deux remèdes — ajouter NODE_OPTIONS=--max-old-space-size=4096 dans l'étage builder du Dockerfile, ou augmenter temporairement le swap du VPS avec fallocate -l 2G /swapfile, chmod 600 /swapfile, mkswap /swapfile puis swapon /swapfile. Si le VPS reste bloqué, construisez l'image sur une machine plus puissante et poussez-la dans un registre.

Cannot find module @strapi/plugin-* : ne partagez jamais le dossier node_modules entre un environnement de développement local et l'image de production via un volume Docker. Le node_modules local est compilé pour votre système d'exploitation, pas pour l'Alpine Linux du conteneur. Supprimez tout volume node_modules du docker-compose et laissez le npm ci du Dockerfile s'en charger.

URL mismatch dans l'admin, ou médias en chemin relatif : la variable URL du .env doit correspondre exactement à l'adresse publique HTTPS de votre Strapi, sans barre oblique finale. Toute divergence casse les liens des médias téléversés et provoque des erreurs CORS dans le panneau d'administration.

413 Request Entity Too Large : la directive client_max_body_size de Nginx est trop basse. Passez-la à 50M au minimum, ou à 100M si vous téléversez des vidéos, puis rechargez Nginx.

password authentication failed for user au démarrage : le mot de passe du .env a changé après la création du volume pgdata. PostgreSQL ne relit son mot de passe qu'à l'initialisation du volume ; alignez le .env sur le mot de passe existant, ou repartez d'un volume neuf après avoir sauvegardé la base.

Trois règles non négociables pour une instance Strapi en production : S3 pour les uploads (@strapi/provider-upload-aws-s3) — vos médias survivent à toute recréation de conteneur et ne grossissent pas le disque du VPS ; pg_dump quotidien automatisé copié hors du serveur — une sauvegarde locale seule disparaît avec le VPS ; NODE_ENV=production impérativement — le mode développement recompile l'admin à chaud, expose le Content-Type Builder et désactive les optimisations de cache, à proscrire en production.

Strapi et un frontend Nuxt ou Next.js sur le même VPS

Il est tout à fait possible de faire cohabiter Strapi et un frontend sur le même VPS, à condition d'avoir suffisamment de RAM — comptez 8 Go pour les deux, puisque les deux builds peuvent se déclencher au même moment. L'architecture la plus simple utilise Nginx comme dispatcher : les requêtes vers cms.mondomaine.com sont proxiées vers le port 1337 (Strapi), et celles vers mondomaine.com vers le port 3000 (Nuxt ou Next.js). Strapi expose son API REST sur /api — le frontend la consomme directement sur le réseau Docker interne, sans repasser par Nginx, ce qui réduit la latence. Si votre frontend génère des pages statiques (nuxt generate, ou next build en export statique), Nginx peut servir les fichiers depuis le disque et ne proxier que les routes dynamiques. Cette architecture monolithique est idéale pour un projet de taille moyenne — un seul serveur, un seul certificat TLS, un seul point de supervision. Si vous préférez séparer le frontend, nos guides de déploiement Nuxt et Node.js reprennent la même méthode sur un second VPS Cloud dimensionné pour le build.

Hébergez votre CMS Strapi en toute autonomie

Le VPS Cloud ServOrbit fournit la RAM nécessaire au build de l'admin Strapi et un environnement Docker + PostgreSQL prêt à l'emploi, pour garder la pleine propriété de vos contenus et de vos données.

Besoin d'aide ?

Parcourez notre centre d'aide et notre FAQ, ou contactez notre équipe — rappel, WhatsApp ou e-mail. Support en français, anglais et arabe.