Docker Best Practices untuk Container Produksi

Foto oleh Unsplash

Foto oleh Unsplash
Containerization Docker merevolusi cara kita membangun dan mengirimkan software, tetapi menulis Dockerfile siap produksi membutuhkan lebih dari sekadar 'COPY . .' dan 'RUN npm install'. Di produksi, ukuran image, waktu startup, permukaan keamanan, dan observabilitas semuanya penting. Panduan ini membahas Docker best practices yang membedakan container dev seadanya dari image produksi yang dikeraskan dan dioptimalkan — termasuk multi-stage build, pengguna non-root, layer caching, health check, dan penanganan secret.
Multi-stage build memungkinkan Anda menggunakan environment build yang berat (Node, Go, Maven) untuk mengompilasi aplikasi, kemudian menyalin hanya artefak yang dikompilasi ke image runtime minimal. Aplikasi Next.js yang berbobot 1,2GB dalam build single-stage dapat diperkecil hingga di bawah 150MB dengan Dockerfile multi-stage yang tepat. Image yang lebih kecil lebih cepat ditransfer, lebih cepat dimulai, dan mengekspos permukaan serangan yang lebih kecil.
Setiap instruksi FROM memulai stage build baru. Anda memberi nama stage dengan AS dan mereferensikannya dalam instruksi COPY --from=. Stage terakhir menjadi image sebenarnya yang didorong ke registry — semua stage perantara hanya ada selama build dan dibuang. Ini berarti image akhir Anda tidak memiliki toolchain build, dependensi dev, maupun source code.
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Stage 2: Production image
FROM node:20-alpine AS runner
WORKDIR /app
# Create non-root user
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
# Copy only built artifacts
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
# Drop privileges
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV NODE_ENV production
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD wget -qO- http://localhost:3000/api/health || exit 1
CMD ["node", "server.js"]Selalu utamakan image berbasis Alpine (node:20-alpine, python:3.12-alpine) untuk stage runtime — biasanya 5-10x lebih kecil dari ekuivalen Debian/Ubuntu. Untuk stage build, gunakan apa saja yang dibutuhkan. Untuk aplikasi Go, image runtime bisa berupa 'scratch' (harfiah kosong) karena Go dikompilasi menjadi binary statis tunggal. Selalu pin ke digest spesifik atau minimal tag versi patch daripada ':latest' untuk memastikan build yang reproducible.
Jalankan 'docker image inspect <image>' untuk melihat daftar lengkap layer dan ukurannya. Gunakan 'docker scout cves <image>' atau 'trivy image <image>' untuk memindai CVE yang diketahui sebelum mendorong ke produksi. Sebagian besar pipeline CI harus memberlakukan kebijakan 'tidak ada kerentanan CRITICAL'.
Secara default container Docker berjalan sebagai root (UID 0). Jika penyerang mengeksploitasi kerentanan dalam kode aplikasi Anda, mereka mendapatkan akses root di dalam container dan berpotensi di host jika container escape dimungkinkan. Membuat pengguna sistem khusus dengan UID tetap dan beralih ke sana dengan instruksi USER adalah salah satu perubahan keamanan bernilai tertinggi yang dapat Anda buat.
Saat Anda COPY file ke dalam image dan kemudian beralih ke pengguna non-root, pastikan pengguna tersebut memiliki akses baca ke file aplikasi. Gunakan 'COPY --chown=nextjs:nodejs' untuk mengatur kepemilikan dalam langkah yang sama dengan copy, menghindari layer RUN chown tambahan yang akan menggandakan ukuran layer. Pendekatan COPY --chown kombinasi juga lebih mudah dibaca dan menghasilkan image yang lebih ramping.
Mulai container dengan '--read-only' dan mount volume yang dapat ditulis khusus di tempat aplikasi perlu menulis (upload, temp file, log). Filesystem root yang read-only mencegah penyerang yang mendapatkan eksekusi kode dari memodifikasi binary aplikasi atau menjatuhkan malware persisten. Padukan dengan '--no-new-privileges' untuk mencegah eskalasi hak istimewa melalui binary setuid.
Bahkan jika Anda menggunakan multi-stage build dan secret hanya ada di layer perantara, secret yang dipanggang ke layer perantara dapat diekstrak dengan 'docker save' dan alat inspeksi layer. Gunakan flag '--secret' BuildKit Docker untuk me-mount secret sebagai file sementara selama build, atau teruskan secret saat runtime melalui variabel environment dari secrets manager (Vault, AWS SSM). Pindai image Anda dengan 'trufflesecurity/trufflehog' sebelum mendorong.
Docker membangun ulang setiap layer di bawah baris pertama yang berubah dalam Dockerfile Anda. Mengoptimalkan urutan layer untuk tingkat cache hit adalah cara tercepat untuk mempercepat waktu build development dan CI. Aturan emas: copy file yang jarang berubah terlebih dahulu, copy file yang sering berubah terakhir.
Untuk aplikasi Node.js: copy package.json dan package-lock.json terlebih dahulu, jalankan npm ci, kemudian copy sisa source code. Dengan cara ini layer npm install yang mahal hanya dibatalkan validitasnya saat dependensi berubah, bukan setiap kali Anda mengedit source file. Prinsip yang sama berlaku untuk Go (go.mod sebelum source), Python (requirements.txt sebelum kode), dan Ruby (Gemfile sebelum kode).
File .dockerignore yang hilang atau tidak lengkap mengirimkan seluruh direktori kerja Anda — termasuk node_modules, .git, data test, dan secret lokal — sebagai build context ke Docker daemon. Ini memperlambat build dan berisiko membocorkan file sensitif ke dalam image. .dockerignore Anda setidaknya harus mengecualikan: node_modules, .git, .env*, *.log, dan file override lokal apa pun.
Instruksi HEALTHCHECK memberi tahu Docker dan orchestrator seperti Kubernetes kapan container benar-benar siap melayani traffic, bukan hanya sudah dimulai. Dikombinasikan dengan penanganan sinyal yang tepat untuk graceful shutdown, health check mencegah orchestrator Anda merutekan traffic ke container yang sedang startup atau menguras koneksi.
Instruksi HEALTHCHECK menentukan perintah, interval, timeout, dan jumlah percobaan ulang. Menggunakan 'wget -qO-' atau 'curl -sf' terhadap endpoint /health atau /readyz aplikasi Anda adalah pola yang idiomatis. Jaga endpoint health agar ringan — harus memeriksa bahwa aplikasi siap (koneksi database hidup, cache sudah hangat) tetapi mengembalikan respons dalam milidetik, bukan detik.
Di Kubernetes, instruksi HEALTHCHECK diabaikan demi liveness dan readiness probe yang didefinisikan dalam spec Pod. Namun, tetap simpan HEALTHCHECK di Dockerfile Anda untuk kompatibilitas dengan Docker Compose dan deployment Docker Swarm bare. Keduanya menggunakan endpoint yang sama tetapi lokasi konfigurasi yang berbeda.
Tag bersifat mutable secara default di Docker registry — ':latest' bisa menunjuk ke image berbeda besok dibandingkan hari ini. Deployment produksi harus selalu mereferensikan identifier yang tidak dapat diubah: digest image penuh (sha256:...) atau tag yang menyertakan commit SHA git. Ini membuat deployment dapat direproduksi dan diaudit.
Strategi penandaan yang masuk akal menerbitkan tiga tag per rilis: versi semantik penuh (1.4.2), versi minor (1.4), dan SHA (sha-abc1234). Pipeline CI menggunakan tag SHA untuk deployment guna memastikan reproducibility yang tepat. Operator manusia dan Dependabot menggunakan tag semver. Tag 'latest' berguna untuk development lokal tetapi tidak boleh pernah digunakan dalam manifes produksi.
Terminologi Docker penting untuk pekerjaan produksi: multi-stage build, Alpine base image, layer caching, health check, build context, and least privilege.