Déployer Next.js sur OpenShift – ce que Netlify ne vous apprend pas

Déployer Next.js sur OpenShift – ce que Netlify ne vous apprend pas
par Zelkulon20 avril 20261 min de lecture

Netlify n'a rien mal fait. Mais je voulais savoir ce que ressent un vrai contrôle Kubernetes. 30 jours de sandbox OpenShift, un site plus rapide – et la réponse honnête sur pourquoi nous sommes restés.

Pourquoi OpenShift – et pourquoi en repartir ?

Netlify n'a rien mal fait. Railway non plus. Mais je voulais savoir ce que ça fait d'avoir un vrai contrôle – pas de déploiement magique, pas d'abstraction sur abstraction. OpenShift Developer Sandbox est gratuit, fonctionne sur Kubernetes, et il m'a fallu une demi-journée pour mettre en ligne la première page Next.js. Cet article est ce que j'aurais voulu avoir avant de commencer.

L'étape suivante : migrer les microservices Spring Boot de Railway vers OpenShift. Mais d'abord, le frontend devait fonctionner.

Le piège nginx : Pas de root sur OpenShift

La première tentative était un container nginx standard. OpenShift l'a immédiatement tué. Pas d'erreur utile, juste CrashLoopBackOff. La raison : OpenShift n'autorise pas les containers root par défaut. nginx tourne sur le port 80, et le port 80 nécessite root. Sur les serveurs normaux ce n'est pas un problème – sur OpenShift c'est bloquant.

OpenShift Security Context Constraint (SCC)

  Standard nginx (Port 80, root)
  ─────────────────────────────
  Container startet → OpenShift prüft SCC
  → User 0 (root) verboten
  → CrashLoopBackOff ✗

  nginxinc/nginx-unprivileged (Port 8080, non-root)
  ──────────────────────────────────────────────────
  Container startet → OpenShift prüft SCC
  → Non-root User ✓
  → Pod läuft ✓

⚠ CrashLoopBackOff au premier déploiement

Problem: nginx standard tourne en root (port 80) – OpenShift interdit les containers root par défaut.

Fix: Utiliser nginxinc/nginx-unprivileged:alpine. Tourne sur le port 8080, pas de root nécessaire.

# Falsch – schlägt fehl auf OpenShift:
FROM nginx:alpine

# Richtig – rootless, Port 8080:
FROM nginxinc/nginx-unprivileged:alpine

Connecter un dépôt GitHub privé

OpenShift ne peut pas simplement accéder à un dépôt GitHub privé. Un Personal Access Token ne suffit pas – le secret doit aussi être annoté pour qu'OpenShift l'associe automatiquement à la bonne URL.

# 1. Secret mit GitHub Personal Access Token anlegen
oc create secret generic github-secret \
  --from-literal=username=<github-username> \
  --from-literal=password=<github-PAT> \
  --type=kubernetes.io/basic-auth

# 2. Secret annotieren – OpenShift nutzt es automatisch für die URL
oc annotate secret github-secret \
  "build.openshift.io/source-secret-match-uri-1=https://github.com/<username>/*"

# 3. Builder-ServiceAccount bekommt Zugriff
oc secret link builder github-secret

✓ L'annotation du secret est obligatoire

Un secret GitHub sans annotation ne fonctionne pas automatiquement. L'annotation build.openshift.io/source-secret-match-uri-1 lie le secret à la bonne URL Git – c'est seulement alors que le clone réussit.

Dockerfile pour Next.js : Multi-Stage et Standalone

Un Dockerfile classique produirait une image énorme – node_modules représente des centaines de mégaoctets. Le build multi-stage résout ça : la phase de build compile, la phase runtime copie uniquement le résultat.

Pourquoi OpenShift – et pourquoi en repartir ?

La clé est output: "standalone" dans next.config.ts. Next.js ne regroupe alors que les dépendances réellement importées dans .next/standalone – pas besoin de node_modules dans l'image finale.

# next.config.ts – eine Zeile entscheidet alles:
output: "standalone"

# Das Ergebnis: .next/standalone enthält nur was wirklich gebraucht wird.
# Kein node_modules im finalen Image – spart ~800MB.

✓ output: standalone économise ~800 Mo

Sans standalone, le Dockerfile copie tout le répertoire node_modules dans l'image finale. Avec standalone, seul le dossier .next/standalone est nécessaire – Next.js a déjà intégré toutes les dépendances requises.

Le piège NEXT_PUBLIC_* : Build time vs. Runtime

C'était l'erreur la plus coûteuse en temps. Next.js fait une distinction stricte entre les variables figées au moment du build et les variables évaluées à l'exécution.

NEXT_PUBLIC_* – die Falle

  Build-Zeit                    Runtime
  ──────────────────────────    ──────────────────────────
  NEXT_PUBLIC_SITE_URL          BLOG_BASE_URL
  NEXT_PUBLIC_BLOG_BASE         AUTH_BASE_URL
  NEXT_PUBLIC_CLOUDINARY_*      CLOUDINARY_API_SECRET
  NEXT_PUBLIC_GA_*              CLOUDINARY_API_KEY

  → Werden in den JS-Bundle     → Werden beim Start des
    eingefroren (unveränderbar)   Servers ausgelesen

  Falsch gesetzt → leere         Falsch gesetzt → localhost
  Variablen im Frontend          als Proxy-Ziel

