Episode ini mengemas aplikasi Node.js ke dalam container: Dockerfile multi-stage yang ramping, .dockerignore, dependensi produksi dengan npm ci, menjalankan image dengan docker run, serta orchestrasi sederhana dengan docker compose.

"Berjalan di mesin saya" adalah mimpi buruk setiap tim. Container menyelesaikannya: aplikasi dikemas bersama runtime, dependensi, dan konfigurasinya sehingga berjalan identik di laptop, server staging, dan cloud. Docker adalah cara paling umum untuk melakukannya.
Episode 21 mengemas aplikasi Node.js yang sudah kalian bangun ke dalam container: menulis Dockerfile multi-stage, membersihkan konteks build dengan .dockerignore, memasang dependensi produksi dengan npm ci, menjalankan image, dan mengatur layanan dengan docker compose.
Dockerfile multi-stage memisahkan stage build dari stage runtime. Stage build berisi semua yang dibutuhkan untuk memasang dependensi, sedangkan stage runtime hanya membawa hasilnya — image jadi lebih kecil dan aman:
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
FROM node:22-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/app.js ./
COPY --from=build /app/package.json ./
USER node
EXPOSE 3000
CMD ["node", "app.js"]node:22-alpine adalah base image resmi yang ringan. Stage build memasang dependensi dengan npm ci, lalu stage runtime menyalin node_modules hasil produksi. USER node menjalankan aplikasi sebagai user non-root — praktik keamanan dari episode 20. EXPOSE 3000 bersifat dokumentatif; pemetaan port sebenarnya terjadi saat docker run -p 3000:3000.
Urutan COPY package.json package-lock.json ./ lalu RUN npm ci, kemudian COPY . . bukan kebetulan. Docker me-cache setiap layer: selama package.json dan lockfile tidak berubah, layer npm ci dipakai ulang tanpa menjalankan install ulang — build berikutnya jadi jauh lebih cepat.
Konteks build adalah seluruh file yang dikirim ke Docker. Tanpa filter, folder seperti node_modules dan .env ikut terkirim — besar dan berisiko. Batasi dengan .dockerignore:
node_modules
.env
.git
*.log
coverageSetiap baris di .dockerignore mengecualikan file atau folder dari konteks build. node_modules dikecualikan karena dipasang ulang di dalam image oleh npm ci, dan .env dikecualikan agar rahasia tidak tertanam di image.
Gunakan npm ci di dalam Dockerfile, bukan npm install. npm ci memasang dependensi persis sesuai lockfile tanpa mengubahnya — deterministik, lebih cepat, dan gagal jika lockfile tidak cocok dengan package.json. Inilah pilihan yang tepat untuk build yang reprodusibel.
Setelah Dockerfile siap, bangun image dan jalankan:
docker build -t api-nodejs .
docker run -d -p 3000:3000 --name api api-nodejs
docker logs apidocker build -t api-nodejs . memberi nama api-nodejs pada image. docker run -d -p 3000:3000 menjalankannya di latar belakang dan memetakan port 3000 host ke port 3000 container. Verifikasi dengan curl http://localhost:3000 dan periksa output lewat docker logs api.
Kelola siklus hidup container dengan perintah sederhana:
docker stop api
docker rm api
docker imagesdocker stop api mengirim sinyal berhenti (yang dipakai aplikasi untuk graceful shutdown — episode 22), docker rm api menghapus container, dan docker images menampilkan image yang tersimpan. Untuk image yang tidak terpakai, docker image prune membersihkannya.
Aplikasi nyata tidak berjalan sendirian — butuh database, cache, dan layanan lain. Docker Compose mendeskripsikan seluruh layanan dalam satu file YAML dan menjalankannya bersama:
services:
api:
build: .
ports:
- "3000:3000"
environment:
NODE_ENV: production
DATABASE_URL: postgresql://app:rahasia@db:5432/app
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: rahasia
POSTGRES_DB: app
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:File di atas mendefinisikan dua layanan: api yang dibangun dari Dockerfile, dan db yang memakai image PostgreSQL. DATABASE_URL menunjuk host db — Compose membuat network internal sehingga layanan saling menemukan lewat nama. Named volume db-data membuat data database bertahan walau container di-recreate.
docker compose up -d
docker compose ps
docker compose logs -f apidocker compose up -d membangun image dan memulai seluruh layanan di latar belakang. docker compose ps menampilkan status, dan docker compose logs -f api mengikuti log API secara real-time. Stack dihentikan dengan docker compose down.
Container memudahkan deployment ke server mana pun: kirim image ke registry, tarik di server, lalu jalankan dengan Compose. Sederhananya:
Di episode 22, seluruh langkah ini akan diotomatiskan oleh pipeline CI/CD sehingga setiap push ke cabang utama langsung di-deploy.
Inti yang harus dibawa pulang:
npm ci memasang dependensi persis sesuai lockfile..dockerignore mengecualikan node_modules dan .env dari build.USER node menjalankan aplikasi sebagai user non-root.Di episode 22 selanjutnya — episode terakhir series ini — kita akan membahas CI/CD, monitoring, dan observability — pipeline GitHub Actions, otomasi rilis, log dan metrik terstruktur, health check, serta merangkum seluruh materi dalam menyusun aplikasi Node.js yang siap produksi dan maintainable.