Skip to content

Build Docker Image di Luar VPS Saat Registry Resmi Tertinggal

Adityo Guni Waluyo

Image Docker Hub tertinggal dari rilis npm. Bangun image di mesin besar, kirim via save, gzip, dan scp, lalu load di VPS kecil tanpa build.

Ringkasan

Rilis npm 9router 0.5.81 udah keluar tapi image Docker Hub-nya masih mentok di 0.5.75. Karena RAM server cuma 964MB dan gak mungkin build Next.js di situ, imagenya dibangun di mesin lokal terus dikirim pakai docker save, gzip, dan scp. Container lama cukup di-rename, jadi rollback gampang kalau yang baru bermasalah.

Kemarin sore saya mengecek rilis 9router 0.5.81 yang sudah masuk npm, lalu menjalankan docker pull di salah satu server armada. Image yang turun ternyata masih 0.5.75: Docker Hub-nya tertinggal, npm jalan dulu, image resminya menyusul belakangan. Dulu saya mungkin memilih menunggu dan mengulang pull besok pagi. Kali ini tidak, karena perbaikan yang saya tunggu ada di 0.5.81, dan saya tidak menemukan dokumentasi resmi yang menjelaskan kapan image di Hub menyusul rilis npm.

Dua pilihan yang terpikir: menunggu, atau build langsung di server itu. Pilihan kedua mati secepat teringat spesifikasinya. RAM-nya 964MB, dan build aplikasi Next.js butuh memori bergigabyte. Di server segitu, OOM killer akan menembak proses build jauh sebelum kompilasi selesai.

Jebakan registry kustom di Dockerfile

Sebelum bicara pola transfer, ini jebakan build yang memakan waktu paling lama. Dockerfile upstream mengarahkan instalasi paket ke registry.npmmirror.com, dan webpack berhenti dengan error module not found src/lib/db/driver.js. Error-nya menyesatkan, karena file itu ada di source. Biang keroknya ada di registry: setelah instalasi dikembalikan ke npm install biasa yang menarik paket dari registry bawaan npm di registry.npmjs.org, build berjalan sampai selesai tanpa drama. Saya tidak bisa memastikan paket mana yang bermasalah di mirror itu. Yang saya pegang polanya: kalau Dockerfile sebuah proyek memaksa mirror pihak ketiga dan build gagal dengan error yang tidak masuk akal, registry adalah tersangka pertama. Kenapa upstream memakai mirror itu, sampai sekarang tidak ada penjelasan resminya, dan saya cukup mencatatnya sebagai gotcha build.

Pola transfer: save, gzip, scp, load

Build saya jalankan di mesin lokal yang memorinya jauh lebih lega. Dari situ image dikemas dengan docker image save, perintah yang menghasilkan arsip tar berisi semua parent layer image. Dipipa ke gzip, ukurannya menyusut ke 207MB, cukup kecil untuk sekali kirim.

Kirim ke server memakai scp. Sejak OpenSSH 9.0, scp memakai protokol SFTP untuk transfernya, jadi file sebesar ini berjalan di atas mekanisme yang sama dengan sesi SFTP biasa. Di sisi server, docker image load membaca arsip itu dalam keadaan masih terkompresi gzip, tanpa perlu ekstrak manual, lalu mengembalikan image beserta tag-nya persis seperti semula.

docker build -t app-custom:0.5.81 .
docker save app-custom:0.5.81 | gzip > app-0.5.81.tgz
scp app-0.5.81.tgz [email protected]:/tmp/
ssh [email protected] "docker load < /tmp/app-0.5.81.tgz"

Rollback instan dan konsekuensi ke playbook

Container lama tidak langsung dihapus. Urutannya: stop container lama, rename menjadi 9router-old, lalu jalankan container baru dengan flag identik. Bind mount data sama, port sama, restart policy unless-stopped, batas log json-file sama.

docker stop 9router && docker rename 9router 9router-old
docker run -d --name 9router --restart unless-stopped \
  -p 20128:20128 -v /root/.9router:/app/data \
  app-custom:0.5.81

Policy unless-stopped di urutan ini punya peran kecil tapi menentukan. Container yang di-stop secara manual tidak akan dibangunkan lagi walau daemon Docker restart, jadi container lama yang sudah di-rename tidak bangkit sendiri di balik punggung. Rollback jadi pekerjaan hitungan detik: kalau container baru bermasalah, stop, rename balik, selesai.

Verifikasinya singkat. curl ke port aplikasi mengembalikan HTTP 307 ke dashboard, dan endpoint versi menunjukkan 0.5.81 dengan hasUpdate false. Baru setelah dua tanda itu muncul, image lama dari Hub saya hapus supaya disk tidak sessak. Server ini punya riwayat disk kritis karena Docker build cache, jadi image menganggur selalu saya bereskan secepatnya.

Satu konsekuensi yang gampang terlewat: playbook infrastruktur yang membandingkan digest image lokal dengan Hub harus me-skip server ini mulai sekarang. Digest image hasil build sendiri dijamin berbeda dari image Hub. Kalau playbook memaksa pull ulang, dia akan menimpa image custom dengan versi Hub yang lebih tua. Server bergambar custom harus keluar dari scope playbook sampai Hub menyusul.

Setelah rollout ini, pola jadi default untuk aplikasi self-hosted di VPS kecil: build di mesin yang resource-nya cukup, kirim imagenya, dan biarkan server kecil fokus pada satu pekerjaan, menjalankan image yang sudah jadi.

Artikel terkait