Belajar Backend Developer - Container & Dockerization
Episode 14 of 28

Belajar Backend Developer - Container & Dockerization

Mengemas service menjadi artefak portable dengan Docker: menulis Dockerfile yang benar untuk Node.js, strategi optimasi ukuran image, prinsip image minimal dan non-root, serta mengorkestrasi banyak service dengan docker-compose

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Di episode 12-13 kalian mendesain arsitektur dan gerbang sistem. Sekarang tiba saatnya mengemas aplikasi menjadi artefak yang bisa dideploy di mana saja — inilah yang membuat "berjalan di mesin saya" tidak lagi jadi alasan. Container mengunci runtime, dependency, dan konfigurasi dalam satu image, sehingga apa yang berjalan di laptop kalian sama persis dengan yang berjalan di server.

Mengapa ini penting? Karena konsistensi environment adalah akar dari sebagian besar masalah deployment: "di lokal jalan, di server tidak". Docker menyelesaikan ini dengan mengemas semuanya. Episode ini membangun Dockerfile yang benar, strategi optimasi image, dan docker-compose untuk mengorkestrasi service toko online kita.

Konsep Dasar: Image vs Container

  • Image — cetakan yang tidak bisa diubah (read-only): kode, runtime, dependency, konfigurasi. Dibangun dari Dockerfile.
  • Container — instance image yang berjalan (process terisolasi).
100%

Dockerfile yang Benar

Mari bangun image untuk API Node.js kita. Perhatikan strategi multi-stage build: tahap build men-download dan meng-compile, tahap runtime hanya mengambil hasilnya — image akhir tetap kecil.

Dockerfile - multi-stage
# --- Tahap 1: build ---
FROM node:22-alpine AS build
WORKDIR /app
 
# 1. Salin hanya file dependency dulu (cache layer efektif)
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
 
# 2. Salin source dan build (TypeScript)
COPY tsconfig.json ./
COPY src ./src
RUN bun run build
 
# --- Tahap 2: runtime ---
FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
 
# 3. Hanya production dependencies
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile --production
 
# 4. Hasil build, tanpa source & tanpa tool dev
COPY --from=build /app/dist ./dist
 
# 5. Non-root user (keamanan)
RUN addgroup -S app && adduser -S app -G app
USER app
 
EXPOSE 3000
CMD ["node", "dist/server.js"]

Kenapa Urutan Ini Penting?

Docker mem-build layer demi layer dan meng-cache layer yang tidak berubah. Dengan menyalin package.json terlebih dahulu, perubahan kode tidak memicu re-install dependency — build jadi jauh lebih cepat. Urutan di atas adalah urutan optimal: dependency dulu, source belakangan.

Optimasi Image

Ukuran image memengaruhi waktu push/pull dan biaya storage. Tiga strategi paling efektif:

1. Alpine dan Multi-Stage

Base node:22-alpine (~50 MB) jauh lebih kecil dari node:22 full. Multi-stage membuang tool build dari image final — image kita berisi hanya hasil build dan production dependencies.

2. Hindari Menyalin yang Tidak Perlu

.dockerignore
node_modules
dist
.git
.env
*.log

node_modules di-rebuild di dalam image, jadi tidak perlu disalin. .env jangan pernah masuk image — itu secrets (episode 17).

3. Satu Binary untuk Bahasa Terkompilasi

Jika memakai Go/Rust, image bisa jadi se-ramping beberapa MB — hanya binary tanpa runtime sama sekali. Inilah salah satu alasan Go populer untuk microservices (episode 3).

Note

Lihat ukuran image dengan docker images. Image production yang sehat untuk API Node: 100-200 MB (Node + dependencies). Jika melebihi 500 MB, ada yang salah — cek apakah tool dev ikut ter-copy atau base image terlalu gemuk.

Image Minimal dan Non-Root

Dua kebijakan keamanan wajib dalam container:

  1. Image minimal — semakin sedikit tool dalam image, semakin kecil permukaan serangan. Tidak ada curl, bash, atau package manager di image production.
  2. Non-root user — container yang berjalan sebagai root adalah risiko besar: jika aplikasi di-compromise, penyerang punya akses root di container. Setiap image production harus memakai USER app.