⚠ NEXT_PUBLIC_* était vide dans le frontend

Problem: Les variables étaient définies comme secrets runtime – mais Next.js les inline au moment du build. Le bundle contenait des chaînes vides.

Fix: Ajouter NEXT_PUBLIC_* comme buildArgs dans la BuildConfig. Les secrets côté serveur (clés API) restent comme secrets runtime.

# BuildConfig – NEXT_PUBLIC_* als buildArgs:
spec:
  strategy:
    type: Docker
    dockerStrategy:
      buildArgs:
        - name: NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME
          value: dein-cloud-name
        - name: NEXT_PUBLIC_SITE_URL
          value: https://zelkulon.com

# Runtime Vars – Server-seitige Secrets:
oc create secret generic zelkulon-secrets --from-env-file=.env.local
oc set env deployment/zelkulon-homepage --from=secret/zelkulon-secrets

BuildConfig plutôt que oc new-app

oc new-app --strategy=docker échoue fréquemment quand OpenShift ne peut pas vérifier directement l'URL Git. La solution plus propre : créer manuellement la BuildConfig via YAML.

⚠ InvalidOutputReference au démarrage du build

Problem: oc new-app ne crée pas d'ImageStream – sans ImageStream le résultat du build ne peut pas être stocké.

Fix: Avant le premier build : oc create imagestream zelkulon-homepage

# ImageStream zuerst anlegen – sonst: InvalidOutputReference
oc create imagestream zelkulon-homepage

# BuildConfig per YAML
cat <<EOF | oc apply -f -
apiVersion: build.openshift.io/v1
kind: BuildConfig
metadata:
  name: zelkulon-homepage
spec:
  source:
    type: Git
    git:
      uri: https://github.com/<username>/zelkulon-homepage.git
    sourceSecret:
      name: github-secret
  strategy:
    type: Docker
  output:
    to:
      kind: ImageStreamTag
      name: zelkulon-homepage:latest
EOF

# Build starten und live beobachten (dauert 5–7 Minuten)
oc start-build zelkulon-homepage --follow

Un build prend 5 à 7 minutes – le TypeCheck et le build de production Next.js prennent du temps. Le cache npm accélérerait les choses, mais c'est suffisant pour la sandbox.

HTTPS automatique avec Edge Route

Ce qui m'a le plus surpris : TLS n'est pas une étape manuelle. OpenShift gère entièrement les certificats – créez une Edge Route et HTTPS fonctionne immédiatement.

# App aus ImageStream deployen
oc new-app --image-stream=zelkulon-homepage:latest --name=zelkulon-homepage

# HTTPS Route mit Edge Termination – TLS übernimmt OpenShift automatisch
oc create route edge zelkulon-homepage \
  --service=zelkulon-homepage \
  --port=3000

# URL abrufen
oc get routes

✓ OpenShift gère TLS complètement

Edge Termination signifie que TLS est terminé à la frontière OpenShift. Pas de certificat à acheter, pas de Let's Encrypt à configurer, pas de renouvellement manuel. Juste oc create route edge et c'est terminé.

OpenShift vs. Netlify vs. Railway

FonctionnalitéOpenShiftNetlifyRailway
Contrôle de l'infrastructure✅ Vollständig✅ Abstrakt✅ Abstrakt
Barrière à l'entrée⚠ Komplex✅ Push & Deploy✅ Push & Deploy
HTTPS✅ Automatisch (Edge)✅ Automatisch✅ Automatisch
Containers root❌ Kein root✅ Kein Problem✅ Kein Problem
Coût30 Tage SandboxKostenlos (kommerziell)$5 Starter
Performance🚀 Sehr schnell✅ Gut✅ Gut

Le tableau présente une évaluation honnête après 30 jours d'utilisation de la sandbox. OpenShift ne remplace pas Netlify pour les petites équipes.

Conclusion : Plus rapide, mais pas pour tout le monde

OpenShift est plus rapide. Ce n'est pas de la théorie – après 30 jours de sandbox, le site se charge nettement plus vite, les images apparaissent instantanément. Ordonnancement Kubernetes, ressources dédiées, pas de démarrage à froid.

Nous restons malgré tout sur Netlify et Railway. Non pas parce qu'OpenShift est moins bien, mais parce que la période sandbox expire et qu'un vrai cluster OpenShift est trop coûteux pour une petite UG. L'avantage de performance ne justifie pas le coût pour l'instant.

  • Les containers root ne fonctionnent pas – nginxinc/nginx-unprivileged est obligatoire
  • NEXT_PUBLIC_* va dans buildArgs, pas dans les secrets runtime
  • Créer l'ImageStream avant le premier build
  • BuildConfig via YAML est plus fiable que oc new-app
  • La performance est mesurément meilleure – mais le prix ne convient pas aux petites UG

C'était la phase 1. Les prochains articles couvriront comment OpenShift gère les microservices Spring Boot – service discovery, bases de données persistantes, communication interne. Si l'effort en vaut la peine, on le saura.

Déployer Next.js sur OpenShift – ce que Netlify ne vous apprend pas