Kita sudah membuktikan bahwa 148.21MB context myapp terkirim utuh pada setiap build — termasuk saat seluruh langkah dilayani cache — dan bahwa node_modules 146MB adalah penumpang yang paling gemuk di kiriman itu. Kabar baiknya, Docker menyediakan mekanisme penyaringan yang mirip dengan .gitignore di dunia Git: satu berkas teks kecil bernama .dockerignore di akar context, berisi pola berkas yang boleh dilewati saat context ditarsi. Chapter ini membedah aturan penempatannya yang sering disalahpahami, bahasa pola glob yang dipakainya, contoh lengkap untuk proyek Node milik Budi, sampai tiga dampak yang langsung terasa setelah berkas itu dipasang — dari transfer yang menyusut 79 kali lipat sampai rahasia yang nyaris bocor ke dalam image.

.dockerignore Berdiri di Root Konteks #

Aturan penempatan .dockerignore sering ditulis sekilas padahal salah paham di sini mahal harganya: berkas ini harus berada di root context, yaitu folder yang kita tunjuk sebagai argumen terakhir docker build — bukan sekadar root folder proyek. Untuk myapp yang dua-duanya sama, perbedaannya tak terlihat; pada monorepo pola kedua dari Chapter Build Context, perbedaannya menentukan terbacanya penyaring:

Dua jalur penyaringan yang benar tersebut berjalan seperti ini:

  1. <context>/.dockerignore — jalur utama: berlaku untuk seluruh build yang memakai context tersebut, apa pun Dockerfile yang dipakai.
  2. <nama-dockerfile>.dockerignore di samping Dockerfile-nya — jalur alternatif yang didukung buildx: saat kita membangun dengan -f services/api/Dockerfile.prod, BuildKit juga mencari berkas bernama Dockerfile.prod.dockerignore di folder yang sama; bila ditemukan, berkas itu dipakai dan menggantikan .dockerignore di akar context. Pola ini memungkinkan tiap varian Dockerfile membawa penyaringnya sendiri.

Perhatikan juga baris load .dockerignore yang terus tampil pada output BuildKit sejak chapter pertama section ini — kini maknanya jelas: builder memang selalu berusaha membaca penyaring itu lebih dulu sebelum menarik context, dan angka transferring context: 2B pada output lama kita adalah tanda bahwa penyaringnya belum ada.

Pola Glob pada .dockerignore #

Isi .dockerignore adalah daftar pola, satu pola per baris, dengan bahasa glob yang kita kenal dari .gitignore. Tabel berikut merangkum seluruh bentuk yang kita pakai di chapter ini:

