İçeriğe geç
·12 dk okuma

Node.js İçin Docker: Kimsenin Anlatmadığı Production-Ready Kurulum

Multi-stage build, root dışı kullanıcı, health check, secret yönetimi ve imaj boyutu optimizasyonu. Her Node.js production deployment'ında kullandığım Docker kalıpları.

Paylaş:X / TwitterLinkedIn

Çoğu production Node.js Dockerfile'ı kötü. "Biraz suboptimal" kötü değil. Root olarak çalışan, devDependencies gömülü 600MB'lık imajlar, health check yok, docker inspect ile herkesin okuyabileceği şekilde environment variable'lara hardcode edilmiş secret'lar türünde kötü.

Biliyorum çünkü o Dockerfile'ları ben yazdım. Yıllarca. Çalışıyorlardı, bu yüzden hiç sorgulamadım. Sonra bir gün güvenlik denetimi, container'ımızın PID 1 root olarak tüm dosya sistemine yazma erişimiyle çalıştığını işaretledi ve "çalışıyor" ile "production-ready" arasındaki farkın ne kadar büyük olduğunu kavradım.

Bu, artık her Node.js projesi için kullandığım Docker kurulumu. Teorik değil. Bu sitenin ve bakımını yaptığım birkaç başka sitenin arkasındaki servisleri çalıştırıyor. Buradaki her kalıp, ya alternatifinden zarar gördüğüm ya da başkasının zarar gördüğünü izlediğim için var.

Mevcut Dockerfile'ın Muhtemelen Neden Yanlış#

Dockerfile'ının neye benzediğini tahmin edeyim:

dockerfile
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["node", "server.js"]

Bu, Dockerfile'ların "hello world"ü. Çalışıyor. Ama production'da canını yakacak en az beş sorunu var.

Root Olarak Çalışmak#

Varsayılan olarak, Docker container'ları root olarak çalışır. Bu, container içindeki Node.js process'inin her şeyi yapabileceği anlamına geliyor — dosya yazma, paket yükleme, sistem ayarlarını değiştirme. Eğer birisi uygulamandaki bir zafiyet üzerinden kod çalıştırabilirse, tam root erişimi elde eder.

dockerfile
# Bunu yap — root olarak çalışma
FROM node:20-slim
 
RUN groupadd --gid 1001 nodejs \
  && useradd --uid 1001 --gid nodejs --shell /bin/bash --create-home nodejs
 
USER nodejs
WORKDIR /home/nodejs/app

