Logo
HOMEABOUTPROJECTSBLOGCONTACT

Luthfi A. Pratama

Enterprise integration specialist building scalable, mission-critical solutions for modern businesses.

LINKS

  • About
  • Projects
  • Blog
  • Contact

CONNECT

GitHubLinkedInEmail

© 2026 Luthfi A. Pratama. All rights reserved.

Designed and built with passion.

Back to all articles
DevOps

Docker & Containerization: Kenapa "Di Laptop Gue Jalan" Bukan Jawaban

Catatan pembelajaran tentang Docker dan konsep containerization - dari kenapa butuh container, gimana Docker kerja di balik layar, sampai best practices yang sering dilanggar.

Luthfi A. Pratama

Luthfi A. Pratama

Software Engineer

2026-03-07
14 min read
Docker & Containerization: Kenapa "Di Laptop Gue Jalan" Bukan Jawaban

Masalah Klasik: "Di Laptop Gue Jalan"

Pernah denger ini?

"Kok error? Di laptop gue jalan kok."

Ini bukan cuma meme. Ini masalah nyata yang terjadi hampir di setiap tim development.

Kenapa bisa jalan di laptop A tapi ga jalan di laptop B? Atau jalan di development tapi crash di production?

  • Versi Node.js beda (laptop: v18, server: v16)
  • Library system yang beda versi
  • Environment variable yang lupa di-set
  • OS yang beda (dev di Mac, server Linux)
  • Dependency yang ga ke-install

Semua masalah ini bermuara ke satu hal: environment-nya beda.

Container lahir untuk menyelesaikan ini.

Apa itu Container?

Container itu paket yang berisi aplikasi beserta semua yang dibutuhkan untuk jalan — kode, runtime, library, system tools, konfigurasi. Semuanya dibungkus jadi satu unit yang bisa jalan di mana aja.

Bayangin kamu pindahan rumah. Daripada angkut barang satu-satu (dan berharap semuanya muat di truk yang beda-beda), kamu masukin semuanya ke dalam kontainer pengiriman standar. Kontainer itu bisa dinaikkin ke truk, kapal, atau kereta api — ga peduli transportasinya apa, isi kontainer tetap aman dan utuh.

Itu exactly apa yang Docker container lakukan untuk software.

Container vs Virtual Machine

"Tapi kan udah ada VM (Virtual Machine)? Kenapa butuh container?"

Pertanyaan bagus. Keduanya menyelesaikan masalah isolasi, tapi caranya beda.

graph TB
    subgraph Virtual Machine
        H1[Hardware]
        HV[Hypervisor]
        subgraph VM1[VM 1]
            OS1[Guest OS - 2GB+]
            L1[Libraries]
            A1[App A]
        end
        subgraph VM2[VM 2]
            OS2[Guest OS - 2GB+]
            L2[Libraries]
            A2[App B]
        end
        H1 --> HV --> VM1
        HV --> VM2
    end
graph TB
    subgraph Container
        H2[Hardware]
        HOS[Host OS]
        DE[Container Engine - Docker]
        subgraph C1[Container 1]
            CL1[Libraries]
            CA1[App A]
        end
        subgraph C2[Container 2]
            CL2[Libraries]
            CA2[App B]
        end
        H2 --> HOS --> DE --> C1
        DE --> C2
    end

Perbedaan utama:

Aspek VM Container
OS Setiap VM punya Guest OS sendiri Sharing Host OS kernel
Ukuran Gigabytes (OS + app) Megabytes (app + dependencies aja)
Startup Menit (harus boot OS) Detik (langsung jalan)
Resource Boros (setiap VM allocate CPU/RAM fixed) Efisien (sharing kernel, overhead minimal)
Isolasi Kuat (OS-level isolation) Cukup kuat (process-level isolation)

Container jauh lebih ringan karena ga perlu bawa OS sendiri. Dia cuma bawa aplikasi dan dependency-nya, lalu pakai kernel dari host OS.

Tapi VM punya isolasi lebih kuat karena setiap VM benar-benar terpisah di level hardware. Untuk workload yang butuh security isolation tinggi (multi-tenant, regulatory compliance), VM masih lebih cocok.

