Gin + Docker Compose untuk Local Development
Menyiapkan environment lokal yang konsisten untuk semua engineer adalah tantangan klasik dalam pengembangan backend Golang + Gin. Tanpa containerisasi, masalah yang sering muncul meliputi versi Go yang berbeda antar mesin, dependency database yang tidak sama, konfigurasi environment yang tidak sinkron, sampai masalah klasik “works on my machine” — kode yang berjalan mulus di laptop satu developer tapi error di laptop developer lain. Docker Compose menjawab masalah ini dengan pendekatan yang sederhana, eksplisit, dan mudah diskalakan, karena seluruh definisi service — API, database, cache, dan dependency lainnya — ditulis dalam satu file yang bisa dijalankan dengan satu perintah saja.
Kenapa Docker Compose untuk Local Development?
Docker Compose memberikan empat manfaat utama untuk local development. Pertama, kamu bisa menjalankan API beserta seluruh dependency-nya (database, cache, dan lainnya) hanya dengan satu perintah, tanpa perlu menginstal dan mengonfigurasi masing-masing secara manual. Kedua, environment jadi sama persis untuk semua developer di tim, karena definisinya tertulis eksplisit di file Compose, bukan tergantung apa yang terinstal di mesin masing-masing. Ketiga, konfigurasi local dan production terpisah dengan jelas — file Compose untuk dev tidak akan pernah tercampur dengan konfigurasi yang dipakai di production. Keempat, setup manual berulang di mesin baru jadi tidak diperlukan lagi.
Dampak praktisnya terasa paling jelas saat onboarding engineer baru. Tanpa Docker Compose, engineer baru biasanya perlu menginstal Go versi tertentu, menyiapkan PostgreSQL lokal, mengatur environment variable satu per satu, dan sering berakhir dengan environment yang sedikit berbeda dari rekan setimnya. Dengan Docker Compose, seluruh proses itu berkurang menjadi satu perintah docker compose up.
Contoh Struktur Proyek
Sebelum menulis konfigurasi Compose, ada baiknya menyamakan struktur project terlebih dahulu. Berikut struktur sederhana untuk project Gin yang akan dipakai sebagai acuan di seluruh artikel ini:
.
├── docker-compose.yml
├── Dockerfile
├── .env
├── go.mod
├── go.sum
├── main.go
└── internal/
├── handler
├── service
└── repository
Struktur ini memisahkan internal/handler untuk layer HTTP, internal/service untuk business logic, dan internal/repository untuk akses data — pemisahan layer yang umum dipakai di project Gin berskala menengah. docker-compose.yml dan Dockerfile ditaruh di root project supaya context build mencakup seluruh source code tanpa konfigurasi path tambahan.
File .env di root menyimpan environment variable yang dipakai aplikasi saat runtime — misalnya connection string database, secret key, atau flag konfigurasi lainnya. Memisahkan .env dari kode aplikasi punya dua manfaat: pertama, kredensial tidak ikut ter-commit ke version control (asalkan .env masuk ke .gitignore); kedua, nilai environment variable bisa berbeda antara local, staging, dan production tanpa mengubah satu baris kode pun, karena aplikasi cukup membaca dari environment yang tersedia saat container berjalan.
Dockerfile untuk Aplikasi Gin
Untuk local development, Dockerfile yang dibutuhkan cukup sederhana — tidak perlu multi-stage build atau optimasi ukuran image, karena image ini hanya dipakai secara lokal di mesin developer.
FROM golang:1.21
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o app
EXPOSE 8080
CMD ["./app"]
Beberapa hal yang perlu diperhatikan dari Dockerfile ini:
- Base image memakai official Go image (
golang:1.21), bukan varian Alpine yang lebih ringan, karena ukuran image bukan prioritas di stage ini - Dependency di-cache lewat
go.moddango.sum— keduanya disalin dan di-download sebelum source code lain, sehingga Docker bisa memanfaatkan layer cache dan tidak perlu mengunduh ulang dependency setiap kali ada perubahan kode - Build binary dilakukan di dalam container, bukan di host, sehingga versi Go yang dipakai untuk build selalu konsisten dengan yang didefinisikan di image
Untuk production, Dockerfile ini biasanya dimodifikasi menjadi multi-stage build agar ukuran image lebih kecil dan lebih aman. Pembahasan detailnya ada di artikel terpisah tentang Dockerfile untuk berbagai stage.
docker-compose.yml untuk Local Development
Berikut contoh konfigurasi Docker Compose paling sederhana untuk menjalankan aplikasi Gin:
version: '3.9'
services:
api:
build: .
container_name: gin-api
ports:
- "8080:8080"
volumes:
- .:/app
env_file:
- .env
command: ./app
Setiap baris konfigurasi punya peran spesifik. build: . memberitahu Compose untuk membangun image dari Dockerfile yang ada di direktori saat ini. ports: - "8080:8080" memetakan port 8080 di container ke port 8080 di host, sehingga API bisa diakses dari browser atau tool seperti curl di mesin lokal. volumes: - .:/app menyinkronkan seluruh isi direktori project di host ke direktori /app di dalam container — baris inilah yang membuat perubahan kode di editor langsung tercermin di dalam container tanpa rebuild image. env_file: - .env memuat environment variable dari file .env di host, sehingga kredensial dan konfigurasi tidak perlu ditulis langsung di file Compose.
Volume sangat penting di tahap ini. Tanpa baris volumes, perubahan kode hanya akan tersimpan di image hasil build terakhir — kamu harus menjalankan docker compose up --build setiap kali mengubah satu baris kode, yang jelas tidak praktis untuk iterasi cepat.
Menjalankan Aplikasi
Untuk menjalankan aplikasi pertama kali atau setelah mengubah Dockerfile, gunakan flag --build agar Compose membangun ulang image sebelum menjalankan container:
docker compose up --build
Jika image sudah pernah di-build sebelumnya dan tidak ada perubahan pada Dockerfile atau dependency, kamu bisa langsung menjalankan tanpa flag tersebut:
docker compose up
Setelah container berjalan, API akan tersedia di:
http://localhost:8080
Perbedaan antara kedua perintah ini penting untuk dipahami: --build selalu memicu proses build image dari awal (atau menggunakan cache layer kalau tidak ada perubahan), sementara tanpa --build, Compose langsung menjalankan container dari image yang sudah ada sebelumnya — lebih cepat, tapi berisiko menjalankan versi kode yang sudah usang kalau Dockerfile-nya berubah.
Secara default, docker compose up berjalan di foreground — terminal akan menampilkan log dari semua container secara real-time, dan container akan berhenti begitu kamu menekan Ctrl+C. Untuk menjalankan di background, tambahkan flag -d (detached):
docker compose up -d
Saat berjalan di mode detached, kamu tetap bisa memantau log kapan saja dengan:
docker compose logs -f api
Flag -f (follow) membuat log terus mengalir secara real-time, mirip seperti tail -f pada file log biasa. Kombinasi up -d dan logs -f ini sering jadi workflow harian: jalankan semua service di background sekali di pagi hari, lalu pantau log spesifik service yang sedang dikerjakan.
Hot Reload (Opsional tapi Sangat Direkomendasikan)
Rebuild manual setiap kali ada perubahan kode sangat tidak efisien untuk local development, bahkan dengan volume mount sekalipun — proses ./app yang sudah berjalan tidak akan otomatis membaca ulang kode yang berubah. Solusinya adalah tool hot reload seperti air, yang memonitor perubahan file dan otomatis me-restart aplikasi begitu ada file yang disimpan.
Instal air di dalam Dockerfile:
go install github.com/air-verse/air@latest
Lalu ubah bagian command di Docker Compose agar menjalankan air sebagai pengganti binary langsung:
command: air
sequenceDiagram
participant Editor as Editor (host)
participant Volume as Volume Mount
participant Air as air (container)
participant App as Aplikasi Gin
Editor->>Volume: Simpan perubahan file .go
Volume->>Air: File berubah terdeteksi
Air->>App: Stop proses lama
Air->>App: go build + restart
App-->>Editor: Endpoint siap diakses ulangDengan kombinasi air dan volume mount, workflow-nya jadi: file berubah di editor, air mendeteksi perubahan melalui volume yang ter-mount, lalu otomatis me-restart aplikasi dengan kode terbaru. Workflow development jadi jauh lebih cepat dibanding harus menjalankan docker compose up --build berulang kali.
airmembaca konfigurasi dari file.air.tomldi root project. Kalau file ini tidak ada,airakan memakai konfigurasi default yang umumnya sudah cukup untuk project sederhana — tapi tetap disarankan membuat.air.tomlsendiri untuk mengatur direktori yang di-exclude (sepertivendoratautmp) agar proses watcher tidak ikut memonitor file yang tidak relevan.
Contoh Menambahkan Database (PostgreSQL)
Salah satu keuntungan terbesar Docker Compose adalah kemudahan menambah dependency baru. Misalnya, untuk menambahkan PostgreSQL sebagai database, cukup tambahkan satu service baru:
services:
api:
build: .
ports:
- "8080:8080"
depends_on:
- db
env_file:
- .env
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: app_db
ports:
- "5432:5432"
Service api sekarang punya depends_on: - db, yang memastikan container db mulai lebih dulu sebelum container api dijalankan. Service db sendiri memakai image resmi postgres:16 dari Docker Hub, tanpa perlu membuat Dockerfile khusus — environment variable POSTGRES_USER, POSTGRES_PASSWORD, dan POSTGRES_DB otomatis dibaca oleh image ini untuk membuat user dan database saat container pertama kali start.
Dengan pola ini, menambah dependency lain seperti Redis untuk cache atau RabbitMQ untuk message queue hanya soal menambah satu service baru lagi ke file Compose yang sama — tidak perlu mengubah cara service lain dikonfigurasi.
depends_onhanya menjamin urutan start container, bukan menjamin database sudah benar-benar siap menerima koneksi. PostgreSQL butuh waktu beberapa detik untuk inisialisasi setelah container start. Kalau aplikasi langsung mencoba koneksi di awal, pertimbangkan menambahkan retry logic di sisi aplikasi atau memakaihealthcheckdi Compose untuk menunggu database benar-benar ready.
Untuk mengatasi masalah timing ini secara lebih eksplisit, Docker Compose mendukung kombinasi healthcheck dan depends_on dengan kondisi service_healthy:
services:
api:
build: .
ports:
- "8080:8080"
depends_on:
db:
condition: service_healthy
env_file:
- .env
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: app_db
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 5s
timeout: 5s
retries: 5
Dengan konfigurasi ini, Compose akan menjalankan pg_isready setiap 5 detik untuk memeriksa apakah PostgreSQL sudah siap menerima koneksi. Container api tidak akan dijalankan sampai db benar-benar berstatus healthy, bukan hanya sekadar sudah running. Pendekatan ini jauh lebih robust dibanding depends_on biasa, terutama untuk database yang butuh waktu inisialisasi lebih lama atau saat menjalankan beberapa service yang saling bergantung sekaligus.
Best Practice untuk Local Development
Beberapa praktik berikut membantu menjaga setup Docker Compose tetap sehat dan tidak menimbulkan masalah di kemudian hari:
- Gunakan Docker Compose hanya untuk local — jangan dipakai langsung di production tanpa modifikasi
- Pisahkan file konfigurasi untuk production, baik itu Dockerfile maupun Compose-nya sendiri
- Jangan commit file
.envke version control, karena biasanya berisi kredensial dan konfigurasi sensitif - Gunakan volume agar tidak perlu rebuild terus-menerus setiap ada perubahan kode
- Jangan over-optimasi Dockerfile untuk dev — multi-stage build dan minimasi layer adalah kebutuhan production, bukan local development
Prinsip yang menyatukan semua poin ini sederhana: local development harus cepat dan nyaman, bukan sempurna. Optimasi yang berguna di production justru bisa menghambat produktivitas kalau diterapkan di tahap yang salah.
Selain kelima poin di atas, ada beberapa kebiasaan kecil yang juga membantu menjaga setup tetap rapi dalam jangka panjang:
| Kebiasaan | Alasan |
|---|---|
Sediakan .env.example | Memberi acuan environment variable apa saja yang dibutuhkan, tanpa membocorkan nilai aslinya |
| Beri nama service yang deskriptif | api, db, cache lebih mudah dipahami dibanding service1, service2 |
| Bersihkan volume yang tidak terpakai | docker compose down -v menghapus volume sekaligus, berguna saat ingin reset state database lokal |
| Dokumentasikan port yang dipakai | Mencegah konflik port antar project yang berjalan bersamaan di mesin yang sama |
.env.example khususnya penting untuk onboarding — file ini berisi nama variable tanpa nilai sensitif (misalnya DB_PASSWORD= kosong atau dengan placeholder), sehingga engineer baru tahu variable apa yang perlu diisi tanpa harus menebak-nebak atau bertanya ke rekan setim.
Ringkasan
- Docker Compose menyatukan API dan seluruh dependency-nya (database, cache, dll) dalam satu file yang bisa dijalankan dengan satu perintah.
- Volume mount (
volumes: - .:/app) adalah kunci agar perubahan kode di host langsung tercermin di dalam container.- Kombinasikan volume mount dengan tool hot reload seperti
airagar aplikasi otomatis restart setiap ada perubahan kode.- Menambah dependency baru (database, cache, message queue) cukup dengan menambah satu service baru di file Compose yang sama.
depends_onhanya mengatur urutan start container, bukan menjamin service sudah siap menerima request.- Jangan commit file
.env, dan jangan pakai konfigurasi Compose yang sama untuk local dan production.- Local development memprioritaskan kecepatan iterasi — hindari optimasi yang sebenarnya hanya relevan untuk production.