PolaMaknaContoh yang cocok
namaBerkas atau folder bernama persis, di level mana punnode_modules, dist
docs/Folder docs beserta seluruh isinya; garis miring menegaskan folderdocs/panduan.md
*Semua karakter dalam satu segmen nama*.log cocok dengan debug.log
**Segmen bertingkat apa pun, termasuk kosonglog/** cocok dengan log/a/b.txt
?Tepat satu karakterlog?.txt cocok dengan log1.txt
!polaNegasi — kembalikan berkas yang tadinya tersaring!.env.example
#Komentar; baris yang diawali pagar diabaikan# dependensi

Urutan baris memegang peranan penting: pola dinilai dari atas ke bawah, dan negasi ! hanya bermakna bila muncul setelah pola yang menyaring. Menuliskan !.env.example di atas .env tidak mengembalikan apa pun, karena saat negasi dibaca, berkas itu belum tersaring.

Menyaring myapp #

Saatnya Budi menulis penyaringnya. Kebutuhan myapp sebagai aplikasi Node dirangkum dalam sembilan baris berikut — cukup lengkap untuk dipakai sebagai titik awal proyek Node pada umumnya:

text
node_modules
.git
.env
!.env.example
dist
coverage
*.log
README.md
docs

Mari kita telaah isi penyaring di atas, dikelompokkan menjadi lima:

  1. Dependensi — node_modules. Penumpang 146MB yang memicu seluruh section ini. Folder ini selalu bisa dipasang ulang dari package-lock.json oleh npm ci, sehingga tidak ada alasannya ikut terkirim atau tersalin.
  2. Riwayat — .git. Seluruh sejarah repositori hanya berguna bagi Git, bukan bagi image; ukurannya sekitar 2MB di myapp dan bisa jauh lebih besar pada proyek tua.
  3. Rahasia — .env dan negasi !.env.example. Berkas .env milik Budi menyimpan kredensial lokal seperti DATABASE_PASSWORD=rahasia123; ia tidak boleh terkirim ke builder sedetik pun. Baris negasi di bawahnya mengembalikan .env.example — templat variabel yang justru harus terlihat rekan-rekan timnya — sehingga templat itu tetap ikut context. Inilah pasangan saring-kecualikan yang membedakan rahasia dari dokumentasinya.
  4. Artefak build dan uji — dist, coverage, *.log. Hasil kompilasi TypeScript, laporan cakupan pengujian, dan log lokal silih berganti isinya setiap hari; ketiganya tidak dibutuhkan image dan — sebagaimana kita lihat pada Chapter Build Context — justru rajin memicu invalidasi cache COPY . ..
  5. Dokumentasi — README.md dan docs. Bacaan manusia yang tidak pernah dibuka container; mengeluarkannya mengecilkan context sekaligus mencegah suntingan dokumentasi meruntuhkan cache.

Budi menyimpan berkas itu sebagai .dockerignore di akar folder myapp — 80 byte yang akan mengubah angka-angka buildnya drastis.

Rebuild: 148.21MB Menjadi 1.87MB #

Bukti pertama datang seketika pada build berikutnya — perhatikan baris load .dockerignore dan load build context pada output berikut:

bash
$ docker build -t myapp:1.0 .

Output yang Dihasilkan:

text
[+] Building 11.8s (13/13) FINISHED
 => [internal] load build definition from Dockerfile                    0.1s
 => => transferring dockerfile: 176B                                    0.0s
 => [internal] load .dockerignore                                       0.0s
 => => transferring context: 80B                                        0.0s
 => [internal] load metadata for docker.io/library/node:22-alpine       1.2s
 => [auth] node:22-alpine pull token from registry-1.docker.io          0.9s
 => [internal] load build context                                       0.1s
 => => transferring context: 1.87MB                                     0.1s
 => [1/5] FROM docker.io/library/node:22-alpine                         0.0s
 => => resolve docker.io/library/node:22-alpine                         1.2s
 => [2/5] WORKDIR /app                                                  0.0s
 => [3/5] COPY . .                                                      0.1s
 => [4/5] RUN npm ci                                                   9.6s
 => [5/5] EXPOSE 8000                                                   0.0s
 => exporting to image                                                  0.8s
 => => exporting layers                                                 0.6s
 => => writing image sha256:8f4d2c9a61e3b057821d9c4f6e2b5a8d1c7f0439e8a2b6d5c4f7a0e3b1d9c8f4a2 0.1s
 => => naming to docker.io/library/myapp:1.0                            0.0s

Mari kita telaah angka-angka yang berubah pada output di atas:

  1. transferring context: 1.87MB dalam 0.1s. Dari 148.21MB selama 11.9 detik menjadi 1.87MB selama 0,1 detik — penyusutan 79 kali lipat. Isi kiriman kini hanya package.json, package-lock.json sekitar 1.8MB, server.js, Dockerfile, dan .dockerignore itu sendiri.
  2. load .dockerignore kini membawa transferring context: 80B — penyaringnya ada dan ikut terhitung sebagai berkas context; bandingkan dengan 2B pada build-build sebelumnya.
  3. COPY . . dan RUN npm ci dieksekusi ulang, bukan CACHED. Perubahan isi context mengubah checksum COPY . ., dan cache parent yang gugur menyeret seluruh keturunannya — rantai invalidasi yang kita bedah tuntas pada Chapter Build Cache. npm ci 9.6 detik harus dibayar satu kali lagi untuk melahirkan layer baru yang ramping.
  4. writing image sha256:8f4d2c9a... — ID image berubah karena susunan layernya kini berbeda: layer COPY . . tidak lagi memuat salinan node_modules; satu-satunya salinan dependensi kini hidup di layer npm ci. Image yang tadinya 424MB karena dependensi tercatat dua kali kini menyusut ke kisaran 278MB — bonus di luar transfer yang lebih cepat.
  5. Build berikutnya akan kembali CACHED penuh — context yang mungil dan stabil membuat checksum COPY . . awet, jadi eksekusi npm ci sekali ini adalah investasi terakhir yang mahal.

Tiga Dampak yang Langsung Terasa #

Satu berkas 80 byte memberi tiga dampak sekaligus, dan yang ketiga sering baru disadari setelah kejadian:

  1. Transfer dan checksum yang cepat. 11.9 detik pengiriman menjadi 0.1 detik pada setiap build — penghematan yang terasa setiap kali Budi mengetik docker build, dan berlipat pada builder jarak jauh atau CI yang menempuh jaringan sungguhan.
  2. Cache COPY . . yang awet. Suntingan README.md, log lokal, dan pasang-lepas node_modules tidak lagi menyentuh checksum context yang terkirim — invalidasi kini hanya terjadi saat berkas yang benar-benar dibutuhkan image berubah.
  3. Keamanan: rahasia tidak pernah sampai ke builder. Tanpa penyaring, .env berisi rahasia123 ikut terkirim dan tersalin oleh COPY . . ke dalam layer image — dan di sinilah bahayanya terpendam.

.gitignore versus .dockerignore #

Kedua berkas ini berbagi bahasa pola tetapi hidup di dua dunia yang berbeda — dan keduanya tetap kita butuhkan berdampingan:

Aspek.gitignore.dockerignore
Dibaca olehGitBuilder BuildKit saat docker build
Kapan dibacaSetiap perintah git add, git status, kloningSetiap pengarsipan build context
EfekBerkas tidak masuk repositori, tetapi tetap ada di diskBerkas tidak masuk context, tidak terkirim, tidak tersalin COPY
Penempatan benarRoot repositoriRoot context (akar folder argumen terakhir docker build)
Tanpa berkas iniSeluruh folder ikut commitSeluruh folder ikut dikirim ke builder

Kesalahan klasiknya: mengira .gitignore sudah melindungi build karena node_modules tidak ada di repositori. Build context diambil dari folder di disk, bukan dari repositori — hasil npm install lokal tetap terkirim lengkap meski Git mengabaikannya. .dockerignore adalah satu-satunya penyaring di sisi builder.

Dockerfile yang Di-ignore Tetap Terbaca #

Satu kejutan terakhir yang perlu dicatat: baris Dockerfile di dalam .dockerignore tidak membuat build gagal. Builder membaca definisi build melalui jalurnya sendiri — langkah load build definition mengambil berkas Dockerfile secara terpisah sebelum context diarsipkan — sehingga Dockerfile tetap bisa dieksekusi walau namanya tersaring. Yang berubah hanyalah satu hal: COPY . . tidak lagi ikut menyalin berkas Dockerfile ke dalam image. Banyak proyek memang sengaja menyaring Dockerfile, compose.yaml, dan berkas CI agar resep buildnya tidak ikut menumpang di image produksi.

Konteks myapp kini ramping dan rahasianya aman, tetapi output rebuild barusan masih menyisakan satu ketidakadilan: npm ci 9.6 detik dieksekusi ulang hanya karena COPY . . — yang berada di atasnya — berubah. Solusinya bukan lagi soal isi context, melainkan soal urutan baris di Dockerfile: taruh yang jarang berubah di atas, yang sering berubah di bawah, dan biarkan cache bekerja untuk kita. Aturan emas itu, beserta mekanisme cache key yang menghidupinya, kita kuasai pada Chapter Build Cache.

Referensi #