docker

Menguasai Docker untuk DevOps: Dockerfile, Docker Compose, dan Troubleshooting yang Wajib Diketahui Tim Indonesia

Menguasai Docker untuk DevOps: Dockerfile, Docker Compose, dan Troubleshooting yang Wajib Diketahui Tim Indonesia

Pendahuluan: Mengapa Docker Penting untuk DevOps di Indonesia?

Docker telah mengubah cara kita membangun, menguji, dan menyebarkan aplikasi. Saya masih ingat ketika pertama kali menerapkan Docker di proyek tim saya pada tahun 2019 — waktu itu kami menggunakan lingkungan development yang berbeda-beda: ada yang pakai Ubuntu, ada yang macOS, ada juga pengguna Windows. “It works on my machine” menjadi lelucon yang sering terjadi. Setelah beralih ke Docker, semua anggota tim menggunakan image yang persis sama.

Di Indonesia sendiri, adopsi container sudah sangat masif, terutama di perusahaan rintisan digital dan perusahaan teknologi. Menurut laporan dari CNCF pada tahun 2023, Indonesia masuk dalam 10 negara dengan pertumbuhan Kubernetes tercepat. Artinya, memahami Docker bukan lagi pilihan, melainkan kewajiban bagi seorang DevOps engineer atau pengembang yang ingin mengoptimalkan pipeline mereka.

Artikel ini bukan untuk pemula mutlak. Saya asumsikan kamu sudah pernah menjalankan docker run hello-world dan paham konsep dasar seperti image, container, volume, dan network. Fokus kita adalah pada teknik praktis: membuat Dockerfile yang efisien, mengelola multi-service dengan Docker Compose, dan mengatasi masalah umum yang sering saya temui di tim Indonesia.

1. Dockerfile Efisien dengan Multi-stage Build

Salah satu masalah yang sering saya lihat adalah ukuran image yang membengkak. Tim sering menggunakan base image full-size seperti node:18 (sekitar 1.2 GB) tanpa memikirkan dependensi yang tidak diperlukan. Di lingkungan produksi, image sebesar itu membuat pull lambat dan meningkatkan waktu deployment.

Contoh Dockerfile Node.js dengan Multi-stage

Berikut adalah contoh Dockerfile yang saya gunakan untuk aplikasi Node.js Express. Pendekatan multi-stage memisahkan proses pembangunan (build) dan runtime.

# Stage 1: Builder
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

# Stage 2: Production
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/index.js"]

Penjelasan singkat:

  • Builder stage: saya menggunakan Alpine Linux versi ringan (node:18-alpine) untuk menginstal dependencies hanya dengan npm ci (lebih cepat dan deterministik). Setelah itu saya menjalankan perintah build.
  • Production stage: saya membuat image baru dari base yang sama, tetapi hanya menyalin dist dan node_modules dari stage sebelumnya. File seperti .git, tests, dan README tidak ikut terbawa. Hasilnya? Ukuran image turun drastis — dari ~800 MB menjadi sekitar 125 MB.

Manfaat .dockerignore

Selain multi-stage, jangan lupa membuat .dockerignore untuk mengecualikan file yang tidak diperlukan saat build.

node_modules
.git
*.md
Dockerfile
.gitignore
.env

Tanpa file ini, setiap kali kamu membangun image, docker build akan mengirim seluruh direktori proyek (termasuk node_modules yang bisa mencapai ratusan MB) ke daemon. Ini memperlambat build dan menghabiskan ruang di disk.

Tips lain: gunakan --no-cache jika kamu ingin memastikan tidak ada lapisan lama yang digunakan, tetapi biasanya docker build akan memanfaatkan cache secara otomatis untuk mempercepat.

2. Mengelola Layanan dengan Docker Compose

Di proyek nyata, hampir tidak mungkin kamu hanya menjalankan satu container. Backend butuh database, mungkin juga message broker atau reverse proxy. Docker Compose membantu mendefinisikan semuanya dalam satu file YAML.

Contoh Stack: Node.js + PostgreSQL + Nginx

Saya akan memberikan contoh docker-compose.yml yang saya gunakan pada proyek backend tim saya.

version: '3.8'

services:
  app:
    build: .
    environment:
      - DB_HOST=db
      - DB_USER=appuser
      - DB_PASSWORD=${DB_PASSWORD}
    depends_on:
      - db
    restart: unless-stopped
    networks:
      - backend

  db:
    image: postgres:15-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    env_file:
      - ./db.env
    ports:
      - "5432:5432"
    networks:
      - backend

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - app
    networks:
      - backend