Banyak setup production sekarang pakai keduanya: container jalan di atas VM. VM untuk isolasi tenant, container untuk packaging aplikasi.

Gimana Docker Kerja?

Docker itu container runtime yang paling populer. Tapi Docker bukan satu-satunya — ada Podman, containerd, CRI-O. Docker cuma yang paling dikenal.

Komponen Docker

graph TB
    CLI["Docker CLI (docker build, run, push)"]
    Daemon["Docker Daemon (dockerd)"]
    Registry["Container Registry (Docker Hub, GCR, ECR)"]
    
    CLI -->|REST API| Daemon
    Daemon -->|pull/push images| Registry
    Daemon -->|manage| Containers["Running Containers"]
    Daemon -->|build/store| Images["Docker Images"]
  • Docker CLI: perintah yang kamu ketik (docker run, docker build, dll)
  • Docker Daemon: service yang running di background, manage container dan image
  • Docker Image: template read-only yang berisi blueprint container
  • Docker Container: instance yang running dari image
  • Docker Registry: tempat nyimpen dan share image (Docker Hub, GCR, ECR)

Analoginya: Image itu resep masakan, Container itu makanan yang udah dimasak. Dari satu resep bisa masak banyak porsi.

Dockerfile: Blueprint Container

Dockerfile itu file yang berisi instruksi step-by-step untuk bikin Docker image.

Contoh Dockerfile sederhana (Node.js)

# 1. Base image — mulai dari mana
FROM node:20-alpine

# 2. Set working directory di dalam container
WORKDIR /app

# 3. Copy dependency files dulu (biar bisa di-cache)
COPY package*.json ./

# 4. Install dependencies
RUN npm ci --omit=dev

# 5. Copy source code
COPY . .

# 6. Expose port
EXPOSE 3000

# 7. Perintah untuk jalankan app
CMD ["node", "server.js"]

Setiap baris itu satu instruksi yang bikin satu layer di image.

Layer System

Ini konsep penting di Docker. Setiap instruksi di Dockerfile bikin layer baru.

graph TB
    L1["Layer 1: FROM node:20-alpine (base OS + Node.js)"]
    L2["Layer 2: COPY package*.json (dependency files)"]
    L3["Layer 3: RUN npm ci (installed dependencies)"]
    L4["Layer 4: COPY . . (source code)"]
    
    L1 --> L2 --> L3 --> L4
    
    style L1 fill:#1a5276,color:#fff
    style L2 fill:#1a6e4e,color:#fff
    style L3 fill:#7d6608,color:#fff
    style L4 fill:#922b21,color:#fff

Kenapa layer penting?

  1. Caching: Docker cache setiap layer. Kalau layer ga berubah, ga perlu di-rebuild. Makanya kita COPY package*.json dulu sebelum COPY . . — kalau source code berubah tapi dependencies ga berubah, layer npm install jadi ke-cache.

  2. Sharing: Dua image yang pakai base node:20-alpine yang sama cuma perlu download base layer sekali. Layer yang sama di-share antar image.

  3. Efisiensi: Build lebih cepat, storage lebih hemat.

Image vs Container

Ini sering bikin bingung:

Konsep Penjelasan Analoginya
Dockerfile Instruksi untuk bikin image Resep masakan
Image Template read-only, hasil build Cetakan kue
Container Instance running dari image Kue yang udah jadi
Registry Tempat simpan dan share image Toko cetakan kue

Dari satu image, bisa bikin banyak container:

graph LR
    I["Image: myapp:1.0"]
    I --> C1["Container 1 (port 3001)"]
    I --> C2["Container 2 (port 3002)"]
    I --> C3["Container 3 (port 3003)"]

Setiap container punya filesystem sendiri, process sendiri, network sendiri. Mereka terisolasi satu sama lain meskipun berasal dari image yang sama.

Docker di Balik Layar: Teknologi Linux

Docker bukan magic. Dia pakai fitur-fitur Linux kernel yang udah ada:

1. Namespaces — Isolasi

Namespaces bikin container merasa dia punya sistem sendiri.

