Chapter Packaging Aplikasi mengirimkan program kita sebagai paket .deb atau tarball — tetapi penerima tetap memikul satu risiko: biner C++ terlink dinamis bergantung pada versi libc di mesin pengguna. Bila glibc di mesin pengguna lebih tua daripada glibc di mesin build, program menolak berjalan dengan pesan GLIBC_2.xx not found. Container mengambil jalan lain: aplikasi dan seluruh dependensinya dibungkus menjadi satu image yang berperilaku sama di setiap mesin yang menjalankan Docker. Chapter ini merakit image seperti itu untuk program C++ — mulai dari build lokal, Dockerfile multi-stage, hingga volume untuk data.
Mengapa Container untuk Aplikasi C++ #
Masalah distribusi yang kita hadapi sepanjang section ini bertemu jawabannya pada empat sifat container:
| Masalah distribusi tradisional | Jawaban container |
|---|---|
| Versi glibc berbeda antar distro; biner gagal dimuat | Image runtime membawa libc yang cocok dengan biner |
| Toolchain antar mesin build tidak seragam | Stage build mengunci compiler dan flag-nya di dalam image |
| “Berjalan di mesin saya” sulit direproduksi | Image yang sama menghasilkan perilaku yang sama di mana pun |
| Dependensi menempel pada host | Proses berjalan terisolasi dari library host |
Isolasi inilah nilai terbesarnya bagi C++. Pada chapter Executable dan Runtime Dependencies kita membaca ldd untuk menebak library yang harus mengikuti biner; pada container, pertanyaan itu tidak perlu lagi karena image sudah memuat semuanya. Reproducibility juga naik: semua anggota tim yang membangun dari Dockerfile yang sama memperoleh binary dari compiler dan flag yang sama.
Skrip Build Lokal sebagai Bahan Uji #
Sebelum membungkus ke container, kita kunci dulu cara build yang benar di host. Skrip berikut mengompilasi hitung-ppn.cpp — kalkulator PPN 11% yang logikanya murni — lalu mengujinya dengan satu kasus:
#!/usr/bin/env bash
set -euo pipefail
g++ -std=c++23 -O2 -Wall -Wextra hitung-ppn.cpp -o hitung-ppn
./hitung-ppn 750000 # uji cepat satu kasus setelah buildOutput yang Dihasilkan:
Harga dasar : 750000
PPN 11% : 82500
Total : 832500Mari kita telaah skrip tersebut:
- Baris 2 (
set -euo pipefail): shell berhenti saat ada perintah gagal (-e), menolak variabel yang belum terdefinisi (-u), dan meneruskan kegagalan di dalam pipeline (-o pipefail) — standar defensif untuk skrip build. - Baris 4 (kompilasi):
g++membangun mode release dengan flag yang identik dengan stagebuildpada Dockerfile, sehingga binary host dan binary container lahir dari resep yang sama. - Baris 5 (uji cepat): program langsung dijalankan dengan satu kasus; bila kompilasi atau uji gagal,
set -emenghentikan skrip dengan exit code bukan nol — pola yang nanti kita kenal lagi sebagai langkah pertama CI.
Program yang dibangunnya sengaja kita buat sederhana dan murni — masukan dari argumen CLI, hasil lewat perhitungan integer, tanpa berkas dan tanpa jaringan:
#include <cstdint>
#include <iostream>
// Kalkulator PPN 11% — bahan uji build lokal maupun build di dalam
// container. Logikanya murni: masukan dari argumen CLI, hasil lewat
// return, tanpa file dan tanpa jaringan.
std::int64_t hitungPpn(std::int64_t hargaDasar) {
return hargaDasar * 11 / 100;
}
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "Pemakaian: hitung-ppn <harga-dasar>\n";
return 1;
}
std::int64_t hargaDasar = std::stoll(argv[1]);
std::int64_t ppn = hitungPpn(hargaDasar);
std::int64_t total = hargaDasar + ppn;
std::cout << "Harga dasar : " << hargaDasar << "\n";
std::cout << "PPN 11% : " << ppn << "\n";
std::cout << "Total : " << total << "\n";
return 0;
}Output yang Dihasilkan:
Harga dasar : 500000
PPN 11% : 55000
Total : 555000Tiga hal menentukan program ini: hitungPpn menghitung pajak dengan aritmetika integer hargaDasar * 11 / 100 (pembagian integer membulatkan ke bawah untuk harga yang bukan kelipatan 100 — cukup untuk bahan uji), pemeriksaan argc menolak pemanggilan tanpa argumen, dan std::stoll mengubah argumen teks menjadi bilangan bulat. Sifat murni ini penting: program tanpa berkas dan jaringan berperilaku identik di dalam container maupun di luar.
Dockerfile Multi-stage #
Docker membaca resep image dari berkas bernama Dockerfile. Untuk C++, resep terbaik memakai pola multi-stage build: satu berkas, dua stage — stage build berisi toolchain penuh, stage runtime hanya berisi binary jadi. Dockerfile berikut ada di bundle chapter ini:
## ---- Stage build: toolchain lengkap, tidak ikut ke image akhir ----
FROM gcc:13 AS build
WORKDIR /src
COPY hitung-ppn.cpp .
RUN g++ -std=c++23 -O2 -Wall -Wextra hitung-ppn.cpp -o /out/hitung-ppn
## ---- Stage runtime: image akhir yang sampai ke pengguna ----
FROM debian:bookworm-slim
RUN useradd --create-home aplikasi
COPY --from=build /out/hitung-ppn /usr/local/bin/hitung-ppn
USER aplikasi
ENTRYPOINT ["/usr/local/bin/hitung-ppn"]Mari kita bedah baris demi baris:
FROM gcc:13 AS build: membuka stage bernamabuilddari image resmi GCC — compiler, header, dan binutils lengkap tersedia tanpa instalasi manual.WORKDIR /srcdanCOPY hitung-ppn.cpp .: menyiapkan direktori kerja lalu menyalin source dari build context (folder tempatdocker builddijalankan) ke dalam stage.RUN g++ ... -o /out/hitung-ppn: mengompilasi dengan flag yang identik denganbuild-lokal.sh; binary diletakkan di/outagar mudah disalin antar stage.FROM debian:bookworm-slim: membuka stage kedua yang baru — stage ini tidak mewarisi apa pun dari stage build.RUN useradd ...danUSER aplikasi: membuat pengguna khusus lalu menjalankan program tanpa hak root — praktik keamanan baku untuk image runtime.COPY --from=build /out/hitung-ppn ...: kunci pola multi-stage — menyalin binary dari stagebuildke stage runtime; compiler dan header tidak ikut.ENTRYPOINT [...]: menjadikan binary sebagai perintah utama image, sehingga argumendocker runlangsung diterima program.
Image Build vs Image Runtime #
Pola multi-stage memisahkan dua kebutuhan yang berbeda. Image gcc:13 berukuran ratusan MB karena memuat compiler, header, dan binutils — semua itu hanya dibutuhkan selama build, dan setelah binary jadi, seluruhnya tidak dibutuhkan pengguna. Image debian:bookworm-slim hanya berisi Debian minim beserta binary hasil COPY antar stage, sehingga ukurannya jauh lebih kecil dan permukaan serangannya lebih sempit.
Ketika docker build dijalankan, Docker mengeksekusi semua stage, tetapi image akhir hanyalah stage terakhir. Stage build tidak terbuang: Docker menyimpannya sebagai layer cache, sehingga build berikutnya hanya mengulang langkah setelah COPY. Image akhir inilah yang kita tag dan kirim ke registry.
Volume untuk Data yang Bertahan #
Filesystem di dalam container bersifat sementara: ketika container dihapus, tulisan yang dibuat program di dalamnya ikut terhapus. Volume adalah penyimpanan yang dikelola Docker dan hidup di luar siklus hidup container — pola bakunya: membuat volume bernama lalu memasangnya ke satu path di dalam container:
docker volume create laporan-ppn
docker run --rm -v laporan-ppn:/data ppn-kalkulator 750000Flag -v laporan-ppn:/data memasang volume bernama laporan-ppn ke path /data di dalam container. Kalkulator kita hanya mencetak ke terminal, tetapi begitu program berkembang menulis laporan ke /data, berkas itu tetap ada setelah container --rm selesai dan dapat dibaca container berikutnya yang memasang volume yang sama. Pola kedua yang umum adalah bind mount — -v "$PWD/laporan:/data" — yang memetakan folder host langsung ke container.
Sesi docker build dan run Ilustratif #
docker build -t ppn-kalkulator .
docker run --rm ppn-kalkulator 500000
docker images ppn-kalkulatorBentuk keluaran yang diharapkan (ilustrasi — waktu build, ID image, dan ukuran berbeda antar mesin):
=> [build 3/4] RUN g++ -std=c++23 -O2 ... 8.1s
=> naming to docker.io/library/ppn-kalkulator 0.0s
Harga dasar : 500000
PPN 11% : 55000
Total : 555000
IMAGE TAG IMAGE ID CREATED SIZE
ppn-kalkulator latest 4f1c9b2a7d3e 2 minutes ago 78MBBagian paling berharga dari sesi itu: tiga baris keluaran program di dalam container sama persis dengan keluaran build-lokal.sh di host — binary yang sama, perilaku yang sama, karena compiler dan libc di dalam container datang dari image, bukan dari mesin host. Image ppn-kalkulator siap dikirim dengan docker push ke registry, dan mesin mana pun yang menjalankannya memperoleh program yang identik.
Dengan ini spektrum strategi distribusi pada course kita lengkap: dari memahami runtime dependencies, memilih static atau dynamic linking, merakit paket, hingga membungkus semuanya dalam image container. Pilih sesuai audiens — biner tunggal untuk power user, paket distro untuk pengguna Linux, container untuk server dan tim yang menuntut reproducibility. Section berikutnya membuka topik baru: membangun GUI dan aplikasi desktop dengan C++.
Referensi: Docker Get Started — Docker Docs , Multi-stage builds — Docker Docs .