Cloud & DevOps

Tutorial Setup Docker & Docker Compose untuk Lingkungan Produksi Web yang Aman

R

Raka Haikal

Tim Teknis Arkatek

·
6 min baca
·
8x dibaca
Ilustrasi Tutorial Setup Docker & Docker Compose untuk Lingkungan Produksi Web yang Aman

Containerization menggunakan Docker dan Docker Compose telah menjadi standar industri yang sangat populer untuk menyatukan lingkungan pengembangan perangkat lunak dengan lingkungan rilis. Namun, menjalankan Docker di komputer lokal (development) sangat berbeda dengan menjalankannya di server cloud publik (production).

Banyak tim pengembang membawa berkas konfigurasi lokal langsung ke server live: menjalankan aplikasi sebagai user root, memaparkan port basis data ke internet publik, serta membiarkan ukuran image membengkak hingga gigabytes. Panduan ini menyajikan langkah menyiapkan Docker Compose untuk lingkungan produksi yang lebih aman, ramping, dan stabil, lengkap dengan Dockerfile dan compose.yaml yang dapat Anda sesuaikan.

Poin Penting / Key Takeaways
  • Hindari menjalankan aplikasi sebagai root di dalam kontainer. Gunakan user non-root agar dampak celah aplikasi lebih terbatas.
  • Multi-stage build memisahkan build tools dari image produksi sehingga image lebih kecil dan permukaan serangan berkurang.
  • Jangan mempublikasikan port database atau Redis. Port yang dipublikasikan Docker melewati aturan firewall UFW, jadi gunakan jaringan internal dan bind ke 127.0.0.1 bila perlu.
  • Tetapkan batas memori dan CPU agar satu kontainer yang bocor memori tidak menghabiskan sumber daya host.
  • Healthcheck hanya menandai status. Docker Compose di satu host tidak me-restart kontainer yang unhealthy secara otomatis.
  • Kelola rahasia lewat secrets, pin versi image, dan pindai kerentanan. Compose di satu host bukan solusi high availability.

1. Keamanan Utama: Menjalankan Kontainer Tanpa Hak Akses Root

Secara default, proses di dalam kontainer Docker berjalan dengan UID 0 (root). Root di dalam kontainer tidak otomatis sama dengan root di host karena kontainer diisolasi lewat namespace, tetapi bila aplikasi Anda memiliki celah (misalnya pengunggahan file atau eksekusi kode jarak jauh) dan kontainer salah dikonfigurasi, dampaknya akan jauh lebih besar. Menjalankan proses sebagai user non-root menambah satu lapis pertahanan.

Definisikan pengguna non-root pada Dockerfile produksi Anda (contoh lengkapnya ada di bagian berikutnya).

2. Multi-Stage Build: Image Ringan dengan Permukaan Serangan Lebih Kecil

Di tahap pengembangan, Anda membutuhkan compiler, paket TypeScript, dev-dependencies, dan berbagai CLI tool. Di produksi, semua berkas tersebut hanya menjadi beban ukuran dan memperluas permukaan serangan (attack surface).

Teknik Multi-Stage Build memungkinkan Anda melakukan kompilasi di stage sementara (builder), lalu hanya menyalin artefak jadi ke stage runtime yang minimalis (misalnya berbasis Alpine). Besarnya penyusutan ukuran bergantung pada aplikasi Anda, jadi ukur sendiri dengan docker images.

MULTI-STAGE BUILD

Build Terpisah dari Runtime

Compiler dan dev-dependencies hanya ada di stage builder. Image akhir hanya membawa artefak jadi, sehingga lebih kecil, lebih cepat di-pull, dan memuat lebih sedikit paket yang dapat dieksploitasi.

2 Stage
Dockerfile
DOCKER
1# ---- Stage 1: builder (berisi compiler dan dev-dependencies) ----
2# Gunakan versi LTS yang masih didukung, dan pin tag atau digest-nya
3FROM node:22-alpine AS builder
4WORKDIR /app
5
6COPY package*.json ./
7RUN npm ci
8
9COPY . .
10RUN npm run build && npm prune --omit=dev
11
12# ---- Stage 2: runtime (minimal, tanpa build tools) ----
13FROM node:22-alpine AS runtime
14ENV NODE_ENV=production
15WORKDIR /app
16
17# Salin hanya artefak yang dibutuhkan, dimiliki oleh user non-root bawaan image
18COPY --from=builder --chown=node:node /app/dist ./dist
19COPY --from=builder --chown=node:node /app/node_modules ./node_modules
20COPY --from=builder --chown=node:node /app/package.json ./package.json
21COPY --chown=node:node healthcheck.js ./healthcheck.js
22
23# Jalankan sebagai user non-root
24USER node
25EXPOSE 3000
26CMD ["node", "dist/server.js"]
Dockerfile multi-stage dengan runtime non-root. Sesuaikan perintah build dan path dengan proyek Anda.

