Next.js'i OpenShift'e Deploy Etmek – Netlify'ın Öğretmediği Şeyler

Next.js'i OpenShift'e Deploy Etmek – Netlify'ın Öğretmediği Şeyler
Zelkulon20 Nisan 20261 dk okuma

Netlify hiçbir şeyi yanlış yapmadı. Ama gerçek Kubernetes kontrolünün nasıl hissettirdiğini bilmek istedim. 30 günlük OpenShift sandbox, daha hızlı bir site – ve neden kaldığımızın dürüst cevabı.

Neden OpenShift – ve neden geri döndük?

Netlify hiçbir şeyi yanlış yapmadı. Railway da yapmadı. Ama gerçek kontrolün nasıl hissettirdiğini bilmek istedim – sihirli deployment yok, soyutlama üstüne soyutlama yok. OpenShift Developer Sandbox ücretsiz, Kubernetes üzerinde çalışıyor ve ilk Next.js sayfasını yayına almak yarım günümü aldı. Bu makale, başlamadan önce sahip olmak istediğim şey.

Sonraki adım: Spring Boot microservisleri Railway'den OpenShift'e taşımak. Ama önce frontend çalışmalıydı.

nginx Tuzağı: OpenShift'te Root Yok

İlk deneme standart bir nginx container'ıydı. OpenShift onu hemen kapattı. Yardımcı bir hata mesajı yok, sadece CrashLoopBackOff. Neden: OpenShift varsayılan olarak root container'lara izin vermiyor. nginx port 80'de çalışıyor ve port 80 root gerektiriyor. Normal sunucularda sorun yok – OpenShift'te engelleyici.

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 ✓

⚠ İlk deploy'da CrashLoopBackOff

Problem: Standart nginx root olarak çalışır (port 80) – OpenShift varsayılan olarak root container'lara izin vermez.

Fix: nginxinc/nginx-unprivileged:alpine kullan. Port 8080'de çalışır, root gerekmez.

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

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

Özel GitHub Repo Bağlamak

OpenShift özel bir GitHub reposuna doğrudan erişemiyor. Personal Access Token yeterli değil – secret'ın doğru URL ile ilişkilendirilmesi için anotasyon da gerekli.

# 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

✓ Secret anotasyonu zorunlu

Anotasyon olmayan bir GitHub secret otomatik olarak çalışmaz. build.openshift.io/source-secret-match-uri-1 anotasyonu secret'ı doğru Git URL'sine bağlar – ancak o zaman clone başarılı olur.

Next.js için Dockerfile: Multi-Stage ve Standalone

Normal bir Dockerfile devasa bir image üretir – node_modules yüzlerce megabyte. Multi-stage build bunu çözer: build aşaması derler, runtime aşaması yalnızca sonucu kopyalar.

Neden OpenShift – ve neden geri döndük?

Anahtar, next.config.ts'deki output: "standalone" ayarı. Next.js o zaman yalnızca gerçekten import edilen bağımlılıkları .next/standalone'a paketler – final image'da node_modules gerekmez.

# 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 ~800MB tasarruf sağlar

Standalone olmadan Dockerfile tüm node_modules dizinini final image'a kopyalar. Standalone ile yalnızca .next/standalone klasörü yeterli – Next.js gerekli bağımlılıkları zaten gömüyor.

NEXT_PUBLIC_* Tuzağı: Build Zamanı vs. Runtime

Bu, zaman açısından en pahalı hataydı. Next.js, build zamanında dondurulan değişkenler ile runtime'da değerlendirilen değişkenler arasında kesin bir ayrım yapar.

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

⚠ Frontend'de NEXT_PUBLIC_* boştu

Problem: Değişkenler runtime secret olarak ayarlandı – ama Next.js onları build zamanında inline eder. Bundle'da boş string'ler vardı.

Fix: NEXT_PUBLIC_*'ı BuildConfig'de buildArgs olarak tanımla. Sunucu tarafı secret'lar (API anahtarları) runtime secret olarak kalır.

# 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

oc new-app Yerine BuildConfig

oc new-app --strategy=docker, OpenShift Git URL'ini doğrudan doğrulayamazsa sık sık başarısız olur. Daha temiz çözüm: BuildConfig'i manuel olarak YAML ile oluşturmak.

⚠ Build başlatmada InvalidOutputReference

Problem: oc new-app ImageStream oluşturmuyor – ImageStream olmadan build çıktısı kaydedilemiyor.

Fix: İlk build'den önce: 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

Bir build 5–7 dakika sürer – Next.js TypeCheck ve production build zaman alır. npm cache'i hızlandırır ama sandbox için yeterli.

Edge Route ile Otomatik HTTPS

Beni en çok şaşırtan şey: TLS manuel bir adım değil. OpenShift sertifika yönetimini tamamen üstleniyor – Edge Route oluştur, HTTPS hemen çalışıyor.

# 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 TLS'i tamamen yönetiyor

Edge Termination, TLS'in OpenShift sınırında sonlandırıldığı anlamına gelir. Sertifika satın alma, Let's Encrypt yapılandırma, manuel yenileme yok. Sadece oc create route edge ve bitti.

OpenShift vs. Netlify vs. Railway

ÖzellikOpenShiftNetlifyRailway
Altyapı kontrolü✅ Vollständig✅ Abstrakt✅ Abstrakt
Giriş engeli⚠ Komplex✅ Push & Deploy✅ Push & Deploy
HTTPS✅ Automatisch (Edge)✅ Automatisch✅ Automatisch
Root container❌ Kein root✅ Kein Problem✅ Kein Problem
Maliyet30 Tage SandboxKostenlos (kommerziell)$5 Starter
Performans🚀 Sehr schnell✅ Gut✅ Gut

Tablo, 30 günlük sandbox deneyiminden sonra dürüst bir değerlendirmeyi gösteriyor. OpenShift küçük ekipler için Netlify'ın yerini almıyor.

Sonuç: Daha Hızlı Ama Herkes İçin Değil

OpenShift daha hızlı. Bu teori değil – 30 günlük sandbox'tan sonra site belirgin şekilde daha hızlı yükleniyor, görseller anında geliyor. Kubernetes zamanlaması, ayrılmış kaynaklar, soğuk başlangıç derdi yok.

Yine de Netlify ve Railway'de kalıyoruz. OpenShift daha kötü olduğu için değil, sandbox süresi dolduğu ve gerçek bir OpenShift kümesi küçük bir UG için çok pahalı olduğu için. Performans avantajı şu an maliyeti haklı kılmıyor.

  • Root container çalışmaz – nginxinc/nginx-unprivileged zorunlu
  • NEXT_PUBLIC_* buildArgs'a girer, runtime secret'a değil
  • İlk build'den önce ImageStream oluştur
  • YAML ile BuildConfig, oc new-app'dan daha güvenilir
  • Performans ölçülebilir şekilde daha iyi – ama küçük UG'ler için fiyat uymuyor

Bu birinci fazı. Sonraki makalelerde OpenShift'in Spring Boot microservisleri nasıl yönettiği ele alınacak – service discovery, kalıcı veritabanlar, iç iletişim. Çabanın karşılığını verip vermeyeceği görülecek.

Next.js'i OpenShift'e Deploy Etmek – Netlify'ın Öğretmediği Şeyler