Docker Security Basics — Security

Keamanan Docker untuk Web Developer Docker memberikan isolasi antara aplikasi dan host, tapi container bukan sandbox yang sempurna. Konfigurasi yang salah…

Docker image adalah paket berisi aplikasimu plus file sistem operasi dan pengaturan yang dibutuhkannya, dan container adalah image itu saat dijalankan. Resepnya ditulis di Dockerfile. Bayangin kamu mengirim barang lewat kontainer kapal. Siapa pun yang memegang kontainer itu bisa membuka setiap lapisan isinya, jadi kunci rumah jangan ikut dikemas. Dan petugas di dalam kontainer gak perlu memegang kunci induk pelabuhan.

🧪 Tebakan cepat: Dockerfile-mu menulis COPY . . (termasuk .env), lalu di baris berikutnya RUN rm .env. Apakah .env sudah hilang dari image? Tebak dulu, jawabannya ada di bagian "Secret jangan masuk image".

Dockerfile yang diperiksa

Ini Dockerfile proyek tokokopi, sama dengan yang ada di Lab:

FROM node:latest
WORKDIR /app
ENV STRIPE_SECRET_KEY=sk_live_abc123
COPY . .
RUN npm ci
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/server.js"]

Jalan, tapi ada lima temuan:

Baris Temuan Kenapa jadi masalah
1 node:latest isinya berganti tiap rilis Node baru, jadi build hari ini dan bulan depan beda
3 secret di ENV tersimpan di image, siapa pun yang punya image bisa membacanya
4 COPY . . tanpa .dockerignore .env dan .git ikut tersalin ke image
5 satu stage devDependencies, source, dan alat build ikut ke image produksi
semua gak ada USER proses jalan sebagai root

Jalankan sebagai user biasa: USER

Tanpa baris USER, proses di container jalan sebagai root (uid 0), user dengan akses tertinggi. Kalau aplikasimu dibobol, penyerang langsung pegang root di container, dan satu salah konfigurasi lagi bisa jadi jalan ke mesin host.

Image Node resmi sudah punya user biasa bernama node (uid 1000), jadi cukup:

USER node
CMD ["node", "dist/server.js"]

Untuk image yang belum punya user, buat dulu, lalu pindah ke user itu:

# Debian atau Ubuntu
RUN adduser --disabled-password --gecos "" appuser
USER appuser

Secret jangan masuk image

Setiap instruksi di Dockerfile membuat satu layer, dan image adalah tumpukan layer itu. Siapa pun yang bisa menarik (pull) image-mu bisa membuka setiap layer.

Perbaikannya ada dua. Pertama, file .dockerignore mencegah file ikut dikirim ke proses build sama sekali:

# .dockerignore
.env
.git
node_modules
dist

Kedua, secret dikirim saat container dijalankan, bukan saat build:

docker run --env-file .env -p 3000:3000 tokokopi:1.0

Nilainya dibaca aplikasi lewat process.env dan gak ikut tersimpan di image yang kamu push. Di platform hosting, pakai fitur secret dari platformnya.

Pin tag dan multi-stage build

Dockerfile setelah dibenahi

# Stage 1: build, devDependencies boleh ada di sini
FROM node:24-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: image yang dikirim ke produksi
FROM node:24-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]

Setelah build, periksa juga paket di dalam image dengan scanner seperti docker scout cves tokokopi:1.0 atau trivy image tokokopi:1.0.

⚠️ Jebakan yang sering ketemu

TL;DR: Image adalah tumpukan layer yang bisa dibuka siapa pun yang memegangnya, jadi secret gak boleh masuk lewat ENV, ARG, atau COPY, dan RUN rm gak menghapusnya dari layer lama. Pakai .dockerignore (isi .env, .git, node_modules) dan kirim secret saat jalan dengan docker run --env-file .env. Jalankan sebagai user biasa dengan USER node (atau adduser lalu USER appuser), pin tag seperti node:24-alpine, dan pakai multi-stage build supaya image produksi cuma berisi hasil build dan dependency produksi.

Yang akan kamu pelajari