Build yang gagal sering terasa seperti ditolak tanpa alasan: BuildKit menyembur lusinan baris, sebuah ERROR: failed to solve: yang panjang menutup layar, lalu kita kembali ke prompt tanpa image. Padahal pesan galat BuildKit sebenarnya terstruktur rapi — pesan itu menyebut instruksi Dockerfile mana yang gagal, exit code perintah di dalamnya, dan jenis masalahnya: berkas yang hilang dari build context, perintah yang gagal dieksekusi, atau cache yang menipu kita. Sore ini tim toko mendapat semuanya sekaligus: build image worker:1.0 (basis python:3.12-slim) tumbang di tengah RUN, sementara build web-backend milik Siti berhenti di COPY. Chapter ini mengajari cara membaca bahasa galat tersebut, lalu memberi tiga teknik isolasi yang memperpendek jalan menuju akar masalah: reproduksi manual di base image, segmentasi Dockerfile, dan pembongkaran build cache — ditutup tabel pola kegagalan yang paling sering muncul beserta obatnya.

Anatomi Pesan Galat BuildKit #

Kegagalan worker:1.0 adalah kasus yang paling sering menimpa pemula — dan juga yang paling mudah diurai:

text
#7 [3/5] RUN apt-get install -y curl
#7 0.412 Reading package lists...
#7 0.638 E: Unable to locate package curl
#7 ERROR: process "/bin/sh -c apt-get install -y curl" did not complete successfully: exit code: 100
------
 > [3/5] RUN apt-get install -y curl:
0.412 Reading package lists...
------
ERROR: failed to solve: process "/bin/sh -c apt-get install -y curl" did not complete successfully: exit code: 100

Mari kita bedah anatomi pesan tersebut baris demi baris:

  1. #7 adalah nomor vertex di graf eksekusi BuildKit — setiap langkah build bernomor, dan seluruh baris log yang diawali #7 berasal dari langkah yang sama.
  2. [3/5] adalah petunjuk paling berharga: instruksi ke-3 dari 5 instruksi build. Buka Dockerfile, hitung instruksi aktif, dan kita langsung tahu baris mana yang gagal — tanpa menebak.
  3. E: Unable to locate package curl adalah kata apt sendiri — index paket di dalam image tidak memuat paket yang diminta; baris berawalan E: itulah stderr perintah di dalam RUN.
  4. exit code: 100 adalah kode keluar apt untuk kegagalan operasi paket — index kosong, GPG kedaluwarsa, konflik lock, atau repository yang sudah tidak tersedia.
  5. ERROR: failed to solve: hanyalah pembungkus BuildKit — diagnosa CLI berhenti di situ, tetapi seluruh bukti yang diperlukan sudah tersaji di baris-baris di atasnya.

Penyebab klasik kasus ini: base image berganti ke varian -slim yang sengaja menghapus index paket agar ramping, sementara RUN apt-get install dijalankan tanpa apt-get update terlebih dahulu. Obatnya sudah kita kenal dari chapter Mengurangi Ukuran Image: gabungkan apt-get update && apt-get install -y curl dalam satu instruksi RUN yang sama — atau simpan index-nya di cache mount seperti praktik chapter Cache Mount. Menggabungkannya penting bukan gaya: dua RUN terpisah membuat apt-get update menjadi layer tersendiri yang ter-cache selamanya, sehingga install berikutnya memakai index basi meski Dockerfile sudah diperbaiki.

Path yang Tidak Ditemukan di Build Context #

Kegagalan kedua menyasar instruksi COPY dan ADD. Wujud modernnya, dari Dockerfile web-backend milik Siti:

text
------
 > [4/5] COPY app/server.js /app/server.js:
------
ERROR: failed to solve: failed to compute cache key: "/app/server.js" not found

Pesan failed to compute cache key terkesan berputar-putar, tetapi intinya satu: berkas app/server.js tidak ditemukan di build context yang dikirim builder — dan COPY hanya boleh mengambil berkas dari kumpulan tersebut, mekanisme yang kita bedah di chapter Build Context. Kasus Siti: berkasnya ada di akar context bernama server.js, sementara Dockerfile menulis app/server.js. Tiga penyebab menempati urutan frekuensi: salah ketik atau salah folder path, perbedaan huruf besar-kecil (Server.js dan server.js adalah dua berkas berbeda bagi Linux), atau berkas justru dikecualikan oleh .dockerignore.

