Ini part terakhir di seri "Belajar Goravel Framework". Setelah API kita lengkap dengan CRUD, auth, relasi, caching, queue, rate limiting, sampai dokumentasi, saatnya kita bahas cara men-deploy aplikasi ini pakai Docker — mulai dari Dockerfile, docker-compose, sampai beberapa hal yang gampang kelewat saat pertama kali deploy aplikasi Go ke container.
Dockerfile Multi-Stage
Project kita sejak awal sudah punya Dockerfile bawaan dari Goravel — memakai pola multi-stage build, umum dipakai untuk aplikasi Go supaya image akhirnya kecil:
FROM golang:alpine AS builder
ENV GO111MODULE=on \
CGO_ENABLED=0
WORKDIR /build
COPY . .
RUN go mod tidy
RUN go build --ldflags "-s -w -extldflags -static" -o main .
FROM alpine:latest
WORKDIR /www
COPY --from=builder /build/main /www/
COPY --from=builder /build/public/ /www/public/
COPY --from=builder /build/storage/ /www/storage/
COPY --from=builder /build/resources/ /www/resources/
# Config disuplai lewat environment variable saat runtime (lihat docker-compose.yml),
# bukan di-bundle ke image — .env sengaja TIDAK di-copy ke sini supaya secret
# tidak pernah tersimpan permanen di layer image.
ENTRYPOINT ["/www/main"]
Stage pertama (builder) meng-compile binary Go dengan CGO_ENABLED=0 dan flag -static, menghasilkan binary yang benar-benar berdiri sendiri tanpa dependency library sistem. Stage kedua cuma menyalin binary hasil compile plus folder public, storage, resources ke image alpine yang jauh lebih kecil dari image Go lengkap — image akhir yang kita deploy tidak perlu membawa seluruh toolchain Go.
Satu penyesuaian penting yang kita lakukan: baris COPY --from=builder /build/.env /www/.env dihapus. Bawaannya, Dockerfile ini meng-copy file .env (isinya password database, JWT secret, dsb) langsung ke dalam image — artinya siapa pun yang punya akses ke image tersebut otomatis punya akses ke semua secret-nya, bahkan setelah kita "hapus" configurasinya nanti (image layer bersifat immutable). Konfigurasi sebaiknya selalu disuntikkan saat container dijalankan, bukan dibakar ke dalam image.
docker-compose.yml
services:
goravel:
build:
context: .
ports:
- "3000:3000"
environment:
APP_KEY: ${APP_KEY}
APP_HOST: 0.0.0.0
APP_PORT: 3000
DB_CONNECTION: mysql
DB_HOST: mysql
DB_PORT: 3306
DB_DATABASE: goravel_blogdb
DB_USERNAME: root
DB_PASSWORD: ${DB_PASSWORD:-password}
JWT_SECRET: ${JWT_SECRET}
QUEUE_CONNECTION: database
depends_on:
mysql:
condition: service_healthy
restart: always
mysql:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-password}
MYSQL_DATABASE: goravel_blogdb
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${DB_PASSWORD:-password}"]
interval: 3s
timeout: 5s
retries: 20
restart: always
volumes:
mysql-data:
Beberapa poin penting yang perlu diperhatikan:
1. APP_HOST harus 0.0.0.0, bukan 127.0.0.1
Ini jebakan klasik saat pertama kali deploy aplikasi Go (atau bahasa lain) ke Docker. Kalau APP_HOST diset ke 127.0.0.1, aplikasi hanya menerima koneksi dari dalam container itu sendiri — port mapping 3000:3000 di docker-compose tidak akan berguna sama sekali, request dari luar container tidak akan pernah sampai. Waktu kami coba langsung tanpa mengubah nilai ini, hasilnya persis begitu: container terlihat "jalan normal" di log, tapi curl dari host selalu gagal connect. Begitu APP_HOST diubah ke 0.0.0.0 (artinya "terima koneksi dari semua network interface"), langsung bisa diakses.
2. APP_KEY wajib diisi
Goravel akan menolak start sama sekali kalau APP_KEY kosong, dengan error Please initialize APP_KEY first. Generate value acak sepanjang 32 karakter, lalu simpan sebagai environment variable — jangan hardcode di docker-compose.yml, cukup referensikan ${APP_KEY} dan sediakan nilainya saat menjalankan compose.
3. Service mysql terpisah, dengan volume persisten
Supaya stack ini benar-benar bisa langsung dijalankan tanpa setup database manual, kita tambahkan service mysql di compose yang sama, dengan volumes: mysql-data:/var/lib/mysql supaya data tidak hilang setiap container di-restart.
4. depends_on saja tidak cukup — butuh healthcheck
Ini jebakan kedua yang baru ketahuan waktu testing beneran: depends_on: [mysql] cuma menjamin container mysql sudah di-start duluan, bukan berarti MySQL di dalamnya sudah siap menerima koneksi. MySQL butuh beberapa detik untuk inisialisasi setelah proses container-nya jalan. Kalau goravel langsung start bareng-bareng, dia akan mencoba connect duluan dan gagal:
WARNING [db] init mysql connection error: dial tcp 172.19.0.2:3306: connect: connection refused
Solusinya, tambahkan healthcheck di service mysql (pakai mysqladmin ping), lalu ubah depends_on di service goravel supaya menunggu MySQL benar-benar healthy, bukan sekadar started:
depends_on:
mysql:
condition: service_healthy
Dengan ini, docker compose up akan terlihat menunggu sebentar di status MySQL (Waiting → Healthy) sebelum container goravel mulai di-start — dan warning koneksi di atas hilang sama sekali.
Deploy
Siapkan environment variable yang dibutuhkan, lalu jalankan:
export APP_KEY=$(openssl rand -base64 24 | tr -d '=+/' | head -c 32)
export JWT_SECRET=$(openssl rand -hex 32)
export DB_PASSWORD=ganti-dengan-password-kuat
docker compose up -d --build
Setelah container pertama kali jalan, database masih kosong — jalankan migration di dalam container:
docker compose exec goravel /www/main artisan migrate
Testing
Cek apakah API sudah bisa diakses dari luar container:
curl http://127.0.0.1:3000/posts
Coba juga register user baru dan buka dokumentasi API di http://127.0.0.1:3000/docs — kalau semuanya merespon normal, deployment sudah berhasil.
Untuk deploy ke VPS sungguhan, langkah-langkahnya kurang lebih sama: install Docker & Docker Compose di VPS, clone/copy project ke server, set environment variable produksi (pakai secret yang benar-benar acak, bukan contoh di atas), lalu jalankan docker compose up -d --build. Untuk expose ke domain publik dengan HTTPS, biasanya ditambahkan reverse proxy seperti Nginx atau Caddy di depan container goravel ini.
Penutup Seri
Sampai di sini, seri "Belajar Goravel Framework" sudah membahas alur lengkap membangun REST API blog dari nol: instalasi, routing, database (migration/model/seeder), CRUD lengkap dengan validasi, autentikasi JWT beserta refresh token dan logout, relasi antar model, upload file, role & permission, testing otomatis, caching, queue, rate limiting, dokumentasi API, sampai deployment pakai Docker. Kode lengkapnya bisa dilihat di project myblog yang sudah kita bangun bertahap di seri ini — semoga bermanfaat untuk teman-teman yang ingin mulai membangun REST API dengan Goravel!
Bagian dari Series: Seri Tutorial Belajar Framework Goravel Rest API untuk Pemula
Overview Seri ini membahas cara membangun REST API menggunakan Goravel, framework web berbasis Golang yang terinspirasi dari Laravel. Materi mencakup...
Lihat Series Lengkap