Tambahkan juga berkas .dockerignore (misalnya mengecualikan node_modules, .git, dan berkas .env) agar rahasia dan berkas yang tidak perlu tidak ikut tersalin ke dalam image.

3. Isolasi Jaringan: Jangan Publikasikan Port Database

Salah satu kesalahan paling berbahaya adalah mencantumkan ports: - "5432:5432" pada service database di docker-compose.yml. Secara default, port yang dipublikasikan dapat diakses dari semua alamat eksternal, sehingga database Anda terbuka ke internet.

Ada jebakan yang sering terlewat: aturan firewall UFW tidak melindungi port yang dipublikasikan Docker. Docker mengelola aturan iptables-nya sendiri, dan lalu lintas ke port yang dipublikasikan melewati jalur yang tidak diperiksa oleh UFW. Server dengan ufw deny aktif tetap bisa terbuka. Cara yang aman:

  • Jangan publikasikan port yang tidak perlu. Database dan Redis cukup dijangkau oleh kontainer aplikasi lewat jaringan Docker.

  • Bind ke localhost bila Anda memang perlu mengaksesnya dari host: 127.0.0.1:5432:5432. Aplikasi yang berada di balik reverse proxy juga cukup di-bind ke 127.0.0.1.

  • Gunakan jaringan internal (internal: true) untuk service data agar tidak punya jalur ke internet. Perhatikan bahwa expose: hanya dokumentasi port, bukan kontrol akses.

  • Bila harus memakai firewall pada port yang dipublikasikan, pasang aturan pada rantai DOCKER-USER, bukan pada aturan UFW biasa.

Verifikasi dari luar, jangan hanya mengandalkan konfigurasi:

Terminal / CLI
BASH
1# Di server: lihat port yang benar-benar dipublikasikan Docker
2docker ps --format "table {{.Names}}\t{{.Ports}}"
3
4# Dari komputer lain: pastikan port database dan Redis tertutup dari luar
5nmap -p 5432,6379 IP_SERVER_ANDA
Memeriksa port yang terpapar, dari dalam server dan dari luar.

4. Contoh compose.yaml Produksi Lengkap

Berikut contoh yang menggabungkan isolasi jaringan, healthcheck, batas sumber daya, rotasi log, dan pengerasan kontainer. Nama image, versi, dan angka limit hanyalah contoh. Sesuaikan dengan aplikasi Anda.

compose.yaml
YAML
1services:
2 app:
3 image: ghcr.io/arkatek/web-app:1.4.2 # contoh: pin ke tag versi, bukan latest
4 restart: unless-stopped
5 ports:
6 - "127.0.0.1:3000:3000" # hanya localhost, reverse proxy di host yang melayani publik
7 env_file:
8 - .env.production # jangan di-commit. DATABASE_URL harus memakai password yang sama dengan secret db
9 depends_on:
10 db:
11 condition: service_healthy
12 redis:
13 condition: service_healthy
14 networks:
15 - edge
16 - backend
17 healthcheck:
18 test: ["CMD", "node", "healthcheck.js"]
19 interval: 30s
20 timeout: 5s
21 retries: 3
22 start_period: 20s
23 read_only: true
24 tmpfs:
25 - /tmp
26 cap_drop:
27 - ALL
28 security_opt:
29 - no-new-privileges:true
30 deploy:
31 resources:
32 limits:
33 cpus: "1.0"
34 memory: 512M
35 logging:
36 driver: json-file
37 options:
38 max-size: "50m"
39 max-file: "3"
40
41 db:
42 image: postgres:16-alpine
43 restart: unless-stopped
44 environment:
45 POSTGRES_DB: app
46 POSTGRES_USER: app
47 POSTGRES_PASSWORD_FILE: /run/secrets/db_password
48 secrets:
49 - db_password
50 volumes:
51 - db_data:/var/lib/postgresql/data
52 networks:
53 - backend
54 healthcheck:
55 test: ["CMD-SHELL", "pg_isready -U app -d app"]
56 interval: 10s
57 timeout: 5s
58 retries: 5
59 deploy:
60 resources:
61 limits:
62 memory: 1G
63 logging:
64 driver: json-file
65 options:
66 max-size: "50m"
67 max-file: "3"
68
69 redis:
70 image: redis:7-alpine
71 restart: unless-stopped
72 command: ["redis-server", "--appendonly", "yes"]
73 volumes:
74 - redis_data:/data
75 networks:
76 - backend
77 healthcheck:
78 test: ["CMD", "redis-cli", "ping"]
79 interval: 10s
80 timeout: 3s
81 retries: 5
82 deploy:
83 resources:
84 limits:
85 memory: 256M
86 logging:
87 driver: json-file
88 options:
89 max-size: "50m"
90 max-file: "3"
91
92networks:
93 edge: {}
94 backend:
95 internal: true # tanpa jalur ke internet, hanya untuk service data
96
97volumes:
98 db_data:
99 redis_data:
100
101secrets:
102 db_password:
103 file: ./secrets/db_password.txt
compose.yaml produksi: database dan Redis hanya di jaringan internal, tanpa port yang dipublikasikan.
  • Jaringan: hanya app yang berada di jaringan edge dan dapat diakses dari host. db dan redis hanya ada di jaringan backend yang internal.

  • Batas sumber daya: deploy.resources.limits mencegah satu kontainer menghabiskan memori host. Pada Docker Compose v2, nilainya dihormati oleh docker compose up. Cek hasilnya dengan docker stats dan tentukan angkanya dari pemakaian nyata.

  • Pengerasan kontainer: read_only, cap_drop: ALL, dan no-new-privileges mengurangi apa yang bisa dilakukan proses yang disusupi. Tidak semua image kompatibel dengan opsi ini (aplikasi yang menulis ke disk membutuhkan volume atau tmpfs), jadi uji dulu.

  • Urutan start: depends_on dengan condition: service_healthy menunda app sampai database dan Redis lolos healthcheck, hal yang tidak dijamin oleh depends_on biasa.

