Build Context

Apa yang dikirim ke daemon saat build, dampak ukurannya, dan peran .dockerignore.

Daftar isi

Saat docker build dieksekusi, yang dikirim ke daemon bukan hanya Dockerfile, melainkan seluruh isi direktori yang ditunjuk sebagai build context, yaitu kumpulan berkas dan direktori yang menjadi bahan baku pembangunan image. Dockerfile hanyalah satu berkas di antara isinya. Topik ini membahas apa saja yang ikut terkirim, dampak ukuran context terhadap kecepatan build, dan cara .dockerignore menyaring isinya sebelum pengiriman. Proyek aplikasi catatan Python menjadi contohnya, dengan Dockerfile dan app.py sebagai berkas proyeknya.

Context yang dikirim ke daemon#

Perintah docker build mengemas isi build context, mengirimkannya ke daemon, dan daemon mengerjakan build berdasarkan paket tersebut. Daemon tidak membaca sistem berkas host, komputer tempat Docker berjalan, secara langsung selama build, sehingga satu-satunya bahan yang dapat dijangkau instruksi Dockerfile adalah isi context. Konsekuensinya, COPY hanya dapat menyalin berkas yang berada di dalam context, sedangkan jalur di luar context tidak akan pernah dapat disalin. Inilah alasan Dockerfile, kode aplikasi, dan berkas pendukungnya perlu berada pada satu direktori proyek.

Argumen posisi pada docker build menetapkan context, sedangkan opsi -f memilih Dockerfile yang berbeda dari nama bakunya. Keduanya dapat dipisah, misalnya docker build -f Dockerfile.prod . tetap mengirim isi direktori saat ini sebagai context, walau Dockerfile yang dibaca bernama lain. Untuk proyek dengan beberapa Dockerfile, Docker juga menerima ignore-file khusus yang diletakkan di samping Dockerfile. Berkas ini diberi akhiran nama Dockerfile tersebut, misalnya Dockerfile.prod.dockerignore, sehingga tiap Dockerfile dapat menyaring context dengan caranya sendiri.

bash
# tidak-dijalankan: daemon Docker tidak tersedia di sandbox
# opsi -f memilih Dockerfile lain, sedangkan titik menetapkan context
docker build -f Dockerfile.prod .

Dampak ukuran context#

Seluruh context dipaketkan dan dikirim sebelum instruksi pertama dieksekusi, termasuk berkas yang tidak pernah disalin ke dalam image. Direktori yang lazim membesar tanpa terasa adalah venv/ yang memuat paket Python, .git yang menyimpan seluruh riwayat repositori, dan berkas log. Semakin besar context, semakin lama setiap build menunggu pengiriman, dan kerugiannya berulang pada setiap pembangunan ulang image. Context yang besar juga menambah peluang kesalahan, karena berkas sensitif seperti .env yang berisi kredensial ikut terkirim dan berisiko tersalin oleh instruksi COPY yang luas.

Menyaring context dengan .dockerignore#

Berkas .dockerignore diletakkan di akar context dan berisi pola, satu pola per baris, untuk berkas dan direktori yang tidak boleh ikut terkirim. Baris yang diawali tanda pagar dianggap komentar. Pola dicocokkan terhadap jalur relatif dari akar context. Tanda bintang tunggal tidak melintasi pemisah direktori, sedangkan tanda bintang ganda mewakili sejumlah direktori apa pun. Berkas berikut adalah .dockerignore untuk proyek aplikasi catatan.

text .dockerignore
# Kontrol versi
.git
.gitignore

# Artefak Python
**/__pycache__/
*.pyc
venv/

# Berkas lingkungan dan log lokal
.env
*.log

Pola .git dan .gitignore mengeluarkan direktori kontrol versi beserta seluruh isinya, karena pola yang mengenai direktori otomatis mengenai isi di dalamnya. Pola **/__pycache__/ mengeluarkan direktori __pycache__ di kedalaman mana pun, sedangkan venv/ mengeluarkan direktori venv pada akar context. Pola *.log hanya mengenai berkas log pada akar context, karena tanda bintang tunggal tidak melintasi pemisah direktori. Pola yang diawali tanda seru (!) dapat digunakan untuk mengembalikan berkas yang sudah dikeluarkan oleh pola sebelumnya.

Skrip berikut meniru pencocokan pola tersebut terhadap beberapa jalur contoh, sehingga pola dapat diuji tanpa menjalankan build sungguhan.

