Deploy aplikasi Go dimulai dari satu berkas: binary. Compiler Go membungkus kode kita, bagian standard library yang dipakai, dan runtime Go ke dalam satu file executable — tanpa installer, tanpa daftar dependensi runtime, dan tanpa interpreter. Model distribusi ini membuat langkah pertama operasional sebuah aplikasi menjadi sangat singkat: bangun binary, salin ke server, jalankan. Chapter ini membahas cara menghasilkan binary siap production dengan go build, mengatur nama outputnya, dan memverifikasi bahwa binary berdiri sendiri tanpa bergantung pada library sistem.

Program yang Akan Kita Bangun #

Berkas build-demo.go pada bagian bawah halaman adalah program mini bernama sapa yang akan kita bangun berulang kali sepanjang section ini. Program tersebut mencetak identitas dirinya supaya hasil setiap build bisa kita bandingkan:

go
package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Println("sapa v0.1.0")
	fmt.Printf("platform: %s/%s\n", runtime.GOOS, runtime.GOARCH)
	fmt.Println("compiler:", runtime.Version())
}

Output yang Dihasilkan:

text
sapa v0.1.0
platform: linux/amd64
compiler: go1.27.2

Mari kita telaah baris demi baris program di atas:

  1. Baris 1 (package main): mendeklarasikan package main, penanda modul executable — go build hanya menghasilkan binary untuk package bertipe main.
  2. Baris 3–6 (import ...): mengimpor fmt untuk mencetak output dan runtime untuk membaca identitas platform.
  3. Baris 8 (func main()): titik masuk program; baris-baris di dalamnya dijalankan saat binary dieksekusi.
  4. Baris 9 (fmt.Println("sapa v0.1.0")): mencetak banner versi — nanti diotomatisasi lewat -ldflags pada chapter berikutnya.
  5. Baris 10 (fmt.Printf(...)): mencetak kombinasi runtime.GOOS dan runtime.GOARCH, yaitu sistem operasi dan arsitektur yang menjadi target build.
  6. Baris 11 (fmt.Println("compiler:", ...)): mencetak runtime.Version(), versi toolchain yang tercetak di dalam binary, bukan dibaca saat dijalankan.

go build dan Nama Output #

Perintah go run mengompilasi program ke build cache, mengeksekusinya sekali, lalu membuang executable-nya. Perintah go build menghasilkan berkas executable permanen. Siapkan modul kecil, salin isi build-demo.go ke main.go, lalu bangun:

bash
mkdir sapa-deploy && cd sapa-deploy
go mod init example.com/sapa
# simpan isi build-demo.go sebagai main.go
go build
ls -lh sapa

Output yang Dihasilkan:

text
-rwxrwxr-x 1 z z 2.3M Oct 13 13:26 sapa

Perhatikan bahwa berkas hasil build bernama sapa, bukan main. Aturan penamaan go build mengikuti komponen terakhir dari path module (example.com/sapa → sapa), bukan nama berkas sumber. Untuk nama berbeda, gunakan flag -o:

bash
go build -o sapa-server
ls -lh sapa-server

Output yang Dihasilkan:

text
-rwxrwxr-x 1 z z 2.3M Oct 13 13:26 sapa-server

Binary hasil -o inilah yang akan kita salin ke server pada chapter-chapter berikutnya.

go build vs go install #

Perintah go install juga mengompilasi, tetapi menaruh hasilnya di direktori GOBIN (bawaannya $HOME/go/bin) alih-alih direktori kerja. Pola ini cocok untuk alat CLI yang ingin tersedia di PATH kita, bukan untuk artefak rilis:

bash
GOBIN=$PWD/bin go install
ls -lh sapa-server bin/sapa

Output yang Dihasilkan:

text
-rwxrwxr-x 1 z z 2.3M Oct 13 13:26 bin/sapa
-rwxrwxr-x 1 z z 2.3M Oct 13 13:26 sapa-server
PerintahLokasi hasilKegunaan utama
go buildDirektori kerja, atau -oArtefak yang didistribusikan ke server
go installGOBINAlat CLI pribadi di PATH developer