5. Secrets dan Higiene Image

  • Jangan menaruh rahasia di image atau repositori. Gunakan file secrets (seperti contoh di atas) atau secrets manager, simpan berkas dengan izin akses terbatas, dan jangan commit .env produksi.

  • Pin versi image: gunakan tag versi spesifik (atau digest) alih-alih latest agar deployment dapat direproduksi.

  • Pindai kerentanan image secara rutin dengan alat seperti Trivy atau Docker Scout, dan bangun ulang image saat base image mendapat perbaikan keamanan.

  • Gunakan registry privat untuk image aplikasi Anda.

6. Healthcheck dan Restart: Apa yang Sebenarnya Terjadi

Healthcheck adalah perintah yang dijalankan Docker secara berkala di dalam kontainer, lalu hasilnya disimpan sebagai status starting, healthy, atau unhealthy. Penting dipahami: Docker Engine di satu host tidak me-restart kontainer yang unhealthy. Kebijakan restart: unless-stopped hanya bereaksi ketika proses utama keluar. Kontainer yang hang tetapi masih berjalan akan tetap berstatus unhealthy tanpa tindakan apa pun.

Healthcheck Tidak Me-restart Kontainer
Untuk memulihkan kontainer yang unhealthy secara otomatis, Anda membutuhkan orkestrator yang bertindak atas status health (Docker Swarm atau Kubernetes), sidecar seperti autoheal, atau aplikasi yang sengaja keluar saat gagal agar kebijakan restart berjalan. Minimal, pasang alert untuk status unhealthy sehingga ada yang menindaklanjutinya.

Berikut skrip healthcheck minimal yang dipanggil oleh konfigurasi di atas. Sesuaikan path /health dengan aplikasi Anda:

healthcheck.js
JS
1const http = require("node:http");
2
3const request = http.get("http://127.0.0.1:3000/health", (res) => {
4 process.exit(res.statusCode === 200 ? 0 : 1);
5});
6
7request.on("error", () => process.exit(1));
8request.setTimeout(3000, () => {
9 request.destroy();
10 process.exit(1);
11});
Healthcheck HTTP minimal: exit code 0 berarti sehat, selain itu unhealthy.

7. Manajemen Log

Dengan driver json-file bawaan, file log kontainer tumbuh tanpa batas dan dapat memenuhi disk server hingga host crash. Batasi ukuran log per kontainer, seperti pada contoh di atas dengan max-size: "50m" dan max-file: "3", atau atur default di level daemon Docker. Untuk produksi yang lebih serius, pertimbangkan mengirim log ke sistem terpusat.

Docker Compose di Satu Host vs Kubernetes (K8s) Cluster

Keunggulan Docker Compose Produksi
  • Sangat hemat sumber daya server karena tidak ada overhead control plane kluster
  • Proses pemeliharaan dan backup volume relatif sederhana
  • Setup cepat untuk satu server bisnis dengan beban menengah
