Kembali ke Artikel

Belajar Golang dari Nol #15: Deploy API Go ke Server Production

Solusi IT

Di Belajar Golang dari Nol #14 kita menutup API produk dengan testing. Database sudah jalan, middleware auth sudah pasang, dan test-nya hijau semua. Tapi API itu masih hidup di satu tempat: laptop kamu.

Bagian ini kita pindahkan ke server. Target akhirnya jelas. API bisa diakses dari internet lewat domain, pakai HTTPS, tetap hidup walau SSH ditutup, dan otomatis nyala lagi kalau server reboot.

Saya tulis langkahnya berurutan. Ikuti pelan-pelan. Setiap langkah ada cara mengeceknya sebelum lanjut ke langkah berikutnya.

Kenapa deploy aplikasi Go itu jauh lebih santai

Ingat bagian 1 waktu kita bahas kenapa Go dipilih? Salah satu alasannya muncul persis di titik ini. Go itu bahasa yang dikompilasi jadi satu file binary. Bukan script yang butuh interpreter, bukan bytecode yang butuh virtual machine.

Artinya di server kamu tidak perlu install Go. Tidak perlu install runtime apa pun. Tidak ada folder vendor yang harus ikut diupload. Tidak ada perintah install dependency di server yang bisa gagal karena versi beda.

Bandingkan dengan aplikasi PHP atau Node.js. Di sana kamu perlu install PHP dengan ekstensi yang cocok, atau Node dengan versi yang cocok, lalu jalankan composer install atau npm install di server. Setiap komponen itu satu kemungkinan gagal.

Di Go, yang kamu kirim ke server cuma satu file. Itu saja. Sisanya tinggal mengatur cara menjalankannya.

Build binary untuk server Linux

Server kamu hampir pasti pakai Linux dengan arsitektur amd64. Laptop kamu mungkin macOS, mungkin Windows, mungkin Linux juga. Tidak masalah. Go bisa membuat binary untuk sistem lain langsung dari laptop kamu. Ini namanya cross compile.

Caranya cukup mengatur dua variabel sebelum perintah build:

GOOS=linux GOARCH=amd64 go build -o api-produk

GOOS menentukan sistem operasi target, GOARCH menentukan arsitektur prosesornya. Hasilnya file bernama api-produk yang siap jalan di server Linux, walaupun kamu build dari MacBook.

Kalau server kamu pakai prosesor ARM, misalnya beberapa VPS murah sekarang, ganti jadi GOARCH=arm64. Cek dulu di server dengan perintah uname -m. Kalau hasilnya x86_64 berarti amd64. Kalau aarch64 berarti arm64.

Di Windows PowerShell sintaksnya sedikit berbeda:

$env:GOOS="linux"; $env:GOARCH="amd64"; go build -o api-produk

Mengecilkan ukuran binary

Binary Go biasanya berukuran belasan sampai puluhan megabyte. Itu wajar karena semua yang dibutuhkan sudah ikut di dalamnya. Kalau mau lebih ramping, buang informasi debug:

GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o api-produk

Flag -s membuang tabel simbol, -w membuang informasi DWARF. Ukuran biasanya turun sekitar 25 sampai 30 persen. Ini opsional. Efek sampingnya, stack trace saat panic jadi kurang detail. Untuk API kecil yang lognya sudah rapi, ini pertukaran yang masuk akal.

Siapkan kode untuk production

Sebelum diupload, ada satu hal yang harus dibereskan di kode. Selama 14 bagian sebelumnya kita menulis port dan koneksi database langsung di dalam kode. Untuk belajar itu tidak apa-apa. Untuk production itu masalah.

Kenapa? Karena kredensial database production tidak boleh masuk ke Git. Dan karena port di server bisa saja berbeda dengan di laptop. Kalau nilai-nilai ini di-hardcode, kamu harus edit kode dan build ulang setiap kali konfigurasinya berubah.