networks:
  backend:

volumes:
  postgres_data:

Beberapa hal yang perlu diperhatikan:

  1. Network backend: Saya membuat network khusus sehingga service-service dapat berkomunikasi menggunakan nama service (misal app dapat merujuk ke db dan nginx) tanpa perlu mapping port ke host.
  2. Volume named postgres_data: Penting untuk persistensi data. Jika container dihapus dan dibuat ulang, data tidak akan hilang.
  3. Environment variables dan env_file: Saya menggunakan file .env atau db.env untuk menyimpan informasi sensitif. Jangan pernah hardcode password di dalam YAML. Kamu juga bisa menggunakan Docker Secrets di mode Swarm untuk ekstra keamanan.
  4. Port mapping: Di contoh, saya expose port 5432 dari container db ke host agar bisa diakses oleh alat eksternal (misal pgAdmin). Namun, di produksi, sebaiknya port tersebut tidak perlu di-publish secara langsung.

Dengan satu perintah docker compose up -d, seluruh stack berjalan. Sangat membantu (time saving), apalagi ketika ada anggota tim baru yang ingin menjalankan proyek tanpa harus menginstal semua dependensi native.

3. Tips Performa dan Keamanan untuk Produksi

Berjalan di mesin development itu mudah. Begitu masuk ke produksi, kita harus memperhatikan resource dan keamanan. Berikut adalah beberapa praktik yang saya terapkan:

3.1. Resource Limit

Container tanpa batas bisa menggunakan semua RAM server. Untuk menghindari hal itu, tetapkan limit CPU dan memori.

docker run --memory="256m" --cpus="0.5" myapp

Jika menggunakan Compose, tambahkan di service:

deploy:
  resources:
    limits:
      cpus: "0.5"
      memory: "256M"

Saya pernah mengalami kasus di mana container Node.js bocor CPU sehingga menghabiskan seluruh resource server pengembangan. Setelah membatasi dengan --cpus, server tetap stabil dan hanya container tersebut yang mengalami penurunan performa. Jadi, lebih mudah di-debug.

3.2. Health Check

Docker dapat memonitor status container melalui health check. Ini sangat berguna untuk orkestrasi seperti Swarm atau Kubernetes agar tahu kapan harus merestart container yang bermasalah.

HEALTHCHECK --interval=30s --timeout=3s --retries=3 CMD curl -f http://localhost:3000/health || exit 1

Jika menggunakan Compose, definisikan langsung di service:

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
  interval: 30s
  timeout: 3s
  retries: 3

Saya sarankan menyediakan endpoint khusus health di aplikasi (/health) yang mengembalikan status 200 jika database dan dependensi eksternal terhubung. Ini memberikan transparency yang baik untuk monitoring.

3.3. Non-Root User

Secara default, container berjalan sebagai root. Di produksi, ini berbahaya. Buat user khusus di dalam Dockerfile:

RUN addgroup -g 1001 -S appgroup && \
    adduser -u 1001 -S appuser -G appgroup
USER appuser

Pastikan direktori yang perlu ditulis oleh aplikasi sudah memiliki izin untuk user tersebut. Saya menggunakan user ID 1001 agar konsisten antara lingkungan.

3.4. Read-Only Filesystem

Jika aplikasi tidak perlu menulis file ke disk (kecuali volume yang disediakan), atur container dengan read-only root filesystem.

docker run --read-only --tmpfs /tmp myapp

Di Compose:

read_only: true
tmpfs:
  - /tmp

Ini mencegah serangan yang mencoba menulis skrip ke filesystem.

4. 5 Troubleshooting Umum dan Solusinya

Selama bertahun-tahun menggunakan Docker, saya mengumpulkan beberapa masalah yang sering muncul, terutama di kalangan tim Indonesia. Berikut ringkasannya dalam tabel:

