Article 3 : Docker + GitHub Actions

Automatisation du build Docker de mon portfolio avec GitHub Actions, publication sur Docker Hub et redéploiement transparent sur Kubernetes.
Automatiser le build et le push d’une app Node.js
Dans cette troisième étape, je m’attaque à la conteneurisation de mon application Next.js (portfolio personnel), et à la mise en place d’une pipeline CI/CD pour automatiser le build de l’image Docker, son push sur Docker Hub, et le redéploiement automatique sur Kubernetes.
CI/CD : création de l’image Docker
L’objectif est de :
- Produire une image Docker légère et optimisée
- La publier automatiquement sur Docker Hub
- Redémarrer le déploiement sur le cluster sans downtime
Tout est défini dans une pipeline GitHub Actions située dans le dépôt de mon application, sous .github/workflows/deploy.yml
.
Construction de l’image Docker
Voici le Dockerfile
utilisé :
# Étape 1 — Base d’image pour builder et runner
FROM node:23-alpine AS base
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED=1
# Étape 2 — Installation des dépendances
FROM base AS deps
COPY package.json package-lock.json ./
RUN npm ci
# Étape 3 — Build avec récupération sécurisée des articles Dev.to
FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_PUBLIC_URL=https://woulf.fr
ENV NEXT_PUBLIC_EMAIL=corentinboucardpro@gmail.com
# Injection sécurisée du token Dev.to via BuildKit
RUN --mount=type=secret,id=devto_token \\
DEVTO_API_KEY=\$(cat /run/secrets/devto_token) node scripts/fetchDevtoArticles.js && npm run build
# Étape 4 — Image finale pour l’exécution
FROM base AS runner
ENV NODE_ENV=production
ENV PORT=3000
# Création d’un utilisateur non-root
RUN addgroup -S nodejs -g 1001 && \\
adduser -S nextjs -u 1001 -G nodejs && \\
mkdir .next && chown nextjs:nodejs .next
WORKDIR /app
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]
🧠 Quelques remarques sur le Dockerfile :
- Multi-stage build : séparation claire entre les étapes de base, d'installation, de build, et de runtime, ce qui permet une image finale propre et minimale.
- Base alpine (node:23-alpine) : image légère, rapide à télécharger, avec une surface d’attaque réduite (bon point pour la sécurité).
- Copie du
package*.json
avant le code source : permet de tirer parti du cache Docker tant que les dépendances ne changent pas → accélère drastiquement les builds. - Utilisation de
npm ci
: garantit une installation propre et rapide des dépendances en se basant uniquement sur lepackage-lock.json
, sans résoudre les versions à nouveau. - Injection sécurisée du token Dev.to avec BuildKit : évite d’exposer des secrets sensibles dans l’image ou les logs. Le token est utilisé uniquement le temps du build.
- Téléchargement dynamique des articles Dev.to : via un script exécuté pendant le build, ce qui permet de maintenir le contenu à jour sans le committer dans le dépôt.
- Création d’un utilisateur non-root (
nextjs
) : renforce la sécurité à l’exécution en limitant les permissions du processus Node.js. - Répertoires
.next/standalone
et.next/static
: copiés depuis l'étape de build pour ne garder que le strict nécessaire à l’exécution (selon les recommandations Next.js pour le déploiement Docker). EXPOSE 3000
: uniquement informatif, mais utile pour la documentation, certains outils, et la lisibilité du Dockerfile.
🔐 Résultat : une image Docker légère, sécurisée, rapide à builder, et prête à être déployée en prod.
Pipeline dans le dépôt applicatif
La pipeline deploy.yml
contient deux jobs :
- Le build & push de l’image
- Le redéploiement sur le cluster Kubernetes
name: Deploy Website
on:
push:
branches:
- main
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: \${{ secrets.DOCKER_USERNAME }}
password: \${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: woulf/portfolio:latest
secrets: |
"devto_token=\${{ secrets.DEVTO_TOKEN }}"
rollout_k8s:
runs-on: ubuntu-latest
needs: docker
steps:
- name: Set up Kubernetes context
uses: azure/k8s-set-context@v3
with:
kubeconfig: \${{ secrets.KUBECONFIG }}
- name: Restart Deployment
run: |
kubectl rollout restart deployment/portfolio -n default
kubectl rollout status deployment/portfolio -n default
🧠 Quelques remarques sur la pipeline :
- Déclencheur automatique sur
push
surmain
: permet de publier les changements dès qu’ils sont mergés, sans action manuelle. Utile en mode rolling release. - Workflow modulaire : deux jobs bien séparés (
docker
etrollout_k8s
), ce qui isole les responsabilités entre la construction/push et le déploiement. - Utilisation de
docker/build-push-action@v5
: outil maintenu par Docker Inc. pour builder efficacement avec BuildKit, gérer les caches, secrets, tags, et plateformes. - Secrets gérés par GitHub (
secrets.*
) : aucun mot de passe ou token dans le code. Le token Dev.to est passé viasecrets
en tant que secret mount avec BuildKit. - BuildKit +
--mount=type=secret
: permet d’injecter un token dans unRUN
temporairement (jamais exposé dans l'image finale, ni dans les logs). - Push sur Docker Hub avec tag
woulf/portfolio:latest
: ton image est automatiquement versionnée et disponible en ligne pour un déploiement rapide sur n’importe quel cluster. - Redéploiement Kubernetes via
kubectl rollout restart
: déclenche un redéploiement du pod sans downtime (Kubernetes gère le remplacement progressif). - Vérification du statut avec
kubectl rollout status
: empêche la pipeline de continuer si le déploiement échoue → fiabilise le process. - Utilisation de
needs: docker
dans le job de déploiement : garantit que l’image est bien poussée avant le restart sur le cluster.
🔄 Résultat : un pipeline CI/CD robuste, 100% automatisé, qui met à jour ton contenu, ton image Docker, et ton déploiement Kubernetes en un seul push.
Gestion des secrets
Aucun identifiant n’est codé en dur. Tout est stocké de manière sécurisée via les GitHub Secrets :
DOCKER_USERNAME
/DOCKER_PASSWORD
→ pour Docker HubKUBECONFIG
→ pour se connecter à mon cluster MicroK8s à distanceDEVTO_TOKEN
→ pour accéder à l’API Dev.to et télécharger les articles automatiquement pendant le build
Résultat
✅ À chaque push :
- Les articles Dev.to sont récupérés
- L’image est reconstruite et poussée
- L’app est redéployée sans interruption
💡 Prochaines étapes :
- Ajouter scan Trivy
- Ajout d’un
HEALTHCHECK
- Réduction des dépendances côté prod
➡️ Dans le prochain article, je vous explique comment j’ai versionné l’infrastructure Kubernetes dans un second dépôt, et mis en place une pipeline GitOps pour garder le cluster synchronisé.