python demo-dockerignore.py
"""Meniru pencocokan pola .dockerignore ala Docker terhadap jalur contoh."""
import re

# Pola pada berkas .dockerignore, satu pola per baris (baris komentar dilewati)
POLA = [
    ".git",
    ".gitignore",
    "**/__pycache__/",
    "*.pyc",
    "venv/",
    ".env",
    "*.log",
]

# Jalur contoh pada build context
JALUR = [
    "app.py",
    "Dockerfile",
    ".env",
    ".git/config",
    ".gitignore",
    "venv/lib/python3.12/site-packages/pip.py",
    "modul/__pycache__/util.cpython-312.pyc",
    "server.log",
    "README.md",
]


def terjemahkan(pola: str) -> str:
    """Menerjemahkan pola glob menjadi regex: ** lintas direktori, * tidak."""
    pola = pola.strip().strip("/")
    hasil = ""
    i = 0
    while i < len(pola):
        if pola.startswith("**/", i):
            hasil += "(?:[^/]+/)*"
            i += 3
        elif pola.startswith("**", i):
            hasil += ".*"
            i += 2
        elif pola[i] == "*":
            hasil += "[^/]*"
            i += 1
        elif pola[i] == "?":
            hasil += "[^/]"
            i += 1
        else:
            hasil += re.escape(pola[i])
            i += 1
    return hasil


REGEX = [re.compile(f"^{terjemahkan(pola)}$") for pola in POLA]


def diabaikan(jalur: str) -> bool:
    """Mengembalikan True bila jalur atau salah satu induknya cocok."""
    bagian = jalur.split("/")
    kandidat = {"/".join(bagian[:i]) for i in range(1, len(bagian) + 1)}
    return any(rx.match(k) for rx in REGEX for k in kandidat)


for jalur in JALUR:
    tanda = "DIABAIKAN" if diabaikan(jalur) else "terkirim"
    print(f"{tanda:<9}  {jalur}")
text Keluaran
terkirim   app.py
terkirim   Dockerfile
DIABAIKAN  .env
DIABAIKAN  .git/config
DIABAIKAN  .gitignore
DIABAIKAN  venv/lib/python3.12/site-packages/pip.py
DIABAIKAN  modul/__pycache__/util.cpython-312.pyc
DIABAIKAN  server.log
terkirim   README.md

Jalur proyek utama lolos, sedangkan .env, isi .git, berkas pada venv, artefak __pycache__, dan log tersaring. Jalur .git/config tersaring melalui induk direktorinya, demikian pula util.cpython-312.pyc yang tertangkap oleh pola **/__pycache__/, bukan oleh *.pyc yang hanya mengenai akar context. Pencocokan pada builder, komponen yang mengeksekusi build, berperilaku sama untuk pola-pola di atas.

Kesalahan umum#

  • COPY gagal dengan pesan bahwa berkas tidak ditemukan, padahal berkasnya ada di komputer. Penyebabnya adalah berkas tersebut berada di luar build context, misalnya satu tingkat di atas direktori proyek, dan instruksi COPY ../kunci.pem . memang tidak diizinkan keluar dari context. Perbaikannya adalah memindahkan berkas ke dalam direktori proyek atau memilih direktori induk yang tepat sebagai context.
  • Build terasa lambat sebelum instruksi pertama berjalan, dengan baris status yang menunjukkan pengiriman context berukuran ratusan megabita. Penyebabnya lazimnya direktori venv/ dan .git yang belum disaring. Perbaikannya adalah menambahkan pola keduanya pada .dockerignore, lalu memeriksa ulang ukuran pengirimannya pada build berikutnya.
  • Berkas rahasia masih lolos ke image meski .env sudah dicantumkan pada .dockerignore. Penyebabnya biasanya pola yang salah, misalnya pola env tanpa tanda titik atau berkas rahasia yang sebenarnya bernama lain seperti kredensial-lokal.json. Pencocokan pola pada skrip di atas dapat dipakai untuk menguji pola terhadap nama berkas yang sebenarnya sebelum build dijalankan.

Ringkasan#

  • docker build mengirim seluruh isi direktori context ke daemon, dan instruksi Dockerfile hanya dapat mengakses berkas dari context tersebut.
  • Context yang besar memperlambat setiap build dan membuka risiko berkas sensitif ikut tersalin ke dalam image.
  • .dockerignore di akar context menyaring isi dengan pola per baris, dan tanda bintang ganda mengenai direktori di kedalaman mana pun.

Referensi#