Du local à la production : Déployez la dernière stack Nuxt 4 avec Docker

Du local à la production : Mon guide pour déployer Nuxt 4 avec Docker
J'ai toujours trouvé que la dernière ligne droite d'un projet — le passage d'un environnement de développement local fluide à un serveur de production stable — est l'endroit où se cache l'essentiel de la complexité. Avec les récents changements dans l'écosystème Nuxt, j'ai voulu documenter exactement comment je containerise mes applications aujourd'hui. Dans ce guide, je vais vous présenter ma configuration personnelle pour déployer une application Nuxt 4 avec Docker, optimisée pour la performance et la simplicité d'utilisation.
Nous travaillons actuellement avec un ensemble d'outils puissants et stables :
- Nuxt v4 - La dernière évolution du framework que j'utilise pour tous mes projets.
- Nuxt UI v4 - Ma bibliothèque de référence pour créer des interfaces soignées.
- Nuxt Content v3 - L'outil avec lequel je gère le contenu que vous lisez actuellement.
J'ai trouvé cette stack spécifique incroyablement robuste, et bien qu'elle apporte de nombreux changements, la logique de déploiement reste élégante une fois que la configuration Docker est bien maîtrisée. Laissez-moi vous montrer comment je mets cela en place.
Configuration de votre projet
Avant de commencer avec Docker, assurez-vous que votre projet Nuxt est correctement configuré. Voici un package.json minimal :
{
"name": "nuxt-app",
"private": true,
"dependencies": {
"@nuxt/content": "latest",
"@nuxt/ui": "^4.3.0",
"@nuxt/image": "2.0.0",
"nuxt": "^4.2.2"
}
}
Explication du Dockerfile
Notre Dockerfile utilise un processus de build multi-étapes pour créer une image de production optimisée. Décomposons chaque section :
FROM node:22.13.0-alpine AS build
WORKDIR /app
COPY pnpm-lock.yaml package.json ./
# Enable corepack for pnpm support
RUN corepack enable
RUN pnpm install --frozen-lockfile --prod
COPY . .
RUN pnpm run build
FROM node:22.13.0-alpine AS final
WORKDIR /app
COPY --from=build /app/.output .output
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]
💡 Conseils d'expert :
- Utiliser
alpineréduit la taille de l'image de base d'environ 300 Mo corepack enablegère les versions de pnpm de manière cohérente entre les environnements- Le build multi-étapes peut réduire la taille finale de l'image jusqu'à 90%
--frozen-lockfilegarantit que les versions des dépendances correspondent exactement- Copier uniquement le répertoire
.outputempêche le code source d'être inclus dans l'image de production
Configuration de Docker Compose
Le fichier docker-compose.yml orchestre notre configuration de conteneurs :
services:
nuxt-app:
build:
context: .
dockerfile: Dockerfile
container_name: nuxt-app
restart: always
ports:
- '3000:3000'
healthcheck:
test: [CMD, curl, -f, 'http://localhost:3000/api/hello']
interval: 30s
timeout: 10s
deploy:
resources:
limits:
memory: 1G
💡 Points clés :
- restart: always garantit la relance automatique en cas de crash
- Le point de terminaison de vérification de l'état assure que votre application fonctionne réellement
- Les limites de ressources empêchent les fuites de mémoire du conteneur
- Le mappage des ports permet un accès direct à votre application
Le healthcheck que j'utilise permet de s'assurer que le conteneur répond réellement. Je crée généralement une route API simple pour gérer cela :
export default defineEventHandler(() => {
return 'Hello World!'
})
Pourquoi j'automatise mes builds avec GitHub Actions
Builder et pousser des images manuellement est fastidieux. Voici le workflow que j'utilise pour automatiser le processus, garantissant que mon portfolio est toujours à jour avec un simple push git :
name: Build and Push Portfolio Docker Image
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: 'Version tag (ex: v1.0.0)'
required: true
type: string
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=tag
type=raw,value=${{ inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
💡 Fonctionnalités du Workflow :
- Trigger automatique sur les tags (v1.0.0, v2.0.0, etc.)
- Support des triggers manuels avec version personnalisée
- Utilisation du cache GitHub pour accélérer les builds
- Tagging automatique des images avec les versions et le tag latest
- Utilisation du nom du dépôt comme nom de l'image (mettre à jour
IMAGE_NAMEsi nécessaire)
Pour utiliser cette configuration:
- Publiez une nouvelle release avec un tag de version (v1.0.0, v2.0.0, etc.) dans votre dépôt GitHub ou poussez un nouveau tag :
git tag v1.0.0 && git push --tags - Ou déclenchez manuellement le workflow depuis l'onglet Actions de GitHub. Vos images seront disponibles à l'adresse ghcr.io/votreutilisateur/votre-repo:v1.0.0
Réflexions finales sur la production
Avec le temps, j'ai réalisé qu'un bon déploiement est tout aussi important que le code lui-même. Voici quelques derniers conseils que je garde toujours à l'esprit :
- Cohérence : Des tags de version spécifiques évitent les problèmes de type "ça marche sur ma machine" en production.
- Surveillance : Je garde toujours un œil sur ces points de terminaison de healthcheck.
- Sécurité : Les variables d'environnement et le SSL ne sont pas négociables.
Bonus : Mon workflow Coolify
Depuis que j'ai commencé à utiliser Coolify, mon processus de déploiement est devenu nettement plus simple. Une fois l'image publiée sur le GitHub Registry :
- Je connecte mon instance Coolify au dépôt.
- Je la pointe vers
ghcr.io/votreutilisateur/votre-repo:latest. - Je synchronise mes variables d'environnement et je déploie.
C'est un workflow qui m'a fait gagner un temps précieux, et j'espère qu'il en sera de même pour vous !