Episode ini membedah integrasi Docker di GitLab CI/CD: menjalankan job dengan image kustom, teknik build image lewat Docker-in-Docker (DinD), lalu Kaniko yang lebih aman tanpa daemon privileged, hingga build dan push image otomatis ke GitLab Container Registry.

Di episode 5 kemarin kalian belajar mengelola variabel dan secret di GitLab CI — memakai variabel predefined, menyembunyikan nilai dengan mask, dan mengambil secret dari Vault. Sekarang mari naik satu level: hampir semua aplikasi modern dikemas sebagai Docker image sebelum dikirim ke environment. Pertanyaannya: dari mana image itu dibangun? Bisa di laptop masing-masing developer, tapi itu rawan "works on my machine". Solusi yang benar adalah membangun dan mem-push image di dalam pipeline GitLab — dari satu kode sumber, satu hasil.
Masalahnya, membangun image di dalam CI tidak senaif menjalankan docker build di laptop. Container CI tidak punya Docker daemon secara bawaan, dan ada beberapa teknik dengan tradeoff keamanan yang berbeda. Episode ini akan membedah dua teknik paling populer — Docker-in-Docker (DinD) dan Kaniko — lengkap dengan integrasinya ke GitLab Container Registry, registry bawaan GitLab yang ikut ter-update setiap kali kalian push.
Sebelum membangun image, ingat kembali: dengan Docker executor, setiap job berjalan di dalam container. Keyword image: menentukan container mana yang dipakai. Setiap job bisa memakai image yang berbeda-beda sesuai kebutuhannya:
build_js:
image: node:20-alpine
script:
- node --version
- npm ci
- npm run build
test_py:
image: python:3.11
script:
- python --version
- pip install -r requirements.txt
- pytestDi sini job build_js berjalan di dalam container node:20-alpine — kecil, ringan, dan sudah berisi Node.js — sedangkan test_py memakai python:3.11 untuk menjalankan pytest. Prinsip pentingnya: pin versi image. node:latest bisa berubah isinya kapan saja dan merusak pipeline secara tiba-tiba. Pakai tag yang spesifik — node:20-alpine atau bahkan digest — agar build reproducible.
Tip
Image berbasis Alpine jauh lebih kecil daripada varian Debian (ratusan MB vs puluhan MB). Untuk job CI yang diciptakan dan dimusnahkan setiap run, image kecil berarti startup lebih cepat dan kuota runner lebih hemat. Pilih varian Alpine bila tool yang kalian butuh tersedia di sana.
Menjalankan job dengan image kustom itu cara menggunakan Docker. Sekarang giliran membangun image sendiri. Tujuannya jelas: sekali konfigurasi di .gitlab-ci.yml, semua tim mendapat image yang sama persis, dibangun dari commit yang sama persis. Tidak ada lagi "coba bangun di mesin saya dulu".
Ada dua teknik dominan, dan keduanya sama-sama valid — hanya berbeda dalam kebutuhan infrastruktur dan profil risikonya.
DinD bekerja dengan cara "menyelipkan" sebuah Docker daemon di dalam container job. Job membangun image dengan docker build, persis seperti di laptop, tapi yang mengeksekusi adalah daemon di dalam container service docker:dind:
build_image:
image: docker:27
services:
- docker:27-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
tags:
- dockerKelebihannya: perintah docker build, docker login, dan docker push bekerja apa adanya — familiar, didukung penuh, dan cepat untuk build pertama karena daemon penuh siap di dalamnya. Tapi ada harga yang harus dibayar: DinD membutuhkan runner dalam privileged mode.
Warning
Privileged mode berarti container job punya akses penuh ke kernel host runner. Kalau daemon DinD berhasil disusupi, penyerang bisa mengambil alih mesin runner itu sendiri. Karena itu DinD hanya boleh dijalankan pada dedicated runner yang kalian kendalikan sendiri — jangan pernah menaruhnya di shared runner milik tim lain. Bagi sebagian besar tim, risikonya tidak sebanding dengan kemudahannya.
Kaniko adalah tool build image dari Google yang bekerja tanpa daemon sama sekali. Ia mengeksekusi instruksi Dockerfile langsung sebagai proses user-space di dalam container — sehingga tidak butuh privileged mode. Ini membuatnya aman untuk dijalankan di runner shared sekalipun, dan menjadi rekomendasi resmi GitLab untuk membangun image.
build_image:
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
script:
- kaniko --context $CI_PROJECT_DIR \
--dockerfile $CI_PROJECT_DIR/Dockerfile \
--destination $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
tags:
- dockerDua detail penting pada config di atas. Pertama, entrypoint: [""] di-override karena image Kaniko punya entrypoint bawaan yang menjalankan executor secara langsung — tanpa override itu, script kalian tidak akan dieksekusi. Kedua, GitLab Runner menanamkan kredensial registry GitLab ke dalam container Kaniko secara otomatis, sehingga image langsung bisa di-push ke GitLab Container Registry tanpa docker login manual — ini alasan mengapa template resmi GitLab terlihat sesederhana itu.
Kaniko juga mendukung layer caching lewat flag --cache=true dengan --cache-repo untuk menyimpan layer antara di registry — konsep yang akan kita dalami di episode 8 soal caching.
| Aspek | DinD | Kaniko |
|---|---|---|
| Kebutuhan runner | Harus privileged | Tidak butuh privileged |
| Arsitektur | Docker daemon dalam container | Proses user-space tanpa daemon |
| Keamanan | Risiko tinggi jika disusupi | Aman di runner shared |
| Perintah yang dipakai | docker build + push manual | Satu perintah kaniko |
| Build pertama | Cepat (daemon penuh) | Sedikit lebih lambat |
| Layer caching | Butuh BuildKit / --cache-from | --cache=true + --cache-repo |
Tip
Aturan praktis: kalau runner kalian sendiri dan dedicated, DinD nyaman dipakai. Kalau pipeline bisa berjalan di runner shared, grup, atau SaaS — pilih Kaniko. Untuk tim yang baru mulai, langsung ambil Kaniko: keamanannya lebih baik dan konfigurasinya justru lebih pendek.
Semua project GitLab punya Container Registry bawaan — kolaborator tidak perlu membuat registry sendiri. Variabel predefined yang perlu kalian kenal: $CI_REGISTRY_IMAGE (path image project, misalnya registry.gitlab.com/team/my-app), $CI_REGISTRY_USER, dan $CI_REGISTRY_PASSWORD untuk autentikasi.
Pola yang umum di industri: tag image dengan SHA commit agar setiap commit punya image unik yang bisa ditelusuri, lalu tambahkan tag tambahan untuk kenyamanan:
kaniko --context $CI_PROJECT_DIR \
--dockerfile $CI_PROJECT_DIR/Dockerfile \
--destination $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA \
--destination $CI_REGISTRY_IMAGE:$CI_COMMIT_BRANCH \
--destination $CI_REGISTRY_IMAGE:latestSetelah image ada di registry, stage berikutnya bisa langsung memakainya sebagai image: dari job:
deploy:
stage: deploy
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
script:
- docker run --rm $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA testPerhatikan bagaimana $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA menghubungkan seluruh pipeline: job build_image membangun dan mem-push, job deploy menarik image yang sama persis dari registry. Tidak ada image basi, tidak ada tebakan versi — semuanya dirangkai oleh variabel GitLab yang sudah terisi otomatis.
Note
Untuk pipeline produksi, jangan tag latest dari setiap push commit — itu akan ditimpa terus-menerus dan kehilangan makna. Biarkan latest mengikuti branch utama saja, misalnya lewat aturan rules yang hanya menjalankan step itu saat commit di main. Kita sudah bahas pengaturan rules di episode 4.
Pada episode 6 ini kalian telah mempelajari fondasi containerization di GitLab CI/CD:
image: menentukan container yang dipakai sebuah job, dan versi image harus di-pin agar pipeline reproducible.$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA menghubungkan build dengan deploy secara telusur.Pola "build image di CI, push ke registry, lalu dipakai di stage berikutnya" ini menjadi tulang punggung pipeline enterprise. Tapi membangun image hanyalah bagian pertama — hasil build lain (binary, zip, laporan) juga harus berpindah antar job secara terkendali, dan kadang kalian ingin mempublikasikan paket untuk dipakai tim lain. Di episode 7 kita akan membahas manajemen artifacts — bagaimana menyimpan, mentransfer, dan mengunduh output build antar job — sekaligus integrasi dengan GitLab Package Registry untuk publikasi paket npm, Maven, PyPI, Go, hingga Helm chart. Sampai jumpa di episode 7!