USER direktifi bundan sonraki tüm komutların (ve nihai CMD'nin) o kullanıcı olarak çalışmasını sağlar. Container güvenliğinin en düşük maliyetli iyileştirmesidir.

Gereksiz Dosyaları Kopyalamak#

COPY . . her şeyi kopyalar. .git dizinini, node_modules'u, .env dosyalarını, test dosyalarını, dökümantasyonu — hiçbirinin production imajında olmaması gereken şeyleri.

dockerfile
# .dockerignore — bunu oluştur, ciddi ol
node_modules
.git
.gitignore
.env*
*.md
tests
coverage
.vscode
.eslint*
.prettier*
docker-compose*.yml
Dockerfile

.dockerignore dosyası, Git'in .gitignore'u gibi çalışır. Ayrıştırılmaz, kopyalanmaz, taşınmaz. İmaj boyutunu ve build süresini büyük ölçüde azaltır.

Katman Önbelleğini Bozmak#

Docker her RUN, COPY ve ADD talimatını bir katman olarak önbelleğe alır. Bir katman değiştiğinde, ondan sonraki tüm katmanlar da geçersiz olur. Yani COPY . . ardından RUN npm install yapıldığında, herhangi bir dosya değiştiğinde tüm bağımlılıklar yeniden yüklenir.

dockerfile
# Kötü — herhangi bir kod değişikliği npm install'ı tetikler
COPY . .
RUN npm install
 
# İyi — önce sadece package dosyalarını kopyala
COPY package.json package-lock.json ./
RUN npm ci
COPY . .

package.json'ı ayrı kopyalayarak, Docker bu katmanı önbelleğe alır. npm ci sadece package.json veya package-lock.json değiştiğinde çalışır. Kaynak kod değişiklikleri yalnızca COPY . . katmanını ve sonrasını etkiler. Build süreleri çarpıcı biçimde düşer.

npm install vs npm ci#

Production Dockerfile'larında her zaman npm ci kullan. Arasındaki fark önemli:

  • npm install lockfile'ı günceller, package.json aralıklarına göre farklı sürümler çözümleyebilir ve bağımlılık sorunlarını sessizce düzeltmeye çalışır
  • npm ci tam olarak lockfile'da yazanı yükler, node_modules'u tamamen siler ve baştan kurar, ve package.json ile lockfile uyuşmazsa başarısız olur

npm ci deterministiktir. Aynı lockfile her zaman aynı node_modules'u üretir. npm install deterministik değildir. Production'da bu fark önemlidir.

Multi-Stage Build: Asıl Gizli Silah#

Multi-stage build, Dockerfile'ının yapıyı çalışma zamanından ayırmasını sağlar. Test çalıştırabilir, TypeScript derleyebilir, kod lint'leyebilir — sonra nihai imajda sadece production'da gereken şeyleri gönderirsin.

Temel Kalıp#

dockerfile
# ===== Aşama 1: Bağımlılıklar =====
FROM node:20-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
 
# ===== Aşama 2: Build =====
FROM node:20-slim AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
 
# ===== Aşama 3: Production =====
FROM node:20-slim AS runner
WORKDIR /app
 
RUN groupadd --gid 1001 nodejs \
  && useradd --uid 1001 --gid nodejs --shell /bin/bash --create-home nodejs
 
# Sadece production'da gereken şeyleri kopyala
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
 
USER nodejs
EXPOSE 3000
CMD ["node", "dist/server.js"]

Nihai imaj sadece node:20-slim temel imajını, derlenmiş kodu ve node_modules'u içerir. TypeScript derleyici yok, kaynak dosyalar yok, devDependencies yok. İmaj boyutu önemli ölçüde düşer.

devDependencies'i Silmek#

Daha da iyisi — nihai imajda sadece production bağımlılıklarını yükle:

dockerfile
# ===== Aşama 1: Tüm bağımlılıklar (build için) =====
FROM node:20-slim AS deps-all
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
 
# ===== Aşama 2: Sadece production bağımlılıkları =====
FROM node:20-slim AS deps-prod
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
 
# ===== Aşama 3: Build =====
FROM node:20-slim AS builder
WORKDIR /app
COPY --from=deps-all /app/node_modules ./node_modules
COPY . .
RUN npm run build
 
# ===== Aşama 4: Production =====
FROM node:20-slim AS runner
WORKDIR /app
 
RUN groupadd --gid 1001 nodejs \
  && useradd --uid 1001 --gid nodejs --shell /bin/bash --create-home nodejs
 
COPY --from=deps-prod /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./package.json
 
USER nodejs
EXPOSE 3000
CMD ["node", "dist/server.js"]

Fark: nihai imaj builder'ın tam node_modules'u yerine deps-prod'dan gelen production node_modules'u içerir. Bu, proje yapısına bağlı olarak yüzlerce MB farkettirebilir.

Gerçek Boyut Farkı#

Projelerimden birinin gerçek boyutları:

Yaklaşımİmaj Boyutu
node:20 + npm install1.2GB
node:20-slim + npm ci485MB
Multi-stage + sadece prod deps187MB
Multi-stage + Alpine98MB

İlk yaklaşımdan sonuncuya kadar 12x küçülme. Bu daha hızlı pull, daha hızlı deployment, daha az depolama maliyeti ve daha küçük saldırı yüzeyi demek.

Temel İmaj Seçimi: slim vs Alpine vs Distroless#

Temel imaj seçimin, hem boyutu hem de pratikte neyin çalışıp çalışmadığını belirler.

node:20 (Tam Debian)#

~350MB'ın üzerinde temel imaj. git, python3, make, gcc ve birçok aracı içerir. Derleme gerektiren native bağımlılıklar için faydalıdır, ama hepsini production'a taşımak israf.

node:20-slim (Minimal Debian)#

~80MB temel imaj. Tam Debian ama gereksiz araçlar çıkarılmış. Çoğu Node.js uygulaması için tercihim. Tam Debian uyumluluğu — paketler gerektiğinde apt-get çalışır.

node:20-alpine (Alpine Linux)#

~50MB temel imaj. Alpine, glibc yerine musl libc kullanır. Boyut farkı gerçektir ama dikkat gerektiren uyumluluk sorunları vardır:

dockerfile
# Alpine: bazı native modüller ek bağımlılık gerektirir
FROM node:20-alpine
RUN apk add --no-cache python3 make g++
# bcrypt, sharp ve diğer native modüller derleme gerektirir

Native bağımlılıkların çoğu Alpine'da çalışır ama ek build araçları gerektirir. sharp, bcrypt, canvas gibi paketlerin hepsini test ettim — çalışıyorlar ama build aşamasında python3, make, ve g++ gerekiyor.

gcr.io/distroless/nodejs20-debian12 (Distroless)#

~30MB temel imaj. Shell yok, paket yöneticisi yok, ls bile yok. Sadece Node.js runtime'ı. En güvenli seçenek çünkü saldırı yüzeyi minimal:

dockerfile
FROM node:20-slim AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
RUN npm run build
 
FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
CMD ["dist/server.js"]

Dikkat: distroless'ta shell olmadığı için docker exec -it container sh çalışmaz. Debugging zorlaşır. Production güvenliği için mükemmel, geliştirme rahatlığı için bedeldir.

Benim Tavsiyem#

  • Geliştirme: node:20-slim — debug kolay, her şey çalışır
  • Production (çoğu proje): node:20-slim ile multi-stage — iyi denge
  • Production (güvenlik öncelikli): distroless — minimal saldırı yüzeyi
  • Production (boyut öncelikli): Alpine multi-stage — en küçük, ama native modülleri test et

Health Check: Container'ının Yaşayıp Yaşamadığını Bil#

Docker health check olmadan container'ın durumunu bilemez. Process çalışıyor olabilir ama uygulama kilitlenmiş, bağlantıları kabul etmiyor veya deadlock'ta olabilir. Docker bunu "healthy" olarak görür çünkü PID hâlâ canlıdır.

Temel Health Check#

dockerfile
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD node -e "fetch('http://localhost:3000/api/health').then(r => { if (!r.ok) throw new Error(); })"

Bu 30 saniyede bir /api/health endpoint'ine istek atar. Üç ardışık başarısızlık durumunda container "unhealthy" olarak işaretlenir.

Daha İyi Bir Health Check#

dockerfile
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
  CMD node healthcheck.js
javascript
// healthcheck.js
const http = require("http");
 
const options = {
  hostname: "localhost",
  port: process.env.PORT || 3000,
  path: "/api/health",
  method: "GET",
  timeout: 4000,
};
 
const req = http.request(options, (res) => {
  process.exit(res.statusCode === 200 ? 0 : 1);
});
 
req.on("error", () => process.exit(1));
req.on("timeout", () => {
  req.destroy();
  process.exit(1);
});
 
req.end();

Ayrı dosya kullanmanın avantajları:

  • Timeout yönetimi
  • Daha iyi hata işleme
  • Port'u environment variable'dan okuma
  • fetch'in mevcut olmadığı eski Node sürümlerinde çalışma

Uygulama Tarafındaki Health Endpoint#

typescript
// src/routes/health.ts
import type { Request, Response } from "express";
 
export async function healthCheck(req: Request, res: Response) {
  const checks = {
    uptime: process.uptime(),
    timestamp: Date.now(),
    status: "ok" as "ok" | "degraded" | "error",
    checks: {} as Record<string, { status: string; latency?: number }>,
  };
 
  // Veritabanı bağlantısını kontrol et
  try {
    const start = Date.now();
    await db.query("SELECT 1");
    checks.checks.database = {
      status: "ok",
      latency: Date.now() - start,
    };
  } catch {
    checks.checks.database = { status: "error" };
    checks.status = "degraded";
  }
 
  // Redis bağlantısını kontrol et
  try {
    const start = Date.now();
    await redis.ping();
    checks.checks.redis = {
      status: "ok",
      latency: Date.now() - start,
    };
  } catch {
    checks.checks.redis = { status: "error" };
    checks.status = "degraded";
  }
 
  const statusCode = checks.status === "ok" ? 200 : 503;
  res.status(statusCode).json(checks);
}

Health endpoint, downstream bağımlılıkları gerçekten kontrol etmeli. Sadece "200 OK" döndürmek, process'in canlı olduğunu söyler ama uygulamanın sağlıklı olup olmadığını söylemez.

Health Check Parametreleri#

  • --interval=30s: Ne sıklıkla kontrol edeceğini belirler. 30 saniye çoğu uygulama için iyi. Çok sık yapmak CPU harcar.
  • --timeout=5s: Tek bir kontrolün ne kadar süre bekleyeceği. Health check'in kendisi sorunlu olmamalı.
  • --start-period=30s: Uygulama başlangıcında health check'i kaç saniye bekletir. Node.js uygulamaları 10-30 saniye alabilir. Bu süre boyunca başarısızlıklar sayılmaz.
  • --retries=3: Kaç ardışık başarısızlıktan sonra "unhealthy" sayılır. 3 iyi bir varsayılan — geçici bir ağ kesintisi nedeniyle yanlış alarm vermez.

Secret Yönetimi: Her Şeyi Yanlış Yapıyorsun (Muhtemelen)#

Build zamanında secret yönetimi, çoğu Docker kurulumunun en yanlış gittiği yerdir.

Yaygın Hatalar#

dockerfile
# ❌ YANLIŞ — secret'lar katman geçmişinde kalır
ENV DATABASE_URL=postgres://user:password@host:5432/db
RUN echo "api_key=sk_live_abc123" > /app/.env
 
# ❌ YANLIŞ — ARG bile katman geçmişinde kalır
ARG GITHUB_TOKEN
RUN git clone https://${GITHUB_TOKEN}@github.com/org/private-repo.git

docker history komutu bu değerleri gösterir. Katmanlarda kalıcıdır.

Docker Build Secrets (Doğru Yol)#

Docker BuildKit, build zamanı secret'ları güvenli bir şekilde mount etmeye olanak tanır:

dockerfile
# syntax=docker/dockerfile:1
 
FROM node:20-slim AS builder
WORKDIR /app
COPY package.json package-lock.json ./
 
# Secret, bir dosya olarak mount edilir — katmana yazılmaz
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
  npm ci
 
COPY . .
RUN npm run build

Build komutu:

bash
docker build --secret id=npmrc,src=$HOME/.npmrc -t myapp .

Secret dosyası sadece o RUN komutu süresince kullanılabilir. Nihai imaja dahil olmaz. Katman geçmişinde görünmez.

Çalışma Zamanı Secret'ları#

Çalışma zamanı secret'ları (veritabanı URL'leri, API anahtarları) environment variable olarak geçirilmelidir, imaja gömülmemelidir:

bash
# İyi — çalışma zamanı env
docker run -e DATABASE_URL="postgres://..." -e API_KEY="sk_..." myapp
 
# Daha iyi — env dosyasından
docker run --env-file .env.production myapp
yaml
# docker-compose.yml
services:
  app:
    image: myapp
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - REDIS_URL=${REDIS_URL}
    env_file:
      - .env.production

Docker Swarm/Kubernetes Secrets#

Orchestration araçları kullanıyorsan, bunların kendi secret yönetimleri var:

yaml
# docker-compose.yml (Swarm)
services:
  app:
    image: myapp
    secrets:
      - db_password
      - api_key
 
secrets:
  db_password:
    external: true
  api_key:
    external: true

Uygulama secret'ları dosya olarak okur:

typescript
import { readFileSync } from "fs";
 
const dbPassword = readFileSync("/run/secrets/db_password", "utf8").trim();

Bu, environment variable'lardan daha güvenlidir çünkü /proc/*/environ üzerinden sızdırılamaz.

Graceful Shutdown: SIGTERM'i Düzgün İşle#

Docker container'ı durdururken bir SIGTERM sinyali gönderir. 10 saniye sonra (varsayılan), SIGKILL ile process'i zorla sonlandırır. Eğer uygulamanın SIGTERM işlemiyorsa, aktif istekler bırakılır, veritabanı bağlantıları temiz kapatılmaz ve veri kaybı olabilir.

PID 1 Problemi#

Dockerfile'ın CMD ["node", "server.js"] olduğunda, Node.js PID 1 olarak çalışır. Sorun: PID 1 varsayılan sinyal işleyicilerine sahip değildir. SIGTERM sinyalini hiçbir handler kurulmamışsa yok sayar.

typescript
// server.ts — SIGTERM handler
const server = app.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});
 
function gracefulShutdown(signal: string) {
  console.log(`${signal} received. Starting graceful shutdown...`);
 
  // Yeni bağlantıları kabul etmeyi durdur
  server.close(() => {
    console.log("HTTP server closed");
 
    // Veritabanı bağlantılarını kapat
    db.end()
      .then(() => {
        console.log("Database connections closed");
        process.exit(0);
      })
      .catch((err) => {
        console.error("Error during shutdown:", err);
        process.exit(1);
      });
  });
 
  // Zorla kapatma zamanlayıcısı
  setTimeout(() => {
    console.error("Graceful shutdown timed out. Forcing exit.");
    process.exit(1);
  }, 8000); // Docker'ın 10 saniyelik timeout'undan önce
}
 
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
process.on("SIGINT", () => gracefulShutdown("SIGINT"));

Tini veya dumb-init Kullan#

Alternatif: PID 1 olarak ince bir init process'i kullan:

dockerfile
FROM node:20-slim
 
# Tini'yi yükle
RUN apt-get update && apt-get install -y --no-install-recommends tini \
  && rm -rf /var/lib/apt/lists/*
 
ENTRYPOINT ["tini", "--"]
CMD ["node", "dist/server.js"]

Tini, sinyalleri düzgün şekilde Node.js process'ine iletir ve zombie process'leri temizler. Node 20+'nın kendi sinyal işleme mekanizması iyileşmiş olsa da, Tini hâlâ en güvenilir yaklaşım.

node:20-slim Zaten Tini İçerir (Neredeyse)#

Aslında docker-init komutu ile kullanabilirsin:

bash
docker run --init myapp

Bu tini'yi otomatik olarak PID 1 olarak ekler. Dockerfile'a bir şey eklemeye gerek yok. Ama docker-compose veya Kubernetes kullanıyorsan bunu ayrıca ayarlamalısın.

Production Docker Compose#

Geliştirme ve production Docker Compose dosyaları farklı olmalı. İşte production compose dosyam:

yaml
# docker-compose.production.yml
services:
  app:
    image: myapp:${VERSION:-latest}
    build:
      context: .
      dockerfile: Dockerfile
      target: runner
    restart: unless-stopped
    init: true
    ports:
      - "${PORT:-3000}:3000"
    environment:
      NODE_ENV: production
      PORT: "3000"
    env_file:
      - .env.production
    healthcheck:
      test: ["CMD", "node", "healthcheck.js"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "1.0"
        reservations:
          memory: 256M
          cpus: "0.25"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    security_opt:
      - no-new-privileges:true
    read_only: true
    tmpfs:
      - /tmp

Önemli ayarlar:

  • restart: unless-stopped — Crash sonrası otomatik yeniden başlatma, ama elle durdurulunca başlatma
  • init: true — PID 1 olarak tini kullan
  • deploy.resources — Bellek ve CPU limitleri. Node.js bellek sızıntısı durumunda tüm sunucuyu çökertmesini engeller
  • logging — Log rotasyonu. Olmadan diskler dolar
  • security_opt: no-new-privileges — Container içindeki process'lerin ayrıcalık yükseltmesini engeller
  • read_only: true — Root dosya sistemi salt okunur. Zararlı kod dosya yazamaz
  • tmpfs: /tmp — Geçici dosyalar için yazılabilir alan

İmaj Etiketleme: latest Kullanma#

latest etiketi bir sürüm değildir. "En son ne push edildiyse o" demektir. Geri alma yapılamaz, hangi sürümün çalıştığı bilinemez, iki farklı sunucuda farklı latest olabilir.

bash
# ❌ Kötü
docker build -t myapp:latest .
 
# ✅ İyi — Git SHA kullan
docker build -t myapp:$(git rev-parse --short HEAD) .
 
# ✅ İyi — Semantik versiyon
docker build -t myapp:2.1.0 .
 
# ✅ En iyi — İkisini birden
VERSION=$(git rev-parse --short HEAD)
docker build -t myapp:${VERSION} -t myapp:latest .
docker push myapp:${VERSION}

Her zaman spesifik bir etiket ile deploy et. latest'i kolaylık olsun diye tut ama docker-compose.production.yml veya Kubernetes manifest'inde asla latest kullanma.

Tam Production Dockerfile#

İşte her şeyi bir araya getiren tam Dockerfile:

dockerfile
# syntax=docker/dockerfile:1
 
# ===== Aşama 1: Bağımlılıklar =====
FROM node:20-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
 
# ===== Aşama 2: Production Bağımlılıkları =====
FROM node:20-slim AS deps-prod
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
 
# ===== Aşama 3: Build =====
FROM node:20-slim AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
 
# ===== Aşama 4: Production =====
FROM node:20-slim AS runner
LABEL maintainer="your-email@example.com"
LABEL org.opencontainers.image.source="https://github.com/your-org/your-repo"
 
WORKDIR /app
 
# Tini'yi yükle ve temizle
RUN apt-get update \
  && apt-get install -y --no-install-recommends tini \
  && rm -rf /var/lib/apt/lists/*
 
# Root olmayan kullanıcı oluştur
RUN groupadd --gid 1001 nodejs \
  && useradd --uid 1001 --gid nodejs --shell /bin/bash --create-home nodejs
 
# Sadece gerekli dosyaları kopyala
COPY --from=deps-prod /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./package.json
COPY healthcheck.js ./healthcheck.js
 
# Root olmayan kullanıcıya geç
USER nodejs
 
# Ortam değişkenleri
ENV NODE_ENV=production
ENV PORT=3000
 
EXPOSE 3000
 
# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
  CMD ["node", "healthcheck.js"]
 
# Tini init process'i ile başlat
ENTRYPOINT ["tini", "--"]
CMD ["node", "dist/server.js"]

Bu Dockerfile:

  • Multi-stage ile minimum imaj boyutu sağlar
  • Root olmayan kullanıcı ile çalışır
  • Sadece production bağımlılıklarını içerir
  • Health check dahil
  • Tini ile düzgün sinyal yönetimi
  • Etiketlerle metadata sağlar
  • Build cache'i verimli kullanır

Sık Yapılan Hataların Özeti#

HataSonuçÇözüm
Root olarak çalışmakGüvenlik açığıUSER nodejs
npm installDeterministik olmayan buildnpm ci
COPY . . ilk satırYavaş build, gereksiz dosyalar.dockerignore + aşamalı COPY
latest etiketiGeri alma yapılamazGit SHA veya semver etiketi
Health check yokKilitli container tespit edilemezHEALTHCHECK direktifi
Secret ENV'deKatman geçmişinde görünürBuild secrets veya runtime env
node:20 (tam)1GB+ imajnode:20-slim veya Alpine
SIGTERM yok sayılırKaba kapatma, veri kaybıGraceful shutdown handler

Bu kalıpların hiçbiri tek başına devrimsel değil. Ama hepsi bir arada, "çalışıyor" ile "production'da güvenle çalışıyor" arasındaki farkı oluşturur. Dockerfile'ını bir kez düzgün kur, sonra her projede kopyala. Yatırım ilk seferinde yapılır, getirisi her deployment'ta geri döner.

İlgili Yazılar