Namespace Isolasi Apa
PID Process IDs — container ga bisa lihat proses host
NET Network — container punya IP sendiri
MNT Filesystem — container punya root filesystem sendiri
UTS Hostname — container punya hostname sendiri
IPC Inter-process communication
USER User IDs — root di container ≠ root di host

Container yang running sebagai PID 1 di dalamnya, di host bisa jadi PID 4829. Dari dalam container, dia merasa dia satu-satunya yang jalan. Padahal dia cuma satu dari banyak proses di host.

2. Cgroups — Resource Limits

Control Groups ngebatesin berapa resource yang boleh dipake container.

docker run --memory=512m --cpus=1.5 myapp

Tanpa cgroups, satu container bisa makan semua RAM host dan bikin container lain (atau host sendiri) crash. Cgroups mencegah ini.

  • Memory: container melebihi limit → di-kill (OOMKilled)
  • CPU: container dibatesin pemakaian CPU-nya
  • I/O: disk read/write bisa di-throttle

3. Union Filesystem — Layered Storage

Ini yang bikin layer system Docker bisa kerja. Union filesystem (OverlayFS di Linux modern) nge-stack multiple layers jadi satu filesystem yang terlihat unified.

graph TB
    subgraph Union Filesystem
        RW["Read-Write Layer (container changes)"]
        L3["Layer 3: Source code (read-only)"]
        L2["Layer 2: npm packages (read-only)"]
        L1["Layer 1: Node.js + Alpine (read-only)"]
    end
    RW --> L3 --> L2 --> L1

Image layers itu read-only. Saat container jalan, Docker tambahin satu read-write layer di atas. Semua perubahan yang dilakukan container (buat file baru, edit file) masuk ke layer ini.

Container dihapus → read-write layer hilang → image tetap bersih.

Networking di Docker

Container perlu komunikasi — ke dunia luar, dan ke container lain.

Network Modes

graph TB
    Internet["🌐 Internet"]
    Host["Host Machine<br/>eth0: 192.168.1.100"]
    Internet --- Host
    subgraph BridgeNet["Bridge Network (default)"]
        B[docker0 bridge<br/>172.17.0.1]
        C1["Container A<br/>172.17.0.2"]
        C2["Container B<br/>172.17.0.3"]
        C1 --- B
        C2 --- B
    end
    Host --- B
  • Bridge (default): container dapet IP di subnet internal Docker. Akses keluar lewat NAT. Antar container bisa komunikasi lewat bridge
  • Host: container pakai network stack host langsung. Ga ada isolasi network, tapi performance lebih bagus
  • None: ga ada network. Untuk container yang ga perlu network access
  • Overlay: untuk multi-host networking (Docker Swarm, Kubernetes)

Port Mapping

Container punya network sendiri. Supaya bisa diakses dari luar:

docker run -p 8080:3000 myapp

Artinya: traffic ke host port 8080 diterusin ke container port 3000.

graph LR
    User["User Browser<br/>localhost:8080"] -->|port 8080| Host[Host Machine]
    Host -->|mapped to 3000| Container["Container<br/>App listening on :3000"]

DNS antar Container

Di Docker network yang sama, container bisa saling reference pakai nama:

# docker-compose.yml
services:
  api:
    image: myapi
    depends_on:
      - db
  db:
    image: postgres

Container api bisa akses database pakai hostname db — Docker handle DNS resolution-nya.

Volume: Data yang Persist

Ingat, container itu ephemeral. Container dihapus = data hilang.

Gimana kalau butuh data yang persist? (database, uploaded files, logs)

Tipe Volume

graph TB
    subgraph Docker Host
        V1["Named Volume<br/>/var/lib/docker/volumes/mydata"]
        V2["Bind Mount<br/>/home/user/project"]
        V3["tmpfs Mount<br/>(RAM only)"]
    end
    
    C1[Container] -->|named volume| V1
    C2[Container] -->|bind mount| V2
    C3[Container] -->|tmpfs| V3
Tipe Cara Kerja Use Case
Named Volume Docker manage lokasi storage-nya Database data, persistent app data
Bind Mount Map folder host langsung ke container Development (live reload source code)
tmpfs Data di RAM, hilang saat container stop Sensitive data yang ga boleh ke disk

