Belajar Application Security Engineer - Container & Deployment Security
Episode 10 of 28

Belajar Application Security Engineer - Container & Deployment Security

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

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

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.

Lapis 1: Base Image & Attack Surface

Keputusan pertama dan paling berdampak: base image apa yang kalian pakai. Bandingkan attack surface-nya:

Base ImageUkuranPaketCatatan
ubuntu:24.04~78 MBRatusanFamiliar, permukaan besar
node:22-slim~74 MBSedangDebian minimal + Node
node:22-alpine~55 MBMinimalmusl libc — waspada native module
gcr.io/distroless/nodejs22~120 MBHampir nolTanpa 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).

Lapis 2: Dockerfile Hardening

Lihat evolusi satu Dockerfile dari naif ke hardened:

Dockerfile — versi naif
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:

Dockerfile — versi hardened
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:

  1. Multi-stage build: dev dependencies (npm ci --omit=dev) tidak ikut ke image final — mengurangi CVE sekaligus ukuran.
  2. USER non-root: proses yang dikompromi tidak bisa bind port <1024, install paket, atau baca file root.
  3. --chown saat COPY, bukan chown terpisah — menghindari layer tambahan pembawa file lama.
  4. dumb-init sebagai PID 1: signal handling benar; zombie process tidak menumpuk.
  5. Tidak ada secret di ENV build-time: gunakan BuildKit secrets (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.

Lapis 3: Image Scanning

Setelah image terbentuk, scan sebelum push — bukan sesudah jalan di production:

Scan image build lokal dengan Trivy
docker build -t myapp:local .
trivy image --exit-code 1 --severity CRITICAL,HIGH --ignore-unfixed myapp:local

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

  • Fail: CRITICAL dengan fix version tersedia.
  • Warn/backlog: HIGH, atau CRITICAL tanpa fix (catat + expiry).
  • Scan ulang otomatis harian — base image baru bisa membawa CVE baru.

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.

Lapis 4: Runtime Guard

Image aman masih bisa dieksploitasi lewat bug aplikasi — batasi apa yang bisa dilakukan proses saat itu terjadi. Urutan prioritas guard:

docker-compose.yml — guard runtime inti
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:

Verifikasi user & capability container
docker exec myapp id          # uid=100(app) — bukan root
docker inspect myapp | grep -i caps

Common Pitfalls

  • :latest sebagai tag production: image berubah diam-diam setiap rebuild — deploy kalian non-reproducible dan rollback mustahil. Pin digest (@sha256:...) untuk critical path.
  • Secret di layer cache: ARG TOKEN + curl di RUN meninggalkan token di history layer meski ENV sudah dihapus — gunakan BuildKit secret mounts.
  • Root "supaya gampang": permission error di development diselesaikan dengan USER root, lalu image sama ter-deploy ke production.
  • Scan sekali saat rilis pertama: CVE di base image terbit terus; tanpa rescan berkala, fleet kalian menua secara diam-diam.

Penutup

Inti yang harus dibawa pulang:

  • Base image minimal (slim/distroless) memangkas attack surface lebih murah daripada patching ratusan paket.
  • Dockerfile hardened: multi-stage, non-root, .dockerignore, tanpa secret di layer.
  • Scan image sebelum push dengan kebijakan proporsional (--ignore-unfixed), plus rescan harian.
  • Runtime guard (read-only, cap_drop, no-new-privileges) adalah airbag saat kode gagal — pasang sebelum butuh.

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!

Belajar Application Security Engineer - Container & Deployment Security | Belajar Application Security Engineer