Solusinya pakai environment variable. Bikin satu helper kecil yang membaca env dengan nilai cadangan:

func getEnv(key, fallback string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return fallback
}

Lalu pakai di fungsi main:

func main() {
	port := getEnv("PORT", "8080")
	dsn := getEnv("DB_DSN", "root:@tcp(127.0.0.1:3306)/toko?parseTime=true")

	db, err := sql.Open("mysql", dsn)
	if err != nil {
		log.Fatal("gagal koneksi database: ", err)
	}
	defer db.Close()

	if err := db.Ping(); err != nil {
		log.Fatal("database tidak merespons: ", err)
	}

	log.Printf("server jalan di port %s", port)
	log.Fatal(http.ListenAndServe(":"+port, router(db)))
}

Sekarang di laptop kamu tetap bisa jalan tanpa set apa-apa karena ada nilai default. Di server, nilai aslinya nanti diisi lewat file environment yang kita buat sebentar lagi.

Satu catatan penting. Jangan lupa db.Ping(). Fungsi sql.Open tidak benar-benar menyambung ke database, dia cuma menyiapkan koneksi. Tanpa Ping, aplikasi kamu akan terlihat sukses jalan padahal kredensialnya salah.

Kirim binary ke server

Sekarang upload. Pakai scp dari folder tempat binary tadi dibuat:

scp api-produk deploy@103.10.20.30:/home/deploy/

Ganti deploy dengan user di server kamu dan IP-nya dengan IP VPS kamu. Setelah selesai, masuk ke server:

ssh deploy@103.10.20.30

Beri izin eksekusi, lalu jalankan langsung untuk tes pertama:

chmod +x /home/deploy/api-produk
DB_DSN="user:password@tcp(127.0.0.1:3306)/toko?parseTime=true" ./api-produk

Kalau muncul log “server jalan di port 8080”, berarti binary-nya sehat. Biarkan terminal itu terbuka. Buka terminal baru di laptop kamu dan tes dari luar:

curl http://103.10.20.30:8080/produk

Kalau balasannya JSON produk, selamat, API kamu sudah hidup di internet. Kalau timeout, kemungkinan besar firewall server belum membuka port 8080. Tidak perlu dibuka permanen, karena sebentar lagi Nginx yang akan menangani lalu lintas dari luar.

Sekarang tekan Ctrl+C di server untuk mematikannya. Kita akan jalankan dengan cara yang benar.

Bikin API tetap hidup dengan systemd

Masalah dari ./api-produk tadi jelas. Begitu SSH ditutup, prosesnya ikut mati. Kalau aplikasi crash, tidak ada yang menghidupkan lagi. Kalau server reboot, API kamu diam saja.

Jawabannya systemd. Ini pengelola service bawaan hampir semua Linux modern. Dia yang akan menyalakan, mengawasi, dan menghidupkan ulang aplikasi kamu.

Pertama, simpan kredensial di file environment terpisah:

sudo nano /etc/api-produk.env

Isinya begini, satu baris satu variabel, tanpa spasi di sekitar tanda sama dengan dan tanpa tanda kutip:

PORT=8080
DB_DSN=user:password@tcp(127.0.0.1:3306)/toko?parseTime=true

Kunci filenya supaya tidak bisa dibaca user lain:

sudo chmod 640 /etc/api-produk.env
sudo chown root:deploy /etc/api-produk.env

Sekarang buat file unit systemd:

sudo nano /etc/systemd/system/api-produk.service

Isi lengkapnya:

[Unit]
Description=API Produk Go
After=network.target mysql.service

