Image yang dibangun dari satu Dockerfile biasa membawa seluruh toolchain, kumpulan alat pembangun seperti kompiler dan pustaka pengembang, ke dalam image produksi, padahal alat itu tidak pernah dijalankan oleh container, instans berjalan di atas image. Build multi-stage, pola Dockerfile dengan beberapa tahap, memisahkan tempat kode dikompilasi dari tempat aplikasi dijalankan, sehingga tahap akhir hanya membawa artefak, berkas hasil akhir yang benar-benar dibutuhkan aplikasi saat berjalan. Topik ini membahas mekanisme dua tahap, contoh dengan bahasa C yang tahap build-nya dieksekusi sungguhan, serta Dockerfile dua tahap lengkapnya.
Konsep dua tahap#
Sebuah Dockerfile multi-stage memuat lebih dari satu instruksi FROM, dan setiap FROM membuka satu tahap (stage) dengan image dasar serta sistem berkasnya sendiri. Tahap-tahap sebelum FROM terakhir hanya ada selama proses build, pembangunan image, sedangkan instruksi FROM terakhir menetapkan image final yang disimpan dan didistribusikan. Berkas dari satu tahap ke tahap lain dipindahkan secara eksplisit melalui COPY --from, yaitu varian COPY yang menyalin dari tahap lain alih-alih dari build context, kumpulan berkas yang dikirim saat build. Segala sesuatu yang tidak disalin dengan cara itu tertinggal di tahap asalnya dan tidak pernah masuk image final.
Isi kedua tahap pada contoh topik ini dapat diringkas sebagai berikut.
| Tahap | Image dasar | Isi | Peran |
|---|---|---|---|
| build | gcc:14 | toolchain gcc, berkas salam.c, biner hasil kompilasi | mengompilasi kode sumber menjadi biner |
| final | alpine:3.20 | biner salam saja | menjalankan program saat container berjalan |
Pemisahan ini menghasilkan tiga manfaat sekaligus. Image final jauh lebih kecil karena tidak membawa toolchain dan cache build. Permukaan serangan menyusut karena kompiler, berkas sumber, dan paket pengembang tidak ikut ke produksi, sehingga alat yang dapat disalahgunakan bila container terkompromi menjadi sedikit. Distribusi pun lebih murah karena byte yang dikirim ke registry, gudang image yang melayani unduhan dan unggahan, lebih sedikit.
Diagram berikut memperlihatkan alur dari tahap build ke tahap final.
Contoh dengan bahasa C#
Program berikut berupa satu berkas C kecil yang mencetak satu baris. Ukurannya sengaja kecil agar mekanisme dua tahap terlihat tanpa distraksi, tetapi polanya sama untuk proyek C apa pun yang dibangun kompiler. Programnya tidak memakai pustaka pihak ketiga sama sekali, sehingga satu instruksi kompilasi sudah cukup.
#include <stdio.h>
int main(void)
{
printf("Salam dari tahap build!\n");
return 0;
}Saat dikompilasi lalu dijalankan, program itu mencetak satu baris.
Salam dari tahap build!Rangkaian berikut mengompilasi program, menjalankan hasilnya, lalu menampilkan berkas binernya, persis pekerjaan yang akan dikerjakan tahap build. Opsi -O2 menyalakan optimasi kompiler, sedangkan touch -r menyamakan waktu berkas biner dengan salam.c agar keluaran contoh tetap stabil.
gcc -O2 -o salam salam.c && ./salam
touch -r salam.c salam
ls -la salamSalam dari tahap build!
-rwxrwxr-x 1 z z 15952 Nov 2 14:33 salamBaris pertama keluaran tersebut berasal dari program yang baru dikompilasi, sedangkan baris keduanya memperlihatkan berkas biner bernama salam berukuran 15952 byte, sekitar 16 KB. Berkas biner inilah satu-satunya artefak yang perlu dibawa ke tahap final, sedangkan gcc beserta seluruh pustaka pengembangnya tidak ikut. Dengan demikian, ukuran image final mendekati ukuran image dasar alpine-nya ditambah biner segitu.
Dockerfile dua tahap utuh#
Dockerfile lengkapnya terlihat seperti berikut, dengan salam.c berada di direktori yang sama.
# Tahap build: seluruh toolchain kompiler hanya hidup di sini.
FROM gcc:14 AS build
WORKDIR /sumber
COPY salam.c .
RUN gcc -O2 -o salam salam.c
# Tahap final: hanya artefak biner yang dibawa ke produksi.
FROM alpine:3.20
WORKDIR /app
COPY --from=build /sumber/salam /app/salam
CMD ["/app/salam"]Tahap pertama dimulai dari FROM gcc:14 AS build. Nama setelah AS memberi label build pada tahap tersebut, dan label inilah yang dirujuk oleh COPY --from=build pada tahap akhir. Instruksi RUN di tahap build menjalankan kompilasi yang sudah diuji di atas. Tahap kedua dimulai dari FROM alpine:3.20, image dasar minimal berbasis Alpine, lalu menyalin biner /sumber/salam dari tahap build ke /app/salam. CMD memakai bentuk exec sehingga program dijalankan langsung tanpa perantara shell.
Saat Dockerfile ini dibangun, image final hanya berisi alpine dan biner, sedangkan salam.c, gcc, dan hasil antara lainnya tertinggal di tahap build.
# tidak-dijalankan: daemon Docker tidak tersedia di sandbox
# membangun image dua tahap lalu menjalankan container-nya
docker build -t salam .
docker run --rm salamPerintah di atas menghasilkan image bernama salam dari Dockerfile di direktori saat ini, kemudian menjalankan container darinya. Container tersebut mencetak baris yang sama dengan percobaan gcc di atas. Bedanya, baris itu dicetak dari image final yang tidak memuat toolchain, berkas sumber, maupun cache kompilasi.
Nama tahap dan penyalinan antartahap#
COPY --from menerima nama tahap atau nomor urutnya, tetapi nama yang diberikan lewat AS lebih tahan perubahan, karena nomor berpindah setiap tahap disisipkan atau dihapus. Sebuah Dockerfile boleh memuat lebih dari dua tahap, misalnya tahap pemasangan dependensi, tahap pengujian, dan tahap produksi, dan antartahap itu bebas saling menyalin. Builder, komponen yang mengeksekusi build, hanya membangun tahap yang dibutuhkan tahap akhir, sedangkan tahap yang tidak dirujuk tidak dieksekusi. Opsi --target pada docker build memilih tahap lain sebagai hasil akhir, misalnya tahap pengujian yang masih membawa alat uji, sehingga satu Dockerfile dapat melayani beberapa keperluan.
Untuk aplikasi catatan Python pada jalur ini, pola yang sama berlaku, yaitu tahap build memasang dependensi dari requirements.txt, sedangkan tahap final berbasis python:3.12-slim hanya menyalin direktori aplikasi. Pada aplikasi pustaka standar yang tidak berdependensi, tahap tambahan belum diperlukan, tetapi pola itu berguna begitu dependensi pihak ketiga masuk. Tahap build yang dipakai pun boleh berbasis varian penuh, sedangkan tahap final tetap berbasis slim, sehingga toolchain tidak ikut ke produksi.
Kesalahan umum#
- Menyalin seluruh direktori build ke tahap final, misalnya
COPY --from=build /sumber /app. Berkas sumber, hasil antara, dan cache ikut terbawa. Perbaikannya, salin artefaknya saja, yaitu/sumber/salam. - Menjadikan image toolchain sebagai dasar tahap final, misalnya
FROM gcc:14tanpa tahap kedua. Toolchain penuh kembali membengkakkan image. Perbaikannya, tahap final memakai image minimal dan hanya menerima artefak. - Mengasumsikan berkas selain artefak otomatis ada di tahap final. Berkas
salam.cdan berkas konfigurasi build hanya ada di tahap yang menyalinnya. Perbaikannya, tulisCOPYeksplisit untuk setiap berkas yang benar-benar dibutuhkan saat berjalan.
Ringkasan#
- Dockerfile multi-stage memuat beberapa FROM, satu tahap per FROM, dan hanya tahap terakhir yang menjadi image final.
COPY --frommemindahkan artefak antartahap, sehingga toolchain, berkas sumber, dan cache build tertinggal di tahap build.- Image final yang hanya memuat artefak mengecilkan ukuran unduhan dan menyusutkan permukaan serangan produksi.
- Opsi
--targetmemilih tahap selain tahap akhir sebagai hasil build, misalnya tahap pengujian yang masih membawa alat uji.
Referensi#
- Multi-stage builds — Docker Docs — Menjelaskan pola beberapa FROM, penamaan tahap dengan AS, dan penyalinan dengan COPY –from.
- Dockerfile reference — Docker Docs — Menetapkan sintaks FROM, COPY –from, RUN, dan CMD yang dipakai contoh.