Contoh

# Named volume — Docker manage path-nya
docker run -v mydata:/var/lib/postgresql/data postgres

# Bind mount — map folder lokal
docker run -v ./src:/app/src myapp

# tmpfs — data di RAM aja
docker run --tmpfs /tmp myapp

Yang paling sering dipakai di development: bind mount (supaya edit kode di host langsung ke-detect di container). Di production: named volume untuk database data.

Docker Compose: Multi-Container Setup

Real-world app jarang cuma satu container. Biasanya ada: app, database, cache, reverse proxy, dll.

Manage satu-satu pakai docker run? Ribet.

Docker Compose bikin kamu bisa define semua services dalam satu file YAML dan jalanin bareng.

Contoh docker-compose.yml

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/myapp
      - REDIS_URL=redis://cache:6379
    depends_on:
      - db
      - cache
    volumes:
      - ./src:/app/src  # live reload di development

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql/data  # persist data
    ports:
      - "5432:5432"

  cache:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  pgdata:  # named volume declaration

Satu command, semuanya jalan:

# Start semua services
docker compose up -d

# Lihat status
docker compose ps

# Lihat logs
docker compose logs -f app

# Stop semua
docker compose down

# Stop dan hapus volumes (hati-hati, data hilang!)
docker compose down -v

depends_on memastikan db dan cache start duluan sebelum app. Tapi ingat: depends_on cuma nunggu container start, bukan nunggu service ready. PostgreSQL bisa sudah start tapi belum siap terima koneksi. Untuk itu, butuh health check.

Best Practices

1. Pakai base image yang kecil

# ❌ Jangan
FROM node:20          # ~900MB

# ✅ Pakai alpine
FROM node:20-alpine   # ~130MB

# ✅✅ Atau distroless (paling minimal)
FROM gcr.io/distroless/nodejs20  # ~120MB, tanpa shell

Image yang lebih kecil = pull lebih cepat, attack surface lebih kecil, disk usage lebih hemat.

2. Multi-stage build

Pisahkan build environment dan runtime environment:

# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

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

Stage 1 punya semua dev dependencies (TypeScript compiler, build tools, dll). Stage 2 cuma punya hasil build dan production dependencies. Image production jadi jauh lebih kecil.

3. Jangan jalankan sebagai root

# Bikin user non-root
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# Switch ke user itu
USER appuser

Container yang jalan sebagai root itu risiko keamanan. Kalau container di-exploit, attacker punya akses root (meskipun di container, ada risiko container escape).

4. Satu container, satu concern

# ❌ Jangan: app + database + nginx di satu container
# ✅ Pisahkan jadi 3 container

Satu container harusnya jalanin satu proses/concern. Kenapa?

  • Scaling: bisa scale app tanpa scale database
  • Isolation: crash di satu ga affect yang lain
  • Updates: bisa update app tanpa restart database

5. Pakai .dockerignore

# .dockerignore
node_modules
.git
.env
*.md
dist
.next

Tanpa .dockerignore, COPY . . bakal copy semuanya termasuk node_modules (yang mau di-install ulang anyway), .git (bisa ratusan MB), file .env (secrets!). Buang waktu dan bisa bocorkan data sensitif.

6. Pin versi, jangan pakai latest

# ❌ Ga tahu versi apa yang ke-pull
FROM node:latest

# ✅ Versi spesifik, reproducible
FROM node:20.11-alpine

latest bisa berubah kapan aja. Build hari ini bisa beda sama build besok. Ini bikin debugging susah dan deployment ga predictable.

Security Considerations

Image Scanning

Image bisa mengandung vulnerability. Scan sebelum deploy:

# Docker native scanning
docker scout cves myapp:1.0

# Trivy (open source, popular)
trivy image myapp:1.0

Secrets Management

# ❌ JANGAN hardcode secrets di Dockerfile
ENV DATABASE_PASSWORD=mysecretpassword

# ❌ JANGAN copy .env file ke image
COPY .env .

# ✅ Inject saat runtime
docker run -e DATABASE_PASSWORD=secret myapp

