M
Mr Sugiarto
Developer
04 Sep 2026 5 min read

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 (WaitingHealthy) 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!

M
Mr Sugiarto

Developer

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