Non-root untuk Python/FastAPI
FROM python:3.12-alpine AS runtime
WORKDIR /app
 
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
 
COPY . .
 
RUN addgroup -S app && adduser -S app -G app
USER app
 
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

docker-compose: Orkestrasi Lokal

Aplikasi nyata tidak berdiri sendiri — API butuh PostgreSQL, Redis, dan RabbitMQ (dari episode 0, 9, 10). docker-compose mendefinisikan seluruh stack dalam satu file dan menjalankannya bersama.

docker-compose.yml - stack toko online
services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://dev:dev@db:5432/shop
      REDIS_URL: redis://redis:6379
      RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672
    depends_on:
      - db
      - redis
      - rabbitmq
    restart: unless-stopped
 
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: dev
      POSTGRES_DB: shop
    volumes:
      - pgdata:/var/lib/postgresql/data
 
  redis:
    image: redis:7-alpine
 
  rabbitmq:
    image: rabbitmq:3-management
    ports:
      - "15672:15672"
 
volumes:
  pgdata:
Jalankan seluruh stack
docker compose up -d --build
docker compose ps
docker compose logs -f api

depends_on menjamin urutan start, dan volumes: pgdata membuat data database tetap hidup meski container di-restart.

Tip

Untuk developer, compose adalah "production mini" — environment yang sama persis dengan server. Biasakan: kalau sesuatu jalan di compose, hampir pasti jalan di produksi. Mulai dari sini sebelum menyentuh Kubernetes — 90% tim tidak akan pernah butuh K8s (episode 15).

Healthcheck dan Restart Policy

Container harus bisa diperiksa kesehatannya dan pulih sendiri:

Healthcheck di compose
services:
  api:
    build: .
    healthcheck:
      test: ["CMD", "node", "-e", "fetch('http://localhost:3000/health').catch(() => process.exit(1))"]
      interval: 30s
      timeout: 5s
      retries: 3
    restart: unless-stopped

restart: unless-stopped membuat container hidup kembali setelah crash — mekanisme self-healing pertama sebelum orkestrator besar.

Common Pitfalls

.env Ikut Masuk Image

Env file berisi secrets. Jangan pernah menyalinnya ke image; inject saat runtime via environment (episode 15 dan 17).

Aplikasi Listen di 127.0.0.1

Di dalam container, aplikasi harus listen di 0.0.0.0 — bukan 127.0.0.1 — karena port dipetakan dari host. Ini penyebab paling umum "sudah expose tapi tidak bisa diakses".

Image Tanpa Non-Root

Container berjalan root = risiko keamanan besar. Selalu USER app.

Build Context Terlalu Besar

Tanpa .dockerignore, Docker mengirim seluruh folder proyek (termasuk node_modules dan .git) sebagai build context — build lambat dan memori boros.

Warning

Prinsip immutable image: image yang sudah di-build tidak boleh diubah dengan cara "masuk dan edit file". Kalau ada perubahan, bangun image baru. Image immutability adalah fondasi rollback yang aman — jika versi baru rusak, deploy versi lama, bukan "memperbaiki" yang berjalan.

Penutup

Episode 14 mengemas service menjadi artefak portable: Dockerfile multi-stage yang efisien, optimasi ukuran image, kebijakan non-root, dan docker-compose untuk menjalankan seluruh stack toko online.

Inti yang harus dibawa pulang:

  • Image = cetakan, container = instance yang berjalan.
  • Multi-stage build: build terpisah dari runtime, image tetap kecil.
  • Urutan COPY memengaruhi cache build — dependency dulu, source belakangan.
  • Image produksi: minimal + non-root; jangan pernah masukkan .env.
  • compose = production mini; depends_on, volume, healthcheck, dan restart policy.
  • Image bersifat immutable — perubahan selalu image baru.

Di episode 15 selanjutnya kita akan mengirim image itu ke produksi: deployment & CI/CD backend — GitHub Actions, environment, dan zero-downtime deployment. Sampai jumpa di episode 15!

Belajar Backend Developer - Container & Dockerization | Belajar Backend