# Docker Workflow yang Saya Pakai di Setiap Project

> Setup Docker praktis dan teruji dengan multi-stage build dan Compose yang saya terapkan ke setiap project NestJS dan Next.js sejak hari pertama.

URL: https://www.matthewswong.com/id/blog/docker-workflow-every-project/

- Author: Matthews Wong (https://www.matthewswong.com/id/about/)
- Published: 2025-06-10
- Updated: 2026-10-17
- Category: DevOps

Setelah beberapa tahun menjalankan Docker di produksi—mulai dari VPS, Cloud Run, hingga server bare-metal—saya sudah menemukan satu workflow yang dapat langsung diterapkan ke project baru di hari pertama. Bukan sesuatu yang revolusioner; justru membosankan dalam artian yang baik. Post ini mendokumentasikan setup tersebut secara detail: apa saja komponennya, kenapa setiap bagian ada di sana, dan kesalahan-kesalahan yang saya buat sebelum akhirnya settle dengan setup ini.

## Kenapa Docker di Setiap Project?

Keberatan yang sering muncul adalah Docker menambah kompleksitas untuk project kecil. Itu benar jika langsung pakai Kubernetes atau Swarm untuk side project. Tapi compose.yml dengan dua service—app dan database—hanya butuh sekitar 30 menit setup dan menghemat berjam-jam debugging 'works on my machine' sepanjang umur project. Keuntungan terbesarnya adalah parity: environment lokal, pipeline CI, dan server produksi semuanya menjalankan image yang sama.

### Prinsip Utama: Parity Dulu

Saya pakai Docker bukan karena container itu keren, tapi karena parity environment memang sulit dicapai dengan cara lain. Version manager Node (nvm, fnm) membantu untuk runtime, tapi tidak menyelesaikan perbedaan library OS, pengaturan locale, atau versi Postgres yang tepat. Dockerfile mengunci semua itu. compose.yml menambahkan layanan pendukung—database, cache, mungkin mail catcher—sehingga seluruh stack bisa jalan dengan satu perintah.

### Struktur Project

Setiap project memiliki Dockerfile di root dengan tiga stage: base (image Node bersama dan instalasi package), development (mount source code, jalankan dev server dengan hot reload), dan production (hanya menyalin output kompilasi, set non-root user, jalankan runtime minimal). compose.yml merujuk ke stage development secara default.

> Gunakan field 'target' di compose.yml untuk memilih stage development. Di CI, build stage production secara eksplisit dengan: docker build --target production -t myapp:latest . Ini mencegah dev dependencies masuk ke image produksi.

## Setup compose.yml

compose.yml standar saya menyertakan healthcheck pada service database dan menggunakan 'depends_on' dengan 'condition: service_healthy'. Ini mencegah container app mulai sebelum Postgres benar-benar siap menerima koneksi. Saya juga menggunakan anonymous volume untuk node_modules di dalam container, yang mencegah node_modules host menimpa modul container.

### Environment Variable dan Secret

Untuk development lokal, saya menyimpan file .env.example di repository dengan nilai placeholder dan file .env di .gitignore. Docker Compose membaca .env secara otomatis. Untuk produksi, saya menggunakan integrasi secret manager Cloud Run—tidak pernah environment variable yang di-bake ke dalam image.

> **Perhatian**

> Jangan pernah gunakan 'latest' sebagai tag image di produksi. Selalu tag image dengan git commit SHA atau versi semantik. 'latest' membuat rollback ambigu—Anda kehilangan kemampuan untuk dengan cepat re-deploy image yang diketahui baik sebelumnya.

## Multi-Stage Build: Apa yang Sebenarnya Dihemat

Dockerfile Node.js yang naif menginstal semua dependensi dan menyalin semua file sumber. Image yang dihasilkan bisa 800 MB atau lebih. Dengan multi-stage build, stage production hanya menyalin direktori dist/ yang dikompilasi dan menginstal hanya production dependencies. Hasilnya biasanya 150-250 MB—pengurangan 60-70%.

## Integrasi CI

Di GitHub Actions, saya build dan test di dalam Docker untuk menjamin environment CI sesuai dengan produksi. Workflow menjalankan 'docker compose up -d' untuk memulai stack penuh, mengeksekusi test suite, lalu build dan push image produksi.

## Kesalahan Umum dan Cara Memperbaikinya

Kesalahan terbesar adalah tidak menggunakan health check, yang menyebabkan kegagalan startup intermiten di CI. Kedua adalah bind-mounting seluruh project tanpa anonymous volume untuk node_modules. Ketiga adalah menggunakan root di dalam container di produksi. Menambahkan 'USER node' ke stage production memperbaiki ketiga kategori masalah.

## Sumber & Bacaan Lanjutan

- Docker — Referensi Compose file — https://docs.docker.com/compose/
- Docker — Multi-stage builds — https://docs.docker.com/build/building/multi-stage/
- Docker — Best practices pengembangan — https://docs.docker.com/develop/dev-best-practices/

## Source links

- https://docs.docker.com/compose/
- https://docs.docker.com/build/building/multi-stage/
- https://docs.docker.com/develop/dev-best-practices/

## FAQ

### Kenapa Docker dipakai di setiap project, bahkan yang kecil sekalipun?

compose.yml dengan dua service saja—app dan database—hanya butuh sekitar 30 menit setup dan menghilangkan debugging 'works on my machine' karena environment lokal, pipeline CI, dan server produksi semuanya menjalankan image yang sama. Version manager Node seperti nvm atau fnm membantu runtime, tapi tidak menyelesaikan perbedaan library OS, pengaturan locale, atau versi Postgres yang tepat — Dockerfile mengunci semua itu.

### Apa saja tiga stage dalam Dockerfile dan kenapa dipisahkan seperti itu?

Tiga stage tersebut adalah base (image Node bersama dan instalasi package), development (mount source code dan jalankan dev server dengan hot reload), dan production (hanya menyalin direktori dist/ yang dikompilasi, menginstal production dependencies saja, dan set non-root user). Pemisahan ini membuat satu Dockerfile melayani semua environment tanpa duplikasi — compose.yml menarget stage development secara default, sementara CI dan deployment produksi membangun stage production secara eksplisit dengan 'docker build --target production'.

### Seberapa besar penghematan ukuran image yang diberikan multi-stage build?

Dockerfile Node.js yang naif bisa menghasilkan image 800 MB atau lebih. Dengan multi-stage build, stage production hanya menyalin output kompilasi dan menginstal production dependencies saja, menghasilkan image 150–250 MB — pengurangan 60–70%. Image yang lebih kecil berarti pull lebih cepat, deploy lebih cepat, attack surface lebih kecil, dan latency cold-start lebih rendah di platform seperti Cloud Run.

### Apa tiga kesalahan Docker yang paling umum dibahas dalam post ini dan bagaimana cara memperbaikinya?

Pertama, tidak menggunakan health check yang menyebabkan kegagalan startup intermiten karena container app mulai sebelum Postgres siap — diperbaiki dengan menambahkan healthcheck pada service database dan menggunakan 'depends_on' dengan 'condition: service_healthy'. Kedua, bind-mounting seluruh project tanpa anonymous volume untuk node_modules, yang membuat modul native yang dibangun untuk macOS menimpa modul container Linux. Ketiga, menjalankan root di dalam container di produksi — diperbaiki dengan menambahkan 'USER node' ke stage production.

### Bagaimana secret dan environment variable sebaiknya dikelola dalam workflow Docker ini?

Untuk development lokal, file .env.example dengan nilai placeholder di-commit ke repository, sementara file .env yang sebenarnya ada di .gitignore dan dibaca otomatis oleh Docker Compose. Untuk produksi, secret dikelola melalui secret manager di level platform seperti integrasi secret manager Cloud Run — tidak pernah di-bake ke dalam image. Ini berarti Dockerfile sendiri tidak mengandung secret dan aman untuk di-commit dan dibagikan.

---

- HTML version: https://www.matthewswong.com/id/blog/docker-workflow-every-project/
- English: https://www.matthewswong.com/en/blog/docker-workflow-every-project/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