Versi klasiknya berbunyi lebih langsung namun membingungkan karena menyebut path internal daemon:

text
COPY failed: stat /var/lib/docker/tmp/docker-builder381749204/serve.js: no such file or directory

Pesan warisan builder lama itu bermakna sama persis: berkas tidak ada dalam arsip context yang dikirim builder. Diagnosis cepatnya selalu dua langkah — daftar isi context dengan ls, lalu baca .dockerignore.

Reproduksi Manual di Base Image #

Ketika instruksi RUN yang gagal, cara tercepat menuju akar masalah bukan mengedit Dockerfile dan membangun ulang berkali-kali — setiap build mengulang seluruh layer sebelum baris gagal. Lebih efisien masuk langsung ke base image dan menjalankan perintah yang sama secara manual:

bash
$ docker run -it --rm python:3.12-slim sh

Output yang Dihasilkan:

text
# apt-get install -y curl
Reading package lists...
E: Unable to locate package curl

Galat yang sama muncul dalam hitungan detik, tanpa satu layer pun dibangun. Dari shell interaktif tersebut kita bisa mencoba varian perbaikan — menjalankan apt-get update lebih dulu, memeriksa nama paket dengan apt-cache search, menguji argumen perintah — lalu begitu resep yang berhasil ditemukan, tulis kembali ke Dockerfile. Loop reproduksi manual inilah yang mengubah debugging build dari menunggu-menunggu menjadi percakapan bolak-balik yang cepat; pola container sekali pakai --rm yang sama kita pakai sepanjang course.

Melihat Lebih Banyak dengan –progress=plain dan –no-cache #

Output bawaan BuildKit dirancang ringkas untuk terminal interaktif: layer yang diambil dari cache hanya menampilkan CACHED tanpa isinya. Saat penyelidikan butuh mata penuh, dua flag datang menolong — di sini dipakai untuk membangun web-backend yang barusan diperbaiki:

bash
$ docker build --progress=plain --no-cache -t web-backend:1.1 .

Output yang Dihasilkan (dipotong):

text
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 189B done
#2 [internal] load .dockerignore
#2 transferring context: 29B done
#3 [internal] load metadata for docker.io/library/node:22-alpine
#4 [1/5] FROM docker.io/library/node:22-alpine
#4 DONE 0.0s
#5 [2/5] WORKDIR /app
#5 DONE 0.0s
#6 [3/5] COPY package*.json ./
#6 DONE 0.1s
#7 [4/5] RUN npm ci --omit=dev
#7 1.204 npm warn deprecated [email protected]: glob versions before 9 are deprecated

Mari kita telaah kedua flag tersebut:

  1. --progress=plain menampilkan keluaran mentah setiap langkah tanpa animasi TTY — termasuk baris berprefix #7 1.204 yang memuat timestamp relatif tiap keluaran perintah, sehingga kita bisa tahu perintah mana yang lambat dan keluaran mana yang muncul kapan.
  2. --no-cache memaksa seluruh layer dieksekusi ulang dari nol — perhatikan absennya kata CACHED pada seluruh langkah. Flag ini adalah pembeda antara kegagalan yang sesungguhnya dan keanehan yang diciptakan cache basi; bila kecurigaan jatuh pada cache, satu build tanpa cache adalah pembuktian tercepat, dengan ongkos waktu build penuh.

Segmentasi Dockerfile untuk Isolasi #

Bila galat tetap samar setelah langkah-langkah di atas, teknik berikutnya memangkas wilayah pencarian dengan segmentasi: komentari separuh instruksi yang dicurigai, bangun ulang, lalu amati apakah kegagalan berpindah atau hilang:

dockerfile
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
# USER node
# EXPOSE 3000
# CMD ["node", "server.js"]

Bila build berhasil dengan bentuk terpotong ini, kegagalan pasti berada pada salah satu baris yang dikomentari — aktifkan kembali separuhnya, bangun lagi, dan ulangi sampai satu baris tersangka tunggal tersisa. Teknik bagi-dua ini adalah bisect — prinsip yang akan kita formalkan pada chapter Strategi Diagnostik — diterapkan pada ruang instruksi Dockerfile. Lima instruksi aktif pada potongan di atas juga menjelaskan angka [3/5] dan [4/5] yang kita lihat pada anatomi galat di awal chapter: penomoran BuildKit selalu menghitung instruksi aktif saja.

