8 Ağustos 2026'da codebiy.com'un baştan yazdığımız sürümünü merge ettik ve 15:26'da push ettik. Site, Next.js 16 ile yazılmış bir uygulama. main'e giden her push'tan sonra deploy aracımız onu sunucuda Docker image'ı olarak build ediyor. Canlı sitede ise hâlâ eski tasarım duruyordu. Arkasından giden iki push da bir şey değiştirmedi: sunucu yine dört hafta önce oluşturulmuş container'dan yanıt veriyordu.
Üç build de npm ci adımında başarısız olmuştu. Lockfile sağlamdı. Dosyayı kendi makinemizde npm 12 yazmıştı. Image'da, yani node:22-slim'de ise npm 10.9.8 vardı. Bu sürüm, dosyada olmayan bir paket istiyordu. Ziyaretçiler yine eski siteyi gördü, çünkü build başarısız olunca eski container ayakta kalır. Düzeltmeyi ilk push'tan 47 dakika sonra push ettik: image artık npm ci'den önce npm 12 kuruyordu ve yeni site o build'le canlıya çıktı. Hata o gün ortaya çıktı, çünkü yeniden yazımda npm ci'yi geri getirmiştik: ondan önceki sekiz ay boyunca image, kurulumu lockfile'ı okumadan yapmıştı.
Kasım 2025: lockfile'ı image'dan çıkaran düzeltme
25 Kasım 2025'te sitenin Docker build'i bozuldu. Image node:20-alpine idi ve kurulumu npm ci ile yapıyordu. O sabah bağımlılıkları bun ile yükseltmiştik. Yükseltme sırasında package.json ile bun.lock yeniden yazıldı, package-lock.json ise eski sürümlerde kaldı. Lockfile package.json ile uyuşmadığında npm ci hata verip çıkar.
Build'i bir yapay zekâ agent'ıyla düzelttik. Agent 12:06 ile 12:19 arasında beş düzeltme yaptı:
- 12:06:
package-lock.jsonyeniden üretildi. - 12:09: builder aşamasına
npm rebuildeklendi. - 12:13: 12:10'da silinen lockfile'ın yerine 812 satırlık yeni bir
package-lock.jsoneklendi. - 12:16:
npm ciyerinde kaldı, arkasınanpm install --no-save lightningcss-linux-x64-musl || truegeldi. - 12:19: lockfile artık image'a kopyalanmıyor, kurulumu düz
npm installyapıyor.
İlk düzeltme uyuşmazlığı giderdi, ama öbür dördünün çözmeye uğraştığı sorunu da o çıkardı. Site Tailwind CSS v4 kullanıyor. Tailwind'in araç zinciri native binary'lerle geliyor: lightningcss ve @tailwindcss/oxide, her platform için ayrı bir isteğe bağlı paket olarak dağıtılıyor.
Eski lockfile'da birincinin on, ikincinin on iki build'i yazılıydı. Yeniden üretilen dosyada ikisinden de tek build kaldı, Mac'inki (darwin-arm64). Alpine'a ise musl build'leri gerekiyor.
Bu, npm/cli'de 2022'de açılan 4828 numaralı issue ile örtüşüyor: node_modules yerindeyken yeniden üretilen lockfile'da yalnızca üretildiği platformun isteğe bağlı paketleri kalıyor. Bu issue'yu npm, Nisan 2025'te 11.3.0 sürümündeki bir düzeltmeyle kapattı. Aynı davranışı o günden sonra da bildirenler var. Bizim dosyayı hangi npm sürümünün yeniden ürettiğini kaydetmedik. Çıkış yolu, node_modules ile lockfile'ı birlikte silip yeniden kurmak.
Beşinci düzeltme başka yoldan gitti:
# Copy only package.json (not lockfile) to ensure platform-specific binaries are resolved
COPY package.json ./
# Fresh install to get correct native binaries for Alpine Linux (musl)
# This is necessary because Tailwind CSS v4 uses multiple native packages
# (lightningcss, @tailwindcss/oxide) that require platform-specific binaries
RUN npm install
Hata bir daha çıkmadı, ama image da artık aynı image değildi. O commit'ten sonra canlıya giden her build'de sürümler, package.json içindeki aralıklara göre baştan belirlendi. Canlıda neyin çalışacağına artık repo'daki lockfile karar vermiyordu. 12:22'de, musl olanlar da dâhil bütün build'leri yeniden içeren bir lockfile commit ettik, ama image onu okumayı üç dakika önce bırakmıştı.
Satırın üstündeki yorumda değişikliğin neden gerektiği yazıyor. Karşılığında nelerden vazgeçildiği yazmıyor, biz de sormadık. Kurulum adımı sekiz ayı aşkın bir süre olduğu gibi kaldı. Bu sürede fark ettiğimiz bir zararı olmadı, o kadar uzun sürmesinin sebebi de bu. O aylarda canlıda hangi bağımlılık sürümlerinin çalıştığını gösteren bir kaydımız da yok.
Ağustos 2026: Debian base image ve yeniden npm ci
Ağustos 2026'da siteyi baştan yazdık, image'ı da onunla birlikte. Base image node:22-slim oldu, yani Alpine yerine Debian. Kurulum aşaması package.json ile package-lock.json dosyalarını kopyalayıp npm ci çalıştırıyordu. Sekiz ay aradan sonra, canlıya neyin kurulacağına ilk kez lockfile karar verdi.
Alpine'ı bıraktık, çünkü bizi npm install'a onun mecbur ettiğini sanıyorduk. Bu yazı için lockfile'ın geçmişine yeniden bakınca, bu düşüncenin o kasım günü 12:22'den sonra dayanağı kalmadığını gördük: dosyada o andan beri musl build'leri de var, yani npm ci Alpine'da da çalışmalıydı. Bunu test etmedik.
Yanlış ilk teşhis: build'i değil deploy'u suçladık
İkinci push 15:48'de gitti, canlı sitede yine eski tasarım vardı.
Önce sunucuya baktık. docker ps çıktısına göre sitenin container'ı dört hafta önce oluşturulmuştu ve o günden beri ona dokunulmamıştı. Aynı mekanizmayla deploy edilen başka bir sitemiz ise kendi push'undan dakikalar sonra yenilenmişti. Buradan build'in başarısız olmadığı sonucunu çıkardık: ya deploy hiç başlamıyordu ya da yeni container eskisinin yerini alamıyordu. İki compose dosyasını karşılaştırdık ve yeniden oluşturmayı engelleyebilecek iki farkı kaldırdık:
container_name, container'ı sabit bir ada bağlıyordu. Deploy aracımız compose container'larını kendisi adlandırıyor. Yeniden oluşturulan container da, çalışan bir container'ın hâlâ kullandığı adı alamaz.devprofiline bağlı ikinci bir servis vardı.node:20-alpineüzerindenpm ciçalıştıran birDockerfile.devdosyasından build ediliyordu. Tek deploy yolu olan bir repo'da ikinci bir build yoluydu.
Sebebin yalnızca bu olmadığından şüpheleniyorduk. Oysa sebep hiç bu değildi: üçüncü push 15:59'da gitti, yine hiçbir şey değişmedi.
Sebep: npm 12'nin yazdığı lockfile'ı npm 10 okuyordu
Sonra sunucudaki build log'unu okuduk. İşe oradan başlamalıydık. O günkü bütün deploy'lar aynı hatada durmuştu. Log'un elimizde yalnızca kısaltılmış bir kopyası kaldı. Aşağıdaki satırları, 9 Ekim 2026'da npm 10.9.8'i aynı package.json ve lockfile üzerinde çalıştırarak aldık. Uzun satırı böldük, npm'in ardından bastığı kullanım yardımını çıkardık:
npm error code EUSAGE
npm error
npm error `npm ci` can only install packages when your package.json and
package-lock.json or npm-shrinkwrap.json are in sync. Please update your
lock file with `npm install` before continuing.
npm error
npm error Missing: @swc/helpers@0.5.23 from lock file
Dokunulmamış bir container, build başarısız olduğunda da, deploy hiç başlamadığında da, yeni container eskisinin yerini alamadığında da aynı görünür. Biz ilkini elemiştik, doğrusu ilkiydi.
Mesaja göre iki dosya uyumsuz. Kasım 2025'te bu doğruydu, bu sefer değildi. lockfileVersion değeri 3 olan lockfile'da @swc/helpers tek bir yerde, 0.5.15 sürümüyle yer alıyordu. Bu, kullandığımız Next.js sürümünün bağımlı olduğu sürümün ta kendisi. Bağımlılık ağacında bir de @swc/core 1.15.3 vardı. Onu next-intl getiriyor. @swc/core ise @swc/helpers >=0.5.17 için isteğe bağlı bir peer dependency tanımlıyor. 0.5.15 bu aralığa girmiyor.
Fark, dosyayı okuyan araçtaydı. npm 12, lockfile'ı yazarken bu isteğe bağlı peer'ı karşılamadan bırakmıştı. Kendi npm ci'si dosyayı bu hâliyle kabul ediyor. npm 10.9.8 ise @swc/helpers'ın aralığa giren ikinci bir kopyasını istedi, lockfile'da bulamadı ve durdu, çünkü npm ci lockfile'a hiçbir şey eklemez. npm'in issue listesindeki açık bir kayıtta aynı durum başka bir paketle anlatılıyor: npm 11 ve 12 böyle bir lockfile'ı npm ci'de kabul ediyor, npm 10 ise Missing: satırıyla hata veriyor. Bundan ötesi için npm'in içine bakmadık. Push'tan önceki kontrolümüz npm ci'yi yerelde çalıştırmaktı. Geçti, ama npm 12 ile.
Aynı lockfile'ı npm 12 kabul ediyor, npm 10 reddediyor.
Düzeltme, kurulum aşamasında tek satır:
FROM base AS deps
# node:22-slim ships npm 10, but this lockfile was written by npm 12, and the
# two resolve the tree differently — npm 10 reports transitive packages as
# "missing from lock file" and `npm ci` refuses. Pinning the image to the same
# major that produced the lockfile is what makes the install reproducible.
RUN npm install -g npm@12
COPY package.json package-lock.json ./
RUN npm ci
Bu satırı ilk push'tan 47 dakika sonra push ettik. Build geçti ve yeni site canlıya çıktı.
Mesajın sonunda npm'in verdiği öneri bu durumda yanlış yöne götürüyor. Lockfile, onu yazan npm'e göre zaten uyumluydu. Öneriyi Dockerfile'da uygulamak, kasımdaki düzeltmenin aynısı olurdu: npm ci yerine npm install yazılır ve hata kaybolur, çünkü image artık lockfile'a bağlı değildir.
Build argümanı olarak NEXT_PUBLIC_ değişkenleri
Aynı öğleden sonra Dockerfile'da, build sırasında sabitlenen değerler için bir düzeltme daha yaptık. Next.js, NEXT_PUBLIC_ değişkenlerini build sırasında JavaScript bundle'ına gömer. Build argümanı olarak yalnızca analitik ID'si tanımlıydı. Yeni sitenin ihtiyaç duyduğu iki reklam alanı ID'si tanımlı değildi. Bunları çalışan container'da ayarlamak hiçbir şeyi değiştirmezdi: bundle'da çoktan undefined yazıyor olurdu, reklam bileşeni de hiçbir şey render etmezdi. Bunu, zarara yol açmadan yakaladık. Her değer artık builder aşamasında hem ARG hem ENV olarak tanımlı ve compose dosyasındaki build args üzerinden geliyor:
ARG NEXT_PUBLIC_ADSENSE_SLOT_FEED
ENV NEXT_PUBLIC_ADSENSE_SLOT_FEED=$NEXT_PUBLIC_ADSENSE_SLOT_FEED
# ... the same pair for the article slot and the analytics id
RUN npm run build
Reklam alanlarının bu ID'lerle ne yaptığını AdSense, çerez izni ve sitenin CSP'si üzerine yazımızda anlattık.
Değiştirdiklerimiz ve hâlâ alışkanlığa kalanlar
Üç şey değişti, bunları artık kimsenin hatırlaması gerekmiyor:
- Image,
npm ci'den önce npm 12 kuruyor. Canlıda lockfile'ı hangi npm'in okuyacağı artık base image'a bağlı değil. - Sitenin tek lockfile'ı var. Deploy'un kullanmadığı bir paket yöneticisine ait 1.010 satırlık
bun.lock, 8 Ağustos 2026'da hâlâ git'te duruyordu. Kasım 2025'tepackage-lock.jsononun yüzünden geride kalmıştı. Dosyayı sildik ve.gitignore'a ekledik. Böylece repo'ya girmiyor, ama makineden gitmiş değil: 9 Ekim 2026'da çalışma kopyasında, git'in yok saydığı, 28 Eylül tarihli birbun.lockyine duruyordu. - Tek Dockerfile, tek build yolu var.
Dockerfile.devve compose'daki servisi kalktı.
Gerisi hâlâ alışkanlığa kalıyor:
- Kendi makinemizdeki npm. Sürümünü sabitleyen bir şey yok:
package.jsoniçinde neenginesne dedevEnginesvar. 9 Ekim 2026'da, siteyi geliştirdiğimiz Mac'te npm 11 vardı, yani image'dakinden bir major sürüm gerideydi. Onu altı hafta önce Homebrew, Node.js ile birlikte kurmuştu.install,civerunkomutlarından önce npm,devEngines.packageManageralanına bakıyor. Bu alan alışkanlığı kontrole çevirirdi. Henüz eklemedik. - Başarısız build'i fark etmek. Build'in başarısız olduğu, sunucuda deploy aracımızın build log'unda görünüyor. Push yine başarılı oluyor, eski container da yanıt veriyor. Dışarıdan bakınca bir sorun görünmüyor. Şimdi yaptığımız şu: push'tan sonra canlı sayfayı açıp değişikliği arıyoruz, container listesinden önce de build log'unu okuyoruz.
- Image'ın araçlarıyla kontrol etmek. Yerelde geçen
npm ci, yalnızca o makinedeki npm'in neyi kabul ettiğini gösterir. Ona güvenmeden önce image'ı build edin ya da en azından image'daki npm'in major sürümüyle çalıştırın. Bütün kontrollerin geçtiği ama sayfanın yanlış olduğu başka örnekleri, yapay zekâ agent'larının yazdığı front-end işini kontrol etmek üzerine yazımızda anlattık. - Düzeltmenin neyi yapmayı bıraktığını sormak. Kasım 2025'teki düzeltmeleri bir agent on üç dakikada yazdı ve her biri, önündeki hataya verilmiş yerinde bir yanıttı. Eksik olan soruyu sormak bize düşüyordu: build artık geçiyor, peki bunun için nelerden vazgeçtik?