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 tradisionalJawaban container
Versi glibc berbeda antar distro; biner gagal dimuatImage runtime membawa libc yang cocok dengan biner
Toolchain antar mesin build tidak seragamStage build mengunci compiler dan flag-nya di dalam image
“Berjalan di mesin saya” sulit direproduksiImage yang sama menghasilkan perilaku yang sama di mana pun
Dependensi menempel pada hostProses 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:

bash
#!/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 build

Output yang Dihasilkan:

text
Harga dasar : 750000
PPN 11%     : 82500
Total       : 832500

Mari kita telaah skrip tersebut:

  1. 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.
  2. Baris 4 (kompilasi): g++ membangun mode release dengan flag yang identik dengan stage build pada Dockerfile, sehingga binary host dan binary container lahir dari resep yang sama.
  3. Baris 5 (uji cepat): program langsung dijalankan dengan satu kasus; bila kompilasi atau uji gagal, set -e menghentikan 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:

cpp
#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:

text
Harga dasar : 500000
PPN 11%     : 55000
Total       : 555000

Tiga 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:

dockerfile
## ---- 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:

  1. FROM gcc:13 AS build: membuka stage bernama build dari image resmi GCC — compiler, header, dan binutils lengkap tersedia tanpa instalasi manual.
  2. WORKDIR /src dan COPY hitung-ppn.cpp .: menyiapkan direktori kerja lalu menyalin source dari build context (folder tempat docker build dijalankan) ke dalam stage.
  3. RUN g++ ... -o /out/hitung-ppn: mengompilasi dengan flag yang identik dengan build-lokal.sh; binary diletakkan di /out agar mudah disalin antar stage.
  4. FROM debian:bookworm-slim: membuka stage kedua yang baru — stage ini tidak mewarisi apa pun dari stage build.
  5. RUN useradd ... dan USER aplikasi: membuat pengguna khusus lalu menjalankan program tanpa hak root — praktik keamanan baku untuk image runtime.
  6. COPY --from=build /out/hitung-ppn ...: kunci pola multi-stage — menyalin binary dari stage build ke stage runtime; compiler dan header tidak ikut.
  7. ENTRYPOINT [...]: menjadikan binary sebagai perintah utama image, sehingga argumen docker run langsung 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:

bash Sesi ilustratif — pola pemakaian volume (tidak dijalankan di praktikum)
docker volume create laporan-ppn
docker run --rm -v laporan-ppn:/data ppn-kalkulator 750000

Flag -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 #

bash Sesi ilustratif — build dan run image (tidak dijalankan di praktikum)
docker build -t ppn-kalkulator .
docker run --rm ppn-kalkulator 500000
docker images ppn-kalkulator

Bentuk keluaran yang diharapkan (ilustrasi — waktu build, ID image, dan ukuran berbeda antar mesin):

text
 => [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   78MB

Bagian 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 .