Membongkar Build Cache #

Build cache punya dua wajah: penyelamat waktu di hari baik, dan penyumbang disk nomor satu di hari buruk — pagi ini kita mengukurnya dengan docker system df pada chapter Debugging Storage dan menyapu 6.9GB dengan docker builder prune. Sore ini, tiga build besar sudah mengisinya kembali — cache memang tumbuh cepat itu. Untuk pemeriksaan yang lebih bedah, buildx menyediakan inventaris per rekaman:

bash
$ docker buildx du

Output yang Dihasilkan (dipotong):

text
ID                                      RECLAIMABLE     SIZE          LAST ACCESSED
p2qxvzj0ehkjkmwy7e0nfw4nx*             true            114.6MB       2 hours ago
8dnq1wkejrqpsgmqzqhn9ik4b*             true            89.2MB        2 hours ago
gx7l2e8vk1wzxdqwwah3lxl0mz             true            512.4MB       5 hours ago
Total:  2.1GB

Tanda bintang pada kolom pertama menandai rekaman yang sedang dipakai builder aktif; kolom RECLAIMABLE menegaskan mana yang aman dihapus lewat docker builder prune. Inventaris ini juga memuat rekaman jenis exec.cachemount — cache dependensi npm/pip dari chapter Cache Mount — dan rekaman basi di dalamnya adalah tersangka ketika build menghasilkan keanehan yang hanya hilang setelah --no-cache: paket terkorup atau index kedaluwarsa bisa menetap di cache mount berhari-hari. Obatnya dua pilihan: bersihkan build cache secara menyeluruh, atau ganti id cache mount (misalnya id=npm-cache menjadi id=npm-cache-v2) supaya rekaman lama tidak pernah dijamah lagi.

Tabel Pola Umum Kegagalan Build #

Sepuluh pola berikut menutup hampir semua kegagalan build yang kita jumpai sehari-hari:

GejalaPenyebab paling umumSolusi
failed to compute cache key: "X" not foundBerkas tidak ada di build context — salah ketik atau beda huruf besar-kecilPeriksa path relatif akar context; samakan kapitalisasi
COPY failed: stat ... no such file or directoryPenyebab sama — pesan varian builder lamaPeriksa .dockerignore dan lokasi berkas
failed to read dockerfile: open Dockerfile: no such fileDockerfile tidak berada di context atau flag -f salahJalankan build dari direktori yang tepat atau set -f jalur/Dockerfile
exit code: 100 pada RUN apt-get ...Index paket kosong (base -slim), GPG kedaluwarsa, atau repo EOLGabungkan apt-get update && apt-get install dalam satu RUN; ganti base yang masih didukung
exit code: 1 pada RUN npm ciResolusi dependensi gagal atau lock file tak sinkronReproduksi manual di base image; commit package-lock.json
exit code: 127 pada RUNPerintah tidak ditemukan di dalam imagePastikan paket penyedianya terpasang di stage yang sama
pull access denied ... repository does not existFROM menarik image privat tanpa login atau tag kelirudocker login; verifikasi nama dan tag image dasar
no space left on device saat buildDisk host penuh oleh cache dan imagedocker buildx du lalu docker builder prune — chapter Debugging Storage
Build sukses tetapi container menjalankan kode lamaService Compose masih memakai image lamadocker compose up -d --build untuk membangun lalu menukar
Keanehan hilang setelah --no-cacheCache mount menyimpan artefak basi atau terkorupsiBersihkan build cache; ganti id cache mount

Image akhirnya berhasil dibangun — tetapi ceritanya belum selesai. Container yang lahir dari image sempurna bisa saja mati tiga detik kemudian, dibangkitkan daemon, mati lagi, dan berulang tanpa henti: crash loop. Gejala itu hidup di wilayah pengetahuan yang sudah kita kumpulkan sepanjang section ini — exit code, logs, inspect, restart policy — dan chapter Mengatasi Container Crash merangkainya menjadi resep penanganan yang lengkap.

Referensi #