Keterbatasan dan Alternatif
  • Compose mengelola satu host: tidak ada failover otomatis, rolling update bawaan, maupun autoscaling
  • Kubernetes memberi orkestrasi penuh, tetapi kurva belajarnya curam dan biaya operasionalnya lebih besar
  • Pilihan menengah: Docker Swarm atau layanan container terkelola dari penyedia cloud

8. Operasional di Luar Docker

  • Backup: volume database perlu dibackup secara terjadwal (misalnya dump berkala yang disimpan di luar server) dan pemulihannya diuji.

  • Keamanan host: perbarui OS secara rutin, nonaktifkan login SSH dengan password dan gunakan SSH key, dan batasi port publik hanya untuk yang diperlukan (misalnya 80 dan 443).

  • Reverse proxy dan TLS: layani trafik publik lewat reverse proxy (Nginx, Caddy, atau Traefik) dengan sertifikat TLS.

  • Pemantauan: pantau penggunaan CPU, memori, disk, dan status health, dan pasang alert.

Menghubungkan dengan Arsitektur Aplikasi Anda

Terapkan optimasi indeks dan connection pooling pada kontainer database sesuai panduan Strategi Optimasi Database Skala Besar. Pola kontainer ini juga cocok untuk menjalankan Redis dan worker antrean seperti di Otomatisasi Alur Kerja dengan Webhooks dan AI Agents, untuk men-deploy API seperti di Tutorial RESTful API Skalabel Laravel & PostgreSQL, dan untuk menimbang kapan satu host cukup berdasarkan Panduan Arsitektur Web Modern: Monolith vs Microservices.

SOLUSI TEKNOLOGI ARKATEK

Ingin Mengamankan & Memodernisasi Infrastruktur Cloud Anda?

Diskusikan audit keamanan server, setup Docker produksi, dan otomatisasi deployment bersama tim engineer Arkatek.

Pertanyaan Umum Seputar Docker Compose untuk Produksi

Apakah root di dalam kontainer sama dengan root di host?

Tidak otomatis, karena kontainer diisolasi lewat namespace. Namun root di dalam kontainer memperbesar dampak bila ada celah aplikasi atau konfigurasi yang salah (misalnya privileged atau docker.sock). Menjalankan proses sebagai non-root menambah lapisan pertahanan.

Mengapa port database tetap terbuka padahal UFW aktif?

Docker mengelola aturan iptables sendiri, dan lalu lintas ke port yang dipublikasikan tidak melewati aturan UFW. Jangan publikasikan port database, atau bind ke 127.0.0.1, dan verifikasi dari luar dengan alat seperti nmap.

Apakah healthcheck me-restart kontainer yang bermasalah?

Tidak untuk Docker Compose di satu host. Healthcheck hanya menandai status. Untuk pemulihan otomatis butuh Swarm atau Kubernetes, sidecar seperti autoheal, atau alert agar ada yang menindaklanjuti.

Apakah Docker Compose cukup untuk produksi?

Untuk satu server dengan beban menengah dan toleransi downtime yang jelas, cukup. Bila Anda membutuhkan high availability, rolling update, atau autoscaling, pertimbangkan Swarm, Kubernetes, atau layanan container terkelola.

Bagaimana menyimpan password database dengan aman?

Gunakan file secrets atau secrets manager, batasi izin akses berkasnya, jangan commit file .env produksi, dan rotasi kredensial secara berkala.

Kesimpulan

Docker Compose adalah alat yang andal dan hemat biaya untuk satu host produksi bila dikonfigurasi dengan benar. Dengan multi-stage build, proses non-root, jaringan database yang internal, batas sumber daya, pengelolaan rahasia, serta pemahaman yang jelas tentang apa yang dilakukan (dan tidak dilakukan) healthcheck, infrastruktur aplikasi web Anda akan jauh lebih aman dan mudah dipelihara. Ingat bahwa Compose di satu host tidak menggantikan kebutuhan akan backup, pemantauan, dan rencana pemulihan.

Referensi: Packet filtering and firewalls (Docker Docs), Docker Compose (Docker Docs), dan Building best practices (Docker Docs).

R

// DITULIS OLEH

Raka Haikal

Pengembang perangkat lunak & spesialis arsitektur sistem digital di Arkatek. Berfokus pada pembangunan solusi software scalable, otomatisasi sistem bisnis, dan optimasi performa.

// EKSPLORASI TOPIK

Artikel Terkait Lainnya

Lihat Semua Artikel →