Continuous Integration dan Continuous Deployment (CI/CD) mengubah cara Developer dan Sysadmin/DevOps Engineer mengirim perubahan kode ke production. Tanpa otomatisasi, setiap perubahan kode berarti seseorang harus login manual ke server, menarik kode terbaru, membangun ulang image, lalu menjalankan container baru satu per satu, proses yang rawan lupa langkah dan sulit direproduksi konsisten. Docker jadi komponen kunci di pipeline CI/CD modern karena satu image yang sama bisa dipakai mulai dari proses testing di runner CI sampai dijalankan di production, menghilangkan celah "berhasil di komputer saya, gagal di server". Bab ini membahas bagaimana proses build image, testing, push ke registry, dan deployment diotomatiskan lewat pipeline CI/CD, dengan contoh konkret memakai GitHub Actions dan GitLab CI sebagai dua platform yang paling umum dipakai.
26.1 Automated Image Building
Membangun image secara manual lewat docker build di laptop Developer cukup untuk development sehari-hari, tapi tidak bisa diandalkan sebagai proses resmi yang menghasilkan image untuk production. Pipeline CI/CD mengambil alih proses ini supaya setiap image dibangun dari kode yang sama persis dengan yang ada di repository, lewat lingkungan build yang konsisten dan bisa diaudit riwayatnya.
26.1.1 Build Otomatis dengan GitHub Actions
GitHub Actions menyediakan action resmi docker/build-push-action yang membungkus proses build dan push image jadi satu langkah dalam workflow. Workflow berikut memicu build setiap kali ada push ke branch main.
name: Build Docker Image
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout kode
uses: actions/checkout@v4
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build image
uses: docker/build-push-action@v6
with:
context: .
push: false
tags: payment-service:${{ github.sha }}Action docker/setup-buildx-action menyiapkan BuildKit lewat driver docker-container, dibutuhkan supaya fitur seperti cache eksternal dan build multi-platform pada docker/build-push-action berfungsi penuh. Opsi push: false pada contoh di atas sengaja dipakai untuk tahap build saja tanpa mengirim image ke registry mana pun, cocok dipakai kalau tujuannya baru memverifikasi image berhasil dibangun sebelum masuk ke tahap testing.
26.1.2 Build Otomatis dengan GitLab CI
GitLab CI menjalankan setiap job di dalam container, sehingga menjalankan docker build di dalam job itu sendiri membutuhkan akses ke Docker daemon lewat pendekatan Docker-in-Docker (dind). Konfigurasi berikut di .gitlab-ci.yml memakai image resmi docker beserta service docker:dind.
build:
stage: build
image: docker:27
services:
- docker:27-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
script:
- docker build -t payment-service:$CI_COMMIT_SHORT_SHA .Variabel DOCKER_TLS_CERTDIR mengaktifkan TLS antara job dan service dind, sesuai rekomendasi dokumentasi resmi GitLab supaya komunikasi ke Docker daemon di dalam service tidak berjalan tanpa enkripsi. Tag image untuk docker dan docker:dind pada contoh di atas sengaja disamakan versinya (27), karena GitLab merekomendasikan versi client dan daemon dind tetap selaras untuk menghindari masalah kompatibilitas API.
26.1.3 Caching Layer untuk Mempercepat Build
Setiap job CI umumnya berjalan di runner yang bersih tanpa cache layer dari build sebelumnya, sehingga tanpa konfigurasi tambahan, setiap build mengulang seluruh proses dari awal meski hanya satu baris kode yang berubah. Di GitHub Actions, docker/build-push-action mendukung cache backend type=gha yang memanfaatkan GitHub Actions cache API untuk menyimpan layer antar-run.
- name: Build image dengan cache
uses: docker/build-push-action@v6
with:
context: .
push: false
tags: payment-service:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=maxOpsi mode=max pada cache-to menyimpan seluruh layer intermediate dari setiap stage, bukan cuma layer stage akhir, sehingga cache lebih efektif untuk Dockerfile yang memakai multi-stage build. Di lapangan, dampak caching ini terasa signifikan pada project dengan proses instalasi dependency yang berat, karena tanpa cache, setiap build mengulang unduhan dan kompilasi dependency yang sebetulnya tidak berubah dari build sebelumnya. Susun instruksi Dockerfile dengan urutan dari yang paling jarang berubah (instalasi dependency) ke yang paling sering berubah (menyalin kode aplikasi), supaya cache layer dependency tetap valid meski kode aplikasi berubah setiap commit.
26.2 Testing dalam Container
Menjalankan test langsung di runner CI tanpa container berisiko lolos padahal aplikasi sebetulnya bergantung pada versi tool atau library tertentu yang kebetulan tersedia di image runner tapi tidak dijamin sama di production. Menjalankan test di dalam container yang sama dengan yang nanti di-deploy memastikan hasil test benar-benar mencerminkan kondisi aplikasi yang akan berjalan di production.
26.2.1 Menjalankan Test Suite di Dalam Container
Pola paling sederhana adalah menjalankan perintah test langsung di dalam image yang baru dibangun, memakai docker run setelah proses build selesai.
docker build -t payment-service:test .
docker run --rm payment-service:test npm testOpsi --rm penting dipakai di pipeline CI supaya container test langsung dihapus setelah selesai, mencegah runner CI menumpuk container bekas test dari run-run sebelumnya. Untuk project yang memakai multi-stage build, stage test bisa dipisah eksplisit di Dockerfile supaya dependency development seperti test runner tidak ikut terbawa ke image production akhir.
FROM node:20-alpine AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM base AS test
COPY . .
RUN npm test
FROM base AS production
COPY . .
RUN npm ci --omit=dev
CMD ["node", "server.js"]Menargetkan stage test secara eksplisit lewat opsi --target membuat proses build berhenti di stage tersebut, sehingga kegagalan test langsung menggagalkan proses build sebelum sempat menghasilkan image production.
docker build --target test -t payment-service:test .26.2.2 Test Dependency Service dengan Docker Compose
Banyak aplikasi butuh dependency seperti database atau cache untuk menjalankan test integrasi, bukan cuma unit test murni. Docker Compose memudahkan penyediaan dependency ini secara sementara khusus untuk kebutuhan testing di pipeline, tanpa perlu server database permanen di lingkungan CI.
services:
app:
build: .
depends_on:
db:
condition: service_healthy
command: npm run test:integration
environment:
DATABASE_URL: postgres://postgres:postgres@db:5432/testdb
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: testdb
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5Jalankan seluruh stack test lewat satu perintah, lalu pastikan exit code service app diperiksa supaya pipeline CI tahu apakah test gagal atau berhasil.
docker compose up --abort-on-container-exit --exit-code-from appOpsi --abort-on-container-exit menghentikan seluruh service begitu salah satu container berhenti, sementara --exit-code-from app membuat docker compose keluar dengan exit code yang sama dengan container app, sehingga step CI berikutnya bisa mendeteksi kegagalan test dengan benar. Jangan lupa membersihkan container dan volume sisa test lewat docker compose down --volumes di akhir job, supaya data test dari satu run tidak bocor ke run berikutnya kalau runner CI kebetulan memakai environment yang persisten.
26.3 Image Registry dalam Pipeline
Image yang sudah lolos test perlu didistribusikan lewat registry supaya bisa ditarik oleh environment staging atau production. Bagian ini fokus pada bagaimana proses push ke registry diotomatiskan dan diautentikasi dengan aman langsung dari pipeline CI/CD.
26.3.1 Push Image Otomatis ke Registry
Setelah build dan test lolos, langkah berikutnya di pipeline adalah mengirim image ke registry seperti Docker Hub, GitHub Container Registry, atau private registry milik organisasi. Di GitHub Actions, action docker/login-action menangani autentikasi sebelum docker/build-push-action menjalankan push.
- name: Login ke registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build dan push image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}secrets.GITHUB_TOKEN adalah token bawaan yang otomatis dibuat GitHub Actions untuk setiap run, cukup diberi permission packages: write di level workflow supaya bisa push ke GitHub Container Registry (ghcr.io) tanpa perlu membuat kredensial tambahan secara manual.
26.3.2 Strategi Tagging di Pipeline
Tag latest saja tidak cukup untuk pipeline CI/CD karena tidak memberi jejak image mana yang berasal dari commit mana, menyulitkan proses rollback saat versi tertentu bermasalah. Praktik yang lazim dipakai adalah memberi image hasil build lebih dari satu tag sekaligus, biasanya kombinasi antara hash commit yang unik dan label yang lebih mudah dibaca manusia.
tags: |
ghcr.io/${{ github.repository }}:${{ github.sha }}
ghcr.io/${{ github.repository }}:latestUntuk pipeline yang dipicu dari tag Git (misalnya saat rilis versi baru), github.ref_name bisa dipakai supaya tag image mengikuti versi rilis alih-alih hash commit.
tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}Di GitLab CI, variabel predefined seperti $CI_COMMIT_SHORT_SHA dan $CI_COMMIT_TAG berfungsi sama, tersedia otomatis di setiap job tanpa konfigurasi tambahan.
push:
stage: push
image: docker:27
services:
- docker:27-dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA26.3.3 Autentikasi Registry di CI/CD
GitLab CI menyediakan variabel bawaan CI_REGISTRY, CI_REGISTRY_USER, dan CI_REGISTRY_PASSWORD yang otomatis terisi untuk autentikasi ke GitLab Container Registry bawaan tanpa perlu menyimpan kredensial terpisah.
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRYUntuk registry eksternal seperti Docker Hub atau private registry di luar platform CI yang dipakai, kredensial harus disimpan secara eksplisit lewat fitur secret masing-masing platform, GitHub Actions memakai encrypted secrets di pengaturan repository sementara GitLab CI memakai CI/CD variables yang ditandai Masked dan Protected. Jangan pernah menuliskan username atau password registry langsung sebagai teks polos di file konfigurasi pipeline, karena file tersebut biasanya ikut ter-commit ke repository dan bisa dibaca siapa pun yang punya akses baca ke kode. Attacker yang berhasil membaca riwayat commit sebuah repository publik sering kali justru menemukan kredensial registry yang bocor lewat cara ini, bukan lewat serangan yang canggih.
26.4 Deployment Strategies
Image yang sudah tersimpan di registry belum otomatis berarti aplikasi ter-update di production; dibutuhkan langkah deployment yang menarik image baru tersebut dan menggantikan instance lama. Bagian ini membahas strategi umum deployment lewat pipeline CI/CD beserta cara memicunya dari luar proses build.
26.4.1 Rolling Update dan Blue-Green
Rolling update mengganti instance lama dengan versi baru secara bertahap, beberapa instance dulu, bukan sekaligus semua, sehingga service tetap tersedia selama proses update berlangsung. Di lingkungan yang memakai Docker Swarm, strategi ini sudah menjadi perilaku bawaan lewat docker service update.
docker service update --image payment-service:${CI_COMMIT_SHORT_SHA} payment-apiDocker Swarm menurut dokumentasi resminya mengganti task satu per satu (atau sejumlah --update-parallelism yang dikonfigurasi) dan menunggu --update-delay di antara tiap batch, serta otomatis mundur ke versi sebelumnya kalau --update-failure-action=rollback diaktifkan dan instance baru gagal berjalan sehat.
Blue-green deployment memakai pendekatan berbeda: menyiapkan environment baru ("green") berdampingan dengan environment lama ("blue") yang masih melayani trafik penuh, lalu mengalihkan trafik sekaligus ke environment baru setelah dipastikan sehat. Pendekatan ini butuh resource dua kali lipat selama masa transisi, tapi memberi jalur rollback yang lebih cepat karena environment lama masih utuh dan siap menerima trafik kembali kapan saja tanpa perlu deployment ulang.
26.4.2 Trigger Deployment dari Pipeline
Job deployment biasanya dipisah dari job build dan test, dijalankan hanya setelah kedua job tersebut sukses dan hanya untuk branch tertentu seperti main. Contoh berikut menambahkan job deployment di GitHub Actions yang bergantung pada job build sebelumnya lewat needs.
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy ke server
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
script: |
docker pull ghcr.io/${{ github.repository }}:${{ github.sha }}
docker service update --image ghcr.io/${{ github.repository }}:${{ github.sha }} payment-apiKondisi if: github.ref == 'refs/heads/main' memastikan job deployment cuma jalan untuk push ke branch main, mencegah setiap push ke branch fitur atau pull request ikut memicu deployment ke production. Di GitLab CI, kontrol serupa dilakukan lewat keyword rules atau only, dan tahap deployment bisa dijadikan job manual lewat when: manual supaya tetap membutuhkan konfirmasi eksplisit dari Sysadmin/DevOps Engineer sebelum benar-benar berjalan ke production, alih-alih otomatis berjalan begitu saja setiap kali ada push ke branch utama.
deploy:
stage: deploy
when: manual
only:
- main
script:
- docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
- docker service update --image $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA payment-apiDi lapangan, keputusan antara deployment otomatis penuh dan deployment semi-manual lewat approval biasanya bergantung pada tingkat kepercayaan tim terhadap test suite yang ada. Tim dengan cakupan test yang matang dan sudah terbukti menangkap regresi secara konsisten cenderung nyaman dengan continuous deployment penuh ke production, sementara tim yang test suite-nya belum cukup diandalkan lebih aman memakai gerbang approval manual sebagai lapisan keamanan tambahan sebelum perubahan benar-benar sampai ke pengguna akhir.

