Déployer Next.js sur DigitalOcean
Suivez ça sur votre propre serveur. Commencer gratuitement, sans carte
Vous avez une appli Next.js qui fonctionne en local, et vous voulez la faire tourner sur un droplet DigitalOcean que vous contrôlez, plutôt que sur une plateforme qui facture à l’usage. Ce guide vous amène d’un droplet vide à une appli en ligne sur votre propre domaine : une petite image Docker construite à partir de la sortie standalone de Next.js, Caddy devant elle pour gérer les certificats HTTPS, et une routine courte pour livrer la prochaine version.
Ce qu’il vous faut
- Un droplet DigitalOcean. La taille Basic
s-1vcpu-2gb(1 vCPU, 2 Go de mémoire, 50 Go SSD, 2 000 Go de transfert) coûte 12 $ par mois et fait tourner une appli Next.js typique. Si votre build est lourd, la tailles-2vcpu-4gb(2 vCPU, 4 Go, 80 Go SSD) coûte 24 $ par mois. Les prix proviennent de la page de tarification des droplets de DigitalOcean. - Un nom de domaine dont vous pouvez modifier le DNS.
- Un compte DigitalOcean avec une clé SSH ajoutée.
- Une appli Next.js dans un dépôt Git, avec un fichier de verrouillage committé (
package-lock.json,yarn.lockoupnpm-lock.yaml).
Ce guide utilise Next.js 16.4.3 et Node.js 24, la ligne LTS actuelle, via l’image officielle node:24-slim.
Étape 1 : Créer le droplet
Dans le panneau de contrôle de DigitalOcean, choisissez Create → Droplets. Sélectionnez Ubuntu 24.04 (LTS) x64, le plan Basic avec un SSD Regular, et la taille à 12 $. Choisissez une région proche de vos utilisateurs (Francfort ou Amsterdam gardent les données dans l’UE), sélectionnez votre clé SSH, et créez le droplet.
Une fois prêt, copiez son adresse IPv4 publique et connectez-vous :
ssh root@your-droplet-ip
Vous devriez voir la bannière de bienvenue d’Ubuntu et une invite root@.
Pendant que vous êtes dans les paramètres DNS de votre domaine, créez un enregistrement A qui pointe le domaine (ou un sous-domaine comme app.example.com) vers cette IP. Le DNS peut prendre quelques minutes à se propager, et Caddy en a besoin en place avant de pouvoir obtenir un certificat à l’étape 5, donc autant le faire maintenant.
Étape 2 : Installer Docker
Ubuntu n’inclut pas Docker par défaut. Le script d’installation officiel de Docker installe le moteur et le plugin Compose en une seule fois :
curl -fsSL https://get.docker.com | sh
Vérifiez que les deux sont bien installés :
docker --version
docker compose version
Vous devriez voir une ligne de version pour chacun, et aucune erreur.
Étape 3 : Activer la sortie standalone
Next.js peut déterminer précisément quels fichiers le serveur de production a besoin et les copier dans .next/standalone, avec un petit server.js qui démarre l’appli. Cela garde l’image finale petite, car elle ne transporte pas tout votre dossier node_modules.
Ouvrez next.config.ts (ou next.config.js) et ajoutez output: "standalone" à l’objet de configuration, en gardant les options que vous avez déjà :
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "standalone",
};
export default nextConfig;
Committez le changement. Vous devriez voir .next/standalone/server.js apparaître la prochaine fois que vous lancez npm run build en local.
Étape 4 : Ajouter un Dockerfile
Ajoutez ce Dockerfile à la racine du dépôt. Il suit la structure de l’exemple officiel with-docker du dépôt Next.js : une étape installe les dépendances avec le gestionnaire de paquets correspondant à votre fichier de verrouillage, une autre build l’appli, et la dernière exécute uniquement la sortie standalone en tant qu’utilisateur non privilégié node.
FROM node:24-slim AS dependencies
WORKDIR /app
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN if [ -f package-lock.json ]; then npm ci --no-audit --no-fund; \
elif [ -f yarn.lock ]; then corepack enable yarn && yarn install --frozen-lockfile --production=false; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm install --frozen-lockfile; \
else echo "No lockfile found." && exit 1; \
fi
FROM node:24-slim AS builder
WORKDIR /app
COPY --from=dependencies /app/node_modules ./node_modules
COPY . .
ARG NEXT_PUBLIC_SITE_URL
ENV NEXT_PUBLIC_SITE_URL=$NEXT_PUBLIC_SITE_URL
ENV NODE_ENV=production
RUN if [ -f package-lock.json ]; then npm run build; \
elif [ -f yarn.lock ]; then corepack enable yarn && yarn build; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm build; \
fi
FROM node:24-slim AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
COPY --from=builder --chown=node:node /app/public ./public
RUN mkdir .next && chown node:node .next
COPY --from=builder --chown=node:node /app/.next/standalone ./
COPY --from=builder --chown=node:node /app/.next/static ./.next/static
USER node
EXPOSE 3000
CMD ["node", "server.js"]
Deux lignes méritent une explication. ARG NEXT_PUBLIC_SITE_URL et la ligne ENV qui suit existent parce que Next.js intègre chaque variable NEXT_PUBLIC_ dans le bundle du navigateur pendant next build. Une valeur que vous ne fournissez qu’au démarrage du conteneur arrive trop tard pour celles-ci. Ajoutez une paire ARG et ENV par variable NEXT_PUBLIC_ utilisée par votre appli, ou retirez la paire si elle n’en utilise aucune. Les variables réservées au serveur (URLs de base de données, secrets d’API) n’ont pas leur place ici : elles sont lues au moment de l’exécution.
À côté, ajoutez un .dockerignore pour que votre node_modules local, la sortie de build et les fichiers d’environnement n’entrent jamais dans le build :
.git
.next
node_modules
.env*
Exclure .env* est important : sans ça, COPY . . copierait vos secrets dans une couche du build.
Étape 5 : Ajouter Compose et Caddy
Le guide d’auto-hébergement de Next.js recommande de placer un reverse proxy devant le serveur Node plutôt que de l’exposer directement. Caddy fait ce travail et obtient et renouvelle aussi un certificat Let’s Encrypt par lui-même.
Créez compose.yaml dans le dépôt :
services:
app:
build:
context: .
args:
NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL}
restart: unless-stopped
env_file: .env
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
depends_on:
- app
volumes:
caddy-data:
caddy-config:
Seul Caddy publie des ports. L’appli écoute sur le port 3000 à l’intérieur du réseau Compose, où Caddy l’atteint par son nom de service. Le volume caddy-data conserve vos certificats, afin qu’ils survivent aux redémarrages.
Puis un Caddyfile à côté, avec votre propre domaine :
app.example.com {
reverse_proxy app:3000
}
Committez les deux fichiers.
Étape 6 : Mettre le code et les secrets sur le droplet
Sur le droplet, clonez le dépôt. Pour un dépôt privé, ajoutez d’abord une clé de déploiement en lecture seule au dépôt, ou clonez en HTTPS avec un jeton :
git clone https://github.com/you/your-app.git /opt/your-app
cd /opt/your-app
Créez-y le fichier .env. Il ne va jamais dans Git (le .gitignore par défaut de create-next-app ignore déjà les fichiers .env*), donc il reste sur le serveur entre les déploiements :
cat > .env <<'EOF'
NEXT_PUBLIC_SITE_URL=https://app.example.com
DATABASE_URL=postgres://user:password@db-host:5432/app
EOF
chmod 600 .env
Compose utilise ce seul fichier à deux reprises : il remplit ${NEXT_PUBLIC_SITE_URL} dans compose.yaml pour le build, et env_file transmet chaque variable à l’appli en cours d’exécution.
Étape 7 : Construire et démarrer
docker compose up -d --build
Le premier build prend quelques minutes. Vérifiez ensuite les conteneurs :
docker compose ps
Vous devriez voir app et caddy tous les deux avec un statut Up. Suivez le journal de Caddy pendant qu’il obtient le certificat :
docker compose logs -f caddy
Vous devriez voir une ligne indiquant que le certificat a été obtenu avec succès. Ouvrez https://app.example.com et votre appli se charge avec un certificat valide.
Étape 8 : Livrer des mises à jour
Chaque version passe par les mêmes trois commandes sur le droplet :
cd /opt/your-app
git pull
docker compose up -d --build app
Compose reconstruit l’image et remplace le conteneur app. Caddy continue de tourner et conserve son certificat. Attendez-vous à quelques secondes d’erreurs pendant que le nouveau conteneur démarre, car il n’y a qu’une seule copie de l’appli : si c’est important pour vous, déployez à un moment calme.
Pour revenir en arrière, basculez sur le commit précédent et reconstruisez :
git log --oneline -5
git checkout <previous-commit>
docker compose up -d --build app
Lancez git checkout main (ou le nom de votre branche) avant le prochain déploiement normal.
Étape 9 : Sauvegardes
L’appli elle-même est sans état : son code vit dans Git et son image peut être reconstruite à tout moment. Ce que vous ne pouvez pas reconstruire, c’est le fichier .env, donc gardez-en une copie ailleurs que sur le droplet, par exemple dans un gestionnaire de mots de passe. Si l’appli utilise une base de données, c’est elle la vraie chose à sauvegarder : les sauvegardes de droplet de DigitalOcean ou un pg_dump planifié et copié hors du serveur fonctionnent tous les deux.
Next.js garde son cache de pages et de données sur le disque du conteneur par défaut. Cela fonctionne pour une instance unique comme celle-ci, et le cache repart simplement vide après chaque déploiement, puisque le conteneur est remplacé.
Le raccourci : avec Railyard
La voie manuelle fonctionne, et vous en connaissez maintenant chaque rouage. Railyard fait le même travail sans le Dockerfile, le fichier Compose ni le Caddyfile. Voir Déployer Next.js sur DigitalOcean pour la marche à suivre complète.
- Dans le panneau de contrôle de DigitalOcean, ouvrez API et générez un jeton d’accès personnel avec un accès en lecture et en écriture.
- Dans Railyard, allez dans Servers → Add a server → Create on your cloud, choisissez DigitalOcean, une région et une taille (
s-1vcpu-2gbpar défaut). Le droplet est créé dans votre propre compte et DigitalOcean vous facture directement, sans majoration. Vous pouvez aussi apporter un droplet que vous avez déjà : n’importe quel serveur Ubuntu 22.04 ou 24.04 avec unsudosans mot de passe et les ports 22, 80 et 443 ouverts. - Déployez le dépôt, ou ouvrez
https://app.railyard.run/deploy?repo=https://github.com/you/your-app.gitavec l’URL de votre propre dépôt.
Railyard choisit npm, pnpm, yarn ou bun à partir de votre fichier de verrouillage, lance votre script de build et démarre l’appli avec npm start. L’appli écoute sur $PORT, et HOST et les variables similaires sont définies pour vous. Ajoutez Postgres ou Redis depuis la page de l’appli et la chaîne de connexion est injectée comme variable. Les gros builds front-end sont ajustés à la mémoire du serveur de build, donc vous n’avez pas à gérer le swap ou la taille du tas de Node vous-même. Chaque push redéploie, et le HTTPS est géré pour vous.
Le plan Free couvre un serveur plus un serveur de base de données et trois applis, sans carte requise. Pro est à 19 $ par mois par équipe et Team à 49 $ par mois par équipe. La documentation Node.js liste les règles de détection, et le guide de démarrage rapide vous accompagne à travers un premier déploiement.
Dépannage
bind: address already in use sur le port 80 ou 443. Un autre serveur web sur le droplet occupe déjà le port. sudo ss -tlnp | grep -E ':(80|443) ' montre quel processus. Arrêtez-le et désactivez-le (sudo systemctl disable --now nginx, par exemple), puis relancez docker compose up -d.
Le certificat n’arrive jamais. C’est presque toujours le DNS. Lancez dig +short app.example.com et comparez la réponse avec l’IP du droplet. Si elles diffèrent, corrigez l’enregistrement A et attendez : Caddy continue de réessayer par lui-même. Si le droplet a un pare-feu cloud, les ports 80 et 443 doivent être ouverts, car Let’s Encrypt valide via eux.
Une valeur NEXT_PUBLIC_ est vide ou périmée dans le navigateur. Elle était absente au moment du build. Vérifiez qu’elle est dans .env, que compose.yaml la transmet sous build.args, que le Dockerfile a une paire ARG et ENV correspondante, et reconstruisez avec docker compose up -d --build app. Modifier .env et redémarrer ne suffit pas pour celles-ci.
next build est tué en plein milieu. Le droplet de 2 Go a manqué de mémoire, et le noyau a tué le build. Ajoutez du swap et reconstruisez :
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
Passer à la taille à 24 $ fonctionne aussi, tout comme construire l’image ailleurs et la récupérer depuis un registre.
La connexion ou les redirections pointent vers la mauvaise URL. Votre bibliothèque d’authentification a encore une ancienne URL. Si vous utilisez Auth.js ou NextAuth, vérifiez AUTH_URL ou NEXTAUTH_URL dans .env, puis redémarrez avec docker compose up -d app.
FAQ
Est-ce que ça fonctionne avec pnpm ou yarn ? Oui. Le Dockerfile choisit le gestionnaire de paquets à partir du fichier de verrouillage, comme le fait l’exemple officiel de Next.js. Un fichier de verrouillage bun n’est pas géré par ce Dockerfile : ajoutez une branche pour lui ou committez un package-lock.json.
Puis-je faire tourner plus d’une appli sur le même droplet ? Oui. Donnez à chaque appli son propre service dans compose.yaml et son propre bloc de site dans le Caddyfile (other.example.com { reverse_proxy other:3000 }). Un seul Caddy les sert toutes et gère chaque certificat.
Est-ce que Image Optimization et Proxy fonctionnent encore ? Selon le guide d’auto-hébergement de Next.js, l’optimisation next/image et Proxy (la convention de fichier autrefois appelée Middleware) fonctionnent sans aucune configuration lorsque vous exécutez le serveur Next.js, ce que fait justement server.js.
Ai-je besoin d’une base de données ? Seulement si votre appli en utilise une. Pointez DATABASE_URL dans .env vers une base de données gérée ou une que vous faites tourner vous-même, et sauvegardez-la selon son propre horaire.
Devrais-je désactiver la télémétrie ? Next.js collecte de la télémétrie anonyme par défaut. Ajoutez ENV NEXT_TELEMETRY_DISABLED=1 aux étapes builder et runner si vous préférez vous en retirer.
Une fois tout ça en place, une version se résume à un git pull et une commande Compose, et la facture est fixe à 12 $ par mois avec 2 000 Go de transfert inclus, au lieu d’un chiffre qui bouge avec votre trafic.
Pourquoi les gens choisissent Railyard pour ça
- Votre propre compte infonuagique. Déployez sur DigitalOcean, Hetzner, Vultr, AWS, OVHcloud, ou n'importe quel serveur Linux par SSH, facturé directement par votre fournisseur, sans majoration.
- Un prix fixe par équipe. L'offre gratuite est gratuite pour toujours : un serveur plus un serveur de base de données, trois applications, sans carte. Pro coûte 19 $ par mois pour jusqu'à cinq serveurs et un nombre illimité d'applications. Team coûte 49 $ par mois avec des serveurs illimités. Aucuns frais par application, par déploiement, par siège ou par gigaoctet.
- Des gabarits en un clic. Déployez des applications open source, comme des bases de données, des outils d'analyse et des outils de développement, sur votre propre serveur en un clic.
- Des bases de données gérées. Postgres, MySQL et Redis, avec des sauvegardes planifiées, vérifiées par restauration, et la récupération à un point dans le temps.
- Déploiement par push. Déployez depuis GitHub, GitLab ou la CLI, avec HTTPS automatique et des mises en production sans interruption.