Masalah Penyebab Umum Solusi & Perintah
1. Container name already in use Nama container sudah digunakan oleh container lain (termasuk yang sudah stop). Hapus container lama dengan docker rm -f [nama] atau gunakan --name yang berbeda.
2. Cannot connect to the Docker daemon Docker daemon tidak berjalan atau user tidak punya akses. Cek status systemctl status docker (Linux) atau pastikan user termasuk grup docker: sudo usermod -aG docker $USER. Logout-login kembali.
3. Port already allocated Port host yang ingin digunakan sudah dipakai oleh proses lain atau container lain. Gunakan docker ps -a untuk lihat container yang menggunakan port tersebut, lalu stop/hapus. Bisa juga dengan sudo netstat -tulpn | grep [port].
4. Container exits immediately without error Kesalahan pada CMD/ENTRYPOINT atau aplikasi crash karena environment variable hilang. Jalankan docker logs [container_id] untuk melihat log error. Seringkali database host tidak terisi atau koneksi ditolak. Di Compose, pastikan depends_on benar, tetapi ingat depends_on tidak menunggu service siap (gunakan wait-for-it).
5. No space left on device Banyak image, container, volume yang sudah tidak terpakai menumpuk. Gunakan docker system prune -a --volumes untuk membersihkan semua yang tidak digunakan. Hati-hati, perintah ini menghapus volume yang mungkin berisi data penting. Saya rutin menjalankan prune di server CI tiap minggu melalui cron.

Selain itu, satu masalah khas di Indonesia adalah waktu pull image yang lambat karena bandwidth internet terbatas. Solusi: daftarkan mirror registry lokal misalnya dari DigitalOcean atau Google Cloud Container Registry di wilayah Singapura/ Jakarta. Kamu juga bisa menggunakan alat seperti docker pull --platform untuk memastikan hanya mengambil arsitektur yang diperlukan.

5. Perbandingan Overhead Docker vs Virtual Machine

Banyak yang masih bertanya, “Kenapa pilih Docker dibanding VM?” Saya sering melakukan benchmark dengan stress tool untuk membandingkan overhead pada lingkungan produksi. Hasilnya:

  • VM tradisional (misal VMware/ VirtualBox) memiliki overhead CPU sekitar 10-15% karena emulasi hypervisor.
  • Docker (berjalan di Linux dengan cgroups) hanya memiliki overhead kurang dari 1% CPU dan memori yang sangat kecil (sekitar 50-100 MB untuk container idle vs 512 MB untuk VM minimal).
  • Kinerja disk: Volume Docker menggunakan overlay filesystem yang juga sangat ringan dibandingkan disk VMDK.

Sebagai contoh, saya menjalankan 10 container Nginx di satu server Ubuntu dengan total memori hanya 500 MB. Dibandingkan dengan menjalankan 10 VM yang masing-masing perlu OS sendiri, jelas lebih hemat resource. Tentu saja VM unggul dalam isolasi total, tetapi untuk kebanyakan aplikasi DevOps, container sudah cukup aman dan lebih efisien.

6. Langkah Selanjutnya: Integrasi CI/CD dan Monitoring

Docker tidak berhenti di lingkungan lokal. Dalam workflow DevOps di Indonesia, Docker sering diintegrasikan dengan alat CI/CD seperti GitHub Actions, GitLab CI, atau Jenkins. Saya sendiri menggunakan GitHub Actions untuk membangun ulang container setiap kali ada perubahan di cabang utama dan push ke registry.

Jika kamu tertarik, saya sudah menulis panduan detail tentang otomatisasi ini di artikel Membangun CI/CD Sederhana dengan GitHub Actions dan Docker. Setelah berhasil dijalankan, kamu juga perlu memantau kinerja container dengan alat seperti Prometheus dan Grafana — saya bahas di Monitoring Docker Container dengan Prometheus dan Grafana.

Untuk teman-teman yang masih benar-benar baru, pastikan kamu sudah membaca Belajar Docker untuk Pemula: Pengertian, Instalasi, dan Command Dasar sebagai pondasi sebelum mendalami artikel ini.

Penutup

Docker adalah alat yang sangat powerful, tetapi seperti pisau bermata dua. Jika tidak digunakan dengan benar, image bisa menjadi besar, keamanan bisa longgar, dan troubleshooting memakan waktu. Dengan mengikuti teknik di atas — multi-stage build, penggunaan Compose yang rapi, resource limit, dan solusi troubleshooting — saya harap tim kamu dapat bekerja lebih efektif dan fokus pada logika bisnis, bukan pada masalah lingkungan.

Saya masih terus belajar dan setiap minggu selalu ada trik baru. Cobalah untuk menerapkan satu per satu tips di atas, lihat perbedaannya. Jika ada pertanyaan atau pengalaman unik, silakan diskusikan di kolom komentar. Selamat mencoba!

Ditulis oleh seorang DevOps engineer di Jakarta. Versi awal artikel ini terinspirasi dari diskusi di meetup Docker Indonesia pada bulan Maret 2024.

💬 Komentar (0)

Belum ada komentar. Jadilah yang pertama! 💬

Komentar akan muncul setelah moderasi.