[Service]
Type=simple
User=deploy
Group=deploy
WorkingDirectory=/home/deploy
EnvironmentFile=/etc/api-produk.env
ExecStart=/home/deploy/api-produk
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Mari bedah bagian yang penting.

  • After=network.target menunda start sampai jaringan siap.
  • User=deploy menjalankan aplikasi sebagai user biasa, bukan root. Ini penting untuk keamanan. Kalau aplikasi kamu dibobol, penyerang tidak langsung dapat akses root.
  • WorkingDirectory menentukan folder kerja. Berguna kalau aplikasi kamu membaca file relatif, misalnya template atau file migrasi.
  • EnvironmentFile menyuntikkan isi file env tadi ke proses.
  • ExecStart harus pakai path absolut. Systemd tidak mengenal path relatif.
  • Restart=always menghidupkan ulang aplikasi kalau mati karena alasan apa pun. RestartSec=5 memberi jeda 5 detik supaya tidak restart membabi buta saat database sedang bermasalah.
  • WantedBy=multi-user.target yang membuat service ikut nyala saat server boot.

Muat ulang konfigurasi, aktifkan, lalu jalankan:

sudo systemctl daemon-reload
sudo systemctl enable api-produk
sudo systemctl start api-produk
sudo systemctl status api-produk

Perintah status harus menampilkan active (running) berwarna hijau. Kalau merah, jangan panik. Baca lognya:

sudo journalctl -u api-produk -n 50 --no-pager

Tambahkan -f kalau mau memantau log secara langsung sambil mengetes API:

sudo journalctl -u api-produk -f

Semua yang kamu tulis dengan log.Printf di kode Go akan muncul di sini. Ini yang menggantikan terminal yang biasa kamu lihat waktu development.

Nginx di depan sebagai reverse proxy

API kamu sekarang jalan di port 8080. Tapi orang tidak mau mengetik nomor port. Mereka mau mengetik nama domain.

Di sini Nginx masuk. Dia duduk di depan, menerima permintaan di port 80 dan 443, lalu meneruskannya ke aplikasi Go di port 8080. Ada tiga alasan kenapa ini layak:

  • Port 80 dan 443 butuh hak root. Aplikasi Go kamu jalan sebagai user biasa, jadi tidak boleh memakainya langsung.
  • Nginx yang mengurus sertifikat TLS. Kode Go kamu tidak perlu tahu soal HTTPS sama sekali.
  • Satu server bisa menampung banyak domain dan banyak aplikasi sekaligus.

Buat konfigurasinya:

sudo nano /etc/nginx/sites-available/api-produk

Isi dengan server block sederhana:

server {
    listen 80;
    server_name api.domainkamu.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Empat header itu bukan formalitas. Tanpa X-Real-IP dan X-Forwarded-For, aplikasi Go kamu akan melihat semua pengunjung datang dari 127.0.0.1. Rate limiting dan log akses jadi tidak berguna.

Aktifkan konfigurasinya, cek dulu sintaksnya, baru muat ulang:

sudo ln -s /etc/nginx/sites-available/api-produk /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Selalu jalankan nginx -t sebelum reload. Kalau ada typo, dia akan bilang di baris berapa, dan Nginx yang sedang jalan tidak ikut tumbang.

Pastikan domain kamu sudah diarahkan ke IP server lewat DNS record tipe A sebelum lanjut. Tes dengan curl http://api.domainkamu.com/produk.

Pasang HTTPS gratis dengan Certbot

API tanpa HTTPS itu tidak layak dipakai. Token auth yang kita buat di bagian 13 akan melintas dalam bentuk teks polos. Untungnya sertifikat gratis dari Let’s Encrypt cuma butuh dua perintah.

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d api.domainkamu.com

Certbot akan minta email, minta persetujuan, lalu menawarkan redirect otomatis dari HTTP ke HTTPS. Pilih ya. Setelah selesai, dia mengedit sendiri file Nginx tadi dan menambahkan blok listen 443 lengkap dengan path sertifikat.

Sertifikat Let’s Encrypt berlaku 90 hari. Certbot sudah memasang timer perpanjangan otomatis saat instalasi, jadi kamu tidak perlu mengingat tanggalnya. Kalau mau memastikan mekanismenya jalan:

sudo certbot renew --dry-run

Update versi tanpa drama

Aplikasi kamu akan berubah. Alurnya cuma tiga langkah dan selalu sama:

GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o api-produk
scp api-produk deploy@103.10.20.30:/home/deploy/
ssh deploy@103.10.20.30 "sudo systemctl restart api-produk"

Downtime-nya sangat pendek, biasanya di bawah satu detik. Alasannya kembali ke sifat Go tadi. Systemd cuma perlu mematikan satu proses dan menyalakan satu binary. Tidak ada kompilasi di server, tidak ada cache yang harus dihangatkan, tidak ada dependency yang diunduh ulang.

Satu tips kecil. Kalau scp gagal karena file sedang dipakai, matikan service dulu sebelum upload, atau upload dengan nama sementara lalu ganti nama setelah service berhenti.

Kamu mungkin pernah dengar Docker sebagai alternatif. Docker memang jalan lain yang valid, terutama kalau aplikasi kamu butuh banyak layanan pendamping atau kamu berencana pindah ke Kubernetes. Tapi untuk satu API Go di satu VPS, systemd sudah cukup dan jauh lebih sedikit bagian yang harus dipelajari. Simpan Docker untuk saat kebutuhannya benar-benar muncul.

Checklist sebelum bilang selesai

Lima hal ini yang sering terlewat. Cek satu per satu:

  • Environment variable terpasang. Jalankan sudo systemctl show api-produk -p Environment atau cek langsung apakah aplikasi berhasil konek ke database production, bukan ke default.
  • Log terbaca. Pastikan journalctl -u api-produk memunculkan sesuatu saat kamu mengirim request. Kalau log kosong, kamu buta saat ada masalah.
  • Service nyala saat reboot. Jangan cuma percaya. Uji dengan sudo reboot, tunggu sebentar, lalu cek statusnya lagi. Kalau lupa systemctl enable, di sinilah ketahuannya.
  • Backup database berjalan. Minimal cron harian yang menjalankan mysqldump ke folder terpisah. Backup yang belum pernah dicoba direstore itu belum bisa disebut backup.
  • Firewall rapat. Buka hanya port 22, 80, dan 443. Port 8080 dan port database tidak boleh terlihat dari internet. Cek dengan sudo ufw status.

Sejauh mana tutorial ini berlaku

Saya perlu jujur soal batasnya. Cara di atas cocok untuk API kecil sampai menengah yang jalan di satu VPS. Itu mencakup sebagian besar aplikasi internal, dashboard perusahaan, dan produk yang baru mulai. Banyak sistem berjalan bertahun-tahun dengan setup persis seperti ini.

Yang tidak dibahas di sini adalah situasi skala besar. Kalau kamu butuh beberapa server di belakang load balancer, deploy tanpa downtime sama sekali, rollback otomatis saat error rate naik, atau pipeline CI yang build dan deploy sendiri setiap kali kode di-merge, itu topik lain dengan alat lain. Silakan pindah ke sana kalau memang trafiknya sudah menuntut, bukan sebelum itu.

Sekarang API produk kamu sudah hidup di server dengan domain dan HTTPS. Di bagian 16 kita kembali ke kode: Struktur Project Go yang Rapi untuk Aplikasi Nyata. Semua file yang selama ini menumpuk di satu folder akan kita tata supaya masih enak dibaca enam bulan lagi.

Kalau kamu butuh bantuan membangun dan mengelola API produksi untuk kebutuhan bisnis, tim Arrazy Inovasi mengerjakan pengembangan sistem dan aplikasi dari perencanaan sampai deploy di server.

BACA JUGA

Artikel Lainnya di Kategori Golang

Ingin Membaca Artikel Lainnya?

Temukan lebih banyak insight dan tips tentang teknologi dan bisnis digital.

Lihat Semua Artikel