Binary Statis dan Portabilitas #

Binary murni Go tidak membutuhkan dynamic library sistem. Kita bisa membuktikannya dengan ldd, alat yang menampilkan dynamic library yang dibutuhkan sebuah executable:

bash
ldd sapa

Output yang Dihasilkan:

text
	not a dynamic executable

Pesan tersebut muncul di standard error dan ldd keluar dengan status 1 — keduanya normal, dan justru konfirmasi bahwa binary berdiri sendiri: satu berkas sapa bisa disalin ke server Linux x86-64 mana pun dan langsung berjalan.

Situasi berubah ketika program memakai package dengan varian cgo. Program kecil berikut hanya memanggil net.SplitHostPort, tetapi package net memuat varian cgo yang aktif bila kompiler C tersedia di mesin build:

go
package main

import (
	"fmt"
	"net"
)

func main() {
	host, port, err := net.SplitHostPort("db.internal:5432")
	if err != nil {
		fmt.Println("galat:", err)
		return
	}
	fmt.Printf("host=%s port=%s\n", host, port)
}

Bangun dua varian dengan CGO_ENABLED=1 (default di mesin berkompiler C) dan CGO_ENABLED=0, lalu bandingkan hasil ldd-nya (alamat memori dipangkas lewat awk supaya output stabil):

bash
CGO_ENABLED=1 go build -o netdemo-cgo
CGO_ENABLED=0 go build -o netdemo-static
ls -lh netdemo-cgo netdemo-static
ldd netdemo-cgo | awk '{print $1}'
ldd netdemo-static 2>&1

Output yang Dihasilkan:

text
-rwxrwxr-x 1 z z 2.6M Oct 13 13:26 netdemo-cgo
-rwxrwxr-x 1 z z 2.6M Oct 13 13:26 netdemo-static
linux-vdso.so.1
libc.so.6
/lib64/ld-linux-x86-64.so.2
	not a dynamic executable

Varian netdemo-cgo terikat pada glibc (libc.so.6 dan loader /lib64/ld-linux-x86-64.so.2), sehingga gagal berjalan di Alpine ber-musl maupun di image scratch yang tanpa library. Varian netdemo-static bekerja di keduanya; output programnya sama (host=db.internal port=5432) karena perbedaannya murni di lapisan linking.

Verifikasi Binary dan Reproducibility #

Sebelum binary didistribusikan, verifikasi formatnya dengan file dan pastikan build kita bisa direproduksi. Perintah file membaca header executable — hasil aslinya untuk sapa:

text
sapa: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=bBU1vLaeL8r_8c9_ldi1/-Xvmizuf-YIdrxTMB1ip/77u6xioSe0G0IkWAWrfr/y3Lx2Q0uWQWHrUQGgSNE, BuildID[sha1]=470466e4f03413b02127bc0e5d72eaee621eaf7e, with debug_info, not stripped

Kemudian bangun ulang dan bandingkan checksum:

bash
go build && md5sum sapa
go build && md5sum sapa

Output yang Dihasilkan:

text
0b11034d5cb167a90ae854eee95f8c1c  sapa
0b11034d5cb167a90ae854eee95f8c1c  sapa

Checksum yang identik membuktikan source dan flag yang sama menghasilkan binary byte demi byte identik pada toolchain yang sama — properti yang membuat checksum layak dilampirkan pada setiap rilis.

Binary 2.3M ini masih memuat informasi debug dan path direktori kerja — jejak yang bisa dipangkas sekaligus diisi nomor versi. Chapter Build Flags membahas keduanya.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

build-demo.go

Unduh berkas
go
package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Println("sapa v0.1.0")
	fmt.Printf("platform: %s/%s\n", runtime.GOOS, runtime.GOARCH)
	fmt.Println("compiler:", runtime.Version())
}