# ✅✅ Atau pakai Docker secrets / external secret manager

Apapun yang di-embed di image bisa dilihat oleh siapa aja yang punya akses ke image. docker history bisa reveal environment variables.

Least Privilege

  • Jalankan sebagai non-root user
  • Drop Linux capabilities yang ga diperlukan
  • Pakai read-only filesystem kalau bisa
  • Limit resource (memory, CPU)
docker run \
  --user 1000:1000 \
  --read-only \
  --memory=256m \
  --cpus=0.5 \
  --cap-drop=ALL \
  myapp

Container di Production

Container ≠ Production-Ready

Container memudahkan packaging dan deployment, tapi ada banyak hal lain yang perlu diurus untuk production:

  1. Orchestration: siapa yang manage ratusan container? → Kubernetes, Docker Swarm
  2. Monitoring: gimana tahu container mana yang bermasalah? → Prometheus, Grafana
  3. Logging: logs dari 50 container ke mana? → ELK Stack, Loki
  4. Networking: service discovery, load balancing antar container → Kubernetes Service, Traefik
  5. CI/CD: gimana automate build dan deploy image? → GitHub Actions, GitLab CI

Docker Compose cukup untuk small-scale production atau single-host deployment. Untuk multi-host, butuh orchestrator seperti Kubernetes.

Container Registry

Jangan cuma store image di lokal. Push ke registry supaya bisa di-pull dari server production:

# Build dan tag
docker build -t myapp:1.0 .

# Tag untuk registry
docker tag myapp:1.0 registry.example.com/myapp:1.0

# Push ke registry
docker push registry.example.com/myapp:1.0

Popular registries: Docker Hub, GitHub Container Registry (ghcr.io), AWS ECR, Google GCR, Azure ACR.

Ringkasan

Konsep Penjelasan
Container Paket isolated yang berisi app + semua dependency
Image Template read-only, blueprint untuk container
Dockerfile Instruksi step-by-step untuk bikin image
Layer Setiap instruksi Dockerfile bikin layer yang bisa di-cache
Namespaces Isolasi proses, network, filesystem container
Cgroups Limit resource (CPU, RAM) per container
Volume Persistent storage yang survive container restart
Docker Compose Manage multi-container setup via YAML file
Multi-stage build Pisah build environment dan runtime untuk image kecil
Registry Tempat simpan dan distribusi images

Kesimpulan

Docker mengubah cara kita develop, test, dan deploy software. Intinya:

  1. Container = consistency — "di laptop gue jalan" bukan masalah lagi karena environment-nya identik
  2. Docker bukan VM — container sharing kernel host, jauh lebih ringan
  3. Layer system itu kunci — pahami caching supaya build cepat
  4. Security bukan optional — scan images, jangan root, jangan hardcode secrets
  5. Docker Compose untuk multi-service — satu YAML, satu command
  6. Container bukan solusi lengkap — di production butuh orchestration, monitoring, CI/CD

Docker itu foundation. Setelah paham Docker, langkah selanjutnya belajar Kubernetes — karena di dunia nyata, kamu ga manage 3 container. Kamu manage ratusan. Dan itu butuh orchestrator.

Tapi itu cerita untuk lain waktu.

Topics

DevOpsSoftware Engineering

Related Articles

OAuth 2.0 & OpenID Connect: Gimana "Login dengan Google" Itu Bekerja

Catatan pembelajaran tentang OAuth 2.0 dan OpenID Connect - dari kenapa kita butuh authorization framework, Authorization Code Flow step-by-step, PKCE, ID token vs access token, sampai common mistakes di production.

Caching: Kenapa Sistem Cepat Itu Sistem yang Males Ngitung Ulang

Catatan pembelajaran tentang caching - dari kenapa cache itu penting, strategi caching, Redis vs Memcached, cache invalidation, CDN, sampai pitfalls yang sering kejadian di production.

Clustering: Gimana Banyak Server Kerja Bareng Jadi Satu Sistem

Catatan pembelajaran tentang konsep clustering - dari kenapa satu server ga cukup, gimana cluster milih leader, sampai consensus algorithm dan real-world implementations.