# syntax=docker/dockerfile:1 FROM node:20-alpine AS base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable && corepack prepare pnpm@10.8.1 --activate # ── Bağımlılık katmanı ── FROM base AS deps WORKDIR /app # better-sqlite3 Alpine'de kaynaktan derleniyor (musl için prebuilt yok) RUN apk add --no-cache python3 make g++ COPY package.json pnpm-lock.yaml ./ # pnpm store cache mount'ta yaşar: lockfile değişse bile inen paketler yeniden inmez RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ pnpm install --frozen-lockfile # ── Derleme katmanı ── FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules # Build girdileri açık listelenir: docs/, scripts/, CI dosyası değişiklikleri # bu katmanı invalide edemez. next build'in okuduğu her şey burada: # content/ (rehber.ts), data/ (db.ts prerender), public/ (opengraph-image). COPY package.json next.config.ts tsconfig.json postcss.config.mjs ./ COPY scripts/standalone-linkleri-onar.sh ./scripts/ COPY public ./public COPY data ./data COPY content ./content COPY src ./src ENV NEXT_TELEMETRY_DISABLED=1 # V8 heap sınırı ana süreç + statik üretim worker'larına miras kalır. # Turbopack'ın Rust tarafı bu sınırın dışında; onun hedefi next.config.ts'te # turbopackMemoryLimit ile verilir (BUILD_KAYNAK_KISITLI bloğu). ENV NODE_OPTIONS="--max-old-space-size=1024" ENV BUILD_KAYNAK_KISITLI=1 RUN --mount=type=cache,id=next-cache,target=/app/.next/cache \ pnpm build # Turbopack, ESM external'ların (@google/genai vb.) kök node_modules symlink'ini # nft trace'ine yazmıyor; eksik linkler tamamlanmazsa runtime MODULE_NOT_FOUND # ile düşer. Ayrıntı script'in başındaki açıklamada. RUN sh scripts/standalone-linkleri-onar.sh .next/standalone # ── Çalıştırma katmanı ── FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 # V8, cgroup bellek sınırını GÖRMEZ: heap üst sınırını konteynerin değil host'un # RAM'ine göre seçer (host 4 GB ise ~2 GB). docker-compose'daki mem_limit=1g ile # birleşince sonuç, V8 daha GC yapmadan kernel'ın konteyneri OOM ile öldürmesi # olur; o anda açık olan istek ters vekilde 502 "Bad Gateway" olarak düşer # (22 Eyl 2026: liste üretiminin hemen ardından alınan 502 raporu). Sınır elle # verilir: mem_limit'in ~%75'i; kalanı native taraf (better-sqlite3, mmap'lı # SQLite sayfaları, Node'un kendi RSS'i) için ayrılır. mem_limit değişirse bu # sayı da değişmeli. ENV NODE_OPTIONS="--max-old-space-size=768" RUN addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 nextjs # standalone çıktı, file-tracing ile seçilmiş kendi minimal node_modules'unu # içerir (serverExternalPackages dahil) — tam node_modules kopyalanmaz. COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/static ./.next/static COPY --from=builder /app/public ./public # Trace, data/yokatlas.db'yi de standalone'a koyuyor; runtime'da /app/data # volume'ü bunu gölgelediği için imajdan atılır (~22M). RUN rm -rf /app/data # Seed veritabanları: entrypoint açılışta bunları /app/data volume'üne kopyalar COPY data/ ./seed/ # Migration'lar: entrypoint açılışta db-goc.mjs ile volume'deki app.db'ye uygular COPY drizzle ./drizzle COPY scripts/db-goc.mjs ./scripts/db-goc.mjs COPY scripts/docker-entrypoint.sh ./docker-entrypoint.sh # SQLite veri dizini + Next'in runtime'da yazdığı image/fetch cache dizini # (.next'in geri kalanı root'ta kalır; salt-okunur olması sorun değil) RUN chmod +x /app/docker-entrypoint.sh && \ mkdir -p /app/data /app/.next/cache && \ chown -R nextjs:nodejs /app/data /app/seed /app/.next/cache USER nextjs EXPOSE 3000 ENV PORT=3000 ENV HOSTNAME="0.0.0.0" # Konteyner gerçekten istek karşılıyor mu (alpine'de curl yok, Node 20'nin # global fetch'i kullanılır). Uç bilerek en ucuz olanı: app/api/saglik DB'ye, # oturuma, AI'ya dokunmaz. Süreç OOM ile ölüp yeniden açılırken `docker ps` # durumu "unhealthy" gösterir — 502'lerin kaynağı tahmine kalmaz. HEALTHCHECK --interval=30s --timeout=5s --start-period=45s --retries=3 \ CMD node -e "fetch('http://127.0.0.1:3000/api/saglik').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" CMD ["/app/docker-entrypoint.sh"]