Mengeraskan container dari Dockerfile hingga runtime: memilih base image minimal, pola multi-stage build, menjalankan sebagai non-root, image scanning dengan Trivy, dan guard runtime seperti read-only filesystem serta drop capabilities — semua dengan contoh sebelum/sesudah yang bisa langsung dipakai

Setelah di episode 9 kita mengamankan dependency, kita ikuti jejak kode sampai unit deployment: container. Mengapa AppSec engineer wajib menguasai ini? Karena container adalah titik temu segalanya — kode kalian, dependency pihak ketiga, base image distro, dan konfigurasi runtime — semuanya dibeku menjadi artifact yang jalan di production. Satu Dockerfile buruk merangkum empat risiko sekaligus.
Kabar baiknya: container security punya ROI hardening tertinggi karena perubahannya kecil (satu file) tapi dampaknya sistemik. Kita bahas berlapis: image → build → runtime, masing-masing dengan contoh nyata.
Keputusan pertama dan paling berdampak: base image apa yang kalian pakai. Bandingkan attack surface-nya:
| Base Image | Ukuran | Paket | Catatan |
|---|---|---|---|
ubuntu:24.04 | ~78 MB | Ratusan | Familiar, permukaan besar |
node:22-slim | ~74 MB | Sedang | Debian minimal + Node |
node:22-alpine | ~55 MB | Minimal | musl libc — waspada native module |
gcr.io/distroless/nodejs22 | ~120 MB | Hampir nol | Tanpa shell, tanpa package manager |
Prinsipnya sederhana: semakin sedikit binary di dalam image, semakin sedikit yang bisa dieksploitasi dan di-patch. Shell yang hilang bukan kerugian — ia adalah salah satu target favorit penyerang pasca-kompromi (reverse shell butuh executable).
Lihat evolusi satu Dockerfile dari naif ke hardened:
FROM node:22
WORKDIR /app
COPY . .
RUN npm install
ENV NODE_ENV=production SECRET_KEY=rahasia-dev
EXPOSE 3000
CMD ["node", "server.js"]Empat masalah: full OS image, run as root (default), COPY . . menyeret .env/.git, dan secret dibakar ke dalam layer. Versi setelahnya:
FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
FROM node:22-alpine
WORKDIR /app
RUN apk add --no-cache dumb-init && \
addgroup -S app && adduser -S app -G app
COPY --from=deps /app/node_modules ./node_modules
COPY --chown=app:app src ./src
COPY --chown=app:app package.json ./
USER app
EXPOSE 3000
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["node", "src/server.js"]Poin-poin pentingnya:
npm ci --omit=dev) tidak ikut ke image final — mengurangi CVE sekaligus ukuran.--chown saat COPY, bukan chown terpisah — menghindari layer tambahan pembawa file lama.RUN --mount=type=secret) yang tidak tersisa di layer history.Tip
Tambahkan file .dockerignore (setara .gitignore) berisi .git, .env*, node_modules, test, docs. Ini satu menit kerja yang mencegah credential bocor ke registry — temuan yang saya temukan di audit lebih sering dari yang kalian kira.
Setelah image terbentuk, scan sebelum push — bukan sesudah jalan di production:
docker build -t myapp:local .
trivy image --exit-code 1 --severity CRITICAL,HIGH --ignore-unfixed myapp:localFlag --ignore-unfixed praktis dan penting: CVE di paket OS yang belum ada patch-nya akan memblokir pipeline selamanya tanpa nilai remediasi. Untuk gate CI yang adil, kombinasi yang umum:
Jika organisasi kalian pakai registry (ECR/GHCR/Harbor), aktifkan scanning bawaan registry sebagai lapis kedua — tapi jangan jadikan satu-satunya, karena ia hanya menyentuh image yang sudah ter-push.
Image aman masih bisa dieksploitasi lewat bug aplikasi — batasi apa yang bisa dilakukan proses saat itu terjadi. Urutan prioritas guard:
services:
api:
image: myapp:1.4.2
read_only: true
tmpfs:
- /tmp
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
ports:
- "3000:3000"Makna tiap baris:
read_only: true — filesystem root immutable; malware tak bisa menulis payload. Tulis state via volume eksplisit.cap_drop: ALL — buang semua Linux capabilities; tambahkan kembali satu-satu jika ada error yang jelas.no-new-privileges:true — blok eskalasi via setuid binary.tmpfs /tmp — tempat tulis sementara yang aman untuk read-only FS.Untuk Kubernetes, padanannya adalah securityContext plus NetworkPolicy — kita perdalam konteks cluster di episode 15. Di level desktop testing, verifikasi cepat:
docker exec myapp id # uid=100(app) — bukan root
docker inspect myapp | grep -i caps:latest sebagai tag production: image berubah diam-diam setiap rebuild — deploy kalian non-reproducible dan rollback mustahil. Pin digest (@sha256:...) untuk critical path.ARG TOKEN + curl di RUN meninggalkan token di history layer meski ENV sudah dihapus — gunakan BuildKit secret mounts.Inti yang harus dibawa pulang:
.dockerignore, tanpa secret di layer.--ignore-unfixed), plus rescan harian.Di episode 11 kita naik ke lapisan yang menjalankan semuanya: Security in CI/CD — secret scanning, pipeline hardening terhadap supply chain attack, OIDC pengganti long-lived credentials, dan security gates yang membuat rilis lambat hanya jika ada alasan nyata. Sampai jumpa di episode 11!