Ringkasan Utama
Cara rollback deploy Docker di VPS: beri setiap image tag unik seperti commit SHA, catat tag yang sedang jalan, lalu saat rilis rusak ganti IMAGE di .env ke tag sebelumnya dan jalankan docker compose up -d --wait. Verifikasi dengan docker compose ps dan curl. Rollback hanya mengembalikan kode, bukan data atau migrasi database.
Kamu baru deploy versi baru, lalu halaman utama menampilkan error. Pengunjung sudah melihatnya, dan di terminal kamu mengetik apa pun yang terlintas: git reset, build ulang, docker compose down. Sebagian besar waktu habis bukan karena rollback itu sulit, melainkan karena tidak ada yang tahu persis versi mana yang dulu berjalan baik, atau versi itu sudah tertimpa tag latest.
Panduan ini adalah langkah rollback saja, bukan panduan mendeteksi masalah: kamu akan menyiapkan sistemnya sebelum rilis (tag unik, catatan versi), lalu memakainya saat rilis rusak. Contoh memakai pola yang sama dengan setup di balik situs ini, ditulis ulang dengan placeholder, dan mengasumsikan kamu sudah punya file Compose production seperti di panduan deploy Docker Compose di production. Setiap flag dicek ke dokumentasi resmi yang tercantum di akhir.
Ingin paham konsepnya dulu?
Tulisan ini panduan praktik rollback. Lesson gratis kami menjelaskan gambaran besarnya, dari health check sampai backup, dengan diagram interaktif rilis rusak yang di-rollback: Monitoring, Rollback, dan Backup untuk Server Docker. Lesson itu juga punya kuis singkat dan latihan rollback di laptop.
Yang kamu butuhkan
Siapkan empat hal ini sebelum rilis berikutnya, bukan saat sudah rusak. Semua nama di bawah adalah placeholder, ganti dengan milikmu.
- VPS Linux dengan Docker Engine dan plugin Compose, dan akses SSH sebagai user biasa (di contoh bernama deploy).
- Aplikasi yang dijalankan lewat docker-compose.yml di folder /home/deploy/my-app, dengan tag image dibaca dari variabel IMAGE di file .env.
- Image yang di-push ke registry (di contoh ghcr.io/example-user/my-app), supaya versi lama selalu bisa ditarik lagi.
- Satu health check di file Compose. Tanpa health check, --wait tidak punya patokan untuk menyebut rilis gagal.
Langkah 1: Beri tag unik pada setiap image, jangan hanya latest
Rollback artinya menjalankan lagi versi sebelumnya yang terbukti baik. Itu hanya cepat kalau versi tersebut punya nama yang menunjuk tepat satu build. Tag latest berpindah setiap kali ada push, jadi setelah rilis rusak, latest justru menunjuk build yang rusak. Pakai commit SHA sebagai tag: tag itu tidak pernah dipakai ulang, jadi selalu berarti build yang sama.
# Wrong: one moving name. The next push overwrites the build you want back.
docker build -t ghcr.io/example-user/my-app:latest .
# Right: one tag = one build. Here the short commit hash is the tag.
TAG=$(git rev-parse --short HEAD)
docker build -t ghcr.io/example-user/my-app:$TAG .
docker push ghcr.io/example-user/my-app:$TAG
# docker-compose.yml: the tag comes from IMAGE in .env, never hard-coded
# image: ${IMAGE:-ghcr.io/example-user/my-app:latest}Pipeline situs ini mem-push setiap image dua kali, sebagai latest dan sebagai commit SHA lengkap, lalu langkah deploy menulis tag SHA itu ke file .env di server sebagai IMAGE. Dengan begitu versi yang live selalu satu build yang pasti. Contoh di atas memakai SHA pendek agar mudah diketik; di CI, biarkan pipeline yang menandai image.
Hasil yang diharapkan: Di docker image ls dan di registry, setiap build punya tag sendiri, dan tag lama tidak berubah saat build baru masuk.
Langkah 2: Catat versi yang sedang berjalan sebelum deploy
Saat panik, tag yang berjalan baik adalah hal pertama yang hilang dari ingatan. Catat sebelum mengganti apa pun. Satu baris di file teks cukup, dan jangan menyalin seluruh .env karena isinya termasuk secret.
cd /home/deploy/my-app
# Show the tag running right now and keep a copy outside .env.
# Only the IMAGE line is saved: .env also holds secrets, so never copy all of it.
grep '^IMAGE=' .env | tee -a deploy-history.txtHasil yang diharapkan: Satu baris berbentuk IMAGE=ghcr.io/example-user/my-app dengan tag di belakangnya tampil di terminal dan tersimpan di deploy-history.txt. Baris itu adalah tujuan rollback-mu.
Langkah 3: Pastikan image sebelumnya masih ada di server
Rollback paling cepat dimulai dari image yang sudah ada di disk. Lihat apa saja yang tersedia. Kolom default docker image ls adalah REPOSITORY, TAG, IMAGE ID, CREATED, dan SIZE, dan --format dengan template Go memungkinkan tampilan lebih ringkas.
# Every image of this app on the server
docker image ls ghcr.io/example-user/my-app
# A compact view: tag, age, size
docker image ls ghcr.io/example-user/my-app --format 'table {{.Tag}}\t{{.CreatedSince}}\t{{.Size}}'
# Wrong: removes EVERY image no container uses, including the one you may need tonight
# docker image prune -a
# Right: only images older than 72 hours
docker image prune -af --filter "until=72h"Kalau image lama sudah tidak ada, itu belum tentu masalah selama masih tersimpan di registry: docker compose pull akan menariknya lagi, mungkin setelah docker login. Pembersihan di situs ini sengaja kompromi: image yang dibuat dalam 72 jam terakhir dipertahankan, image portofolio dibatasi ke dua terbaru (rilis yang berjalan dan satu untuk rollback), dan rilis yang lebih tua tetap ada di registry. Volume tidak pernah dihapus oleh pembersihan itu.
Hasil yang diharapkan: Tabel berisi beberapa tag dari image aplikasimu, termasuk tag dari Langkah 2. Kalau tag itu tidak muncul, tandai bahwa kamu perlu pull saat rollback.
Langkah 4: Deploy versi baru dengan --wait
Deploy biasa memakai pola yang sama dengan rollback: ganti IMAGE, tarik image, jalankan ulang. Bedanya, kita pakai --wait. Dokumentasi Docker menjelaskannya sebagai menunggu service running atau healthy, dan --wait-timeout memberi batas waktu dalam detik. Tanpa itu, up -d kembali setelah container dijalankan, tanpa menunggu hasil health check.
cd /home/deploy/my-app
# Point IMAGE at the NEW tag (the old one is already in deploy-history.txt)
sed -i 's|^IMAGE=.*|IMAGE=ghcr.io/example-user/my-app:7b1e9d4|' .env
docker compose pull
# --wait: wait for running + healthy; give up after 120 seconds
docker compose up -d --wait --wait-timeout 120
echo "exit=$?"Dalam drill di lesson, rilis yang /health-nya menjawab 500 membuat --wait keluar dengan kode 1 setelah sekitar 19 detik, dengan jeda health check 5 detik. Docker menandai container unhealthy setelah retries kali gagal berturut-turut. Perhatikan juga: pengunjung tetap dilayani oleh rilis rusak itu, karena unhealthy hanyalah label.
Hasil yang diharapkan: Kalau rilis sehat, tercetak exit=0. Kalau tidak sehat atau melewati batas waktu, kode keluar bukan nol, dan itulah sinyal untuk lanjut ke Langkah 5.
--wait hanya membuat deploy menyadari kegagalan, ia tidak membatalkan apa pun. Deploy situs ini sendiri menjalankan docker compose up -d tanpa --wait, jadi rilis yang gagal health check tetap membuat workflow selesai hijau. Rollback tetap langkah yang harus kamu lakukan.
Langkah 5: Rollback dengan mengembalikan tag lama
Inilah seluruh rollback: tulis tag lama dari Langkah 2 kembali ke IMAGE, pastikan Compose membacanya, lalu jalankan up lagi. Karena tag lama menunjuk satu build yang pasti, tidak ada yang perlu di-build ulang. Compose membuat ulang container karena image-nya berbeda dari yang berjalan.
cd /home/deploy/my-app
# 1. Put the OLD tag back (the one from deploy-history.txt)
sed -i 's|^IMAGE=.*|IMAGE=ghcr.io/example-user/my-app:3f9c2a1|' .env
# 2. Check that Compose really reads the old tag before touching anything
docker compose config | grep 'image:'
# 3. Make sure the image is on the server, then recreate with the old one
docker compose pull
docker compose up -d --wait --wait-timeout 120
echo "exit=$?"
# Wrong: this deletes containers AND, with -v, the named volumes (your data)
# docker compose down -v
# Wrong: latest already points at the broken build, so nothing goes back
# sed -i 's|^IMAGE=.*|IMAGE=ghcr.io/example-user/my-app:latest|' .envdocker compose config mencetak konfigurasi hasil interpolasi, jadi baris image: memperlihatkan tag yang benar-benar akan dipakai. Pemeriksaan kecil ini mencegah rollback yang diam-diam tidak mengubah apa pun, misalnya karena pola sed tidak cocok. Jangan pakai down -v untuk rollback: opsi -v menghapus named volume yang dideklarasikan di file Compose, dan itu berarti data.
Hasil yang diharapkan: docker compose config menampilkan tag lama, lalu up -d --wait selesai dengan exit=0 setelah container versi lama healthy. Dalam drill di lesson, ini memakan sekitar 8 detik.
Langkah 6: Verifikasi dari dalam dan dari luar
Jangan berhenti di exit=0. Health check berjalan di dalam container, jadi ia tidak melihat DNS, TLS, atau reverse proxy di depannya. Periksa di dua sisi: di server itu sendiri, dan lewat domain publik seperti pengunjung.
# 1. Container state: running, and healthy once the start period has passed
docker compose ps
# 2. The app answers on the server itself
curl -I http://127.0.0.1:3002
# 3. The app answers the way visitors reach it (through DNS, TLS and the proxy)
curl -I https://example.com
# 4. The app's own log: did it boot the old version without errors?
docker compose logs --tail 50 webKalau log menunjukkan error, baca dengan cara yang dibahas di panduan melihat Docker logs untuk pemula. Rollback yang berhasil berarti aplikasi lama booting tanpa error baru.
Hasil yang diharapkan: Container berstatus running, dan healthy setelah masa start period lewat. Kedua curl menjawab dengan kode 200 atau pengalihan yang memang kamu harapkan, dan log menunjukkan aplikasi sudah siap.
Langkah 7: Jadikan rollback permanen lewat git atau pipeline
Mengedit .env di server hanyalah solusi sementara. Pipeline situs ini menulis ulang .env dengan IMAGE milik build terbaru di setiap deploy, jadi deploy berikutnya akan membawa kode rusak itu kembali kalau kodenya belum diperbaiki. Ada dua jalan resmi: git revert commit yang bermasalah lalu push, atau jalankan ulang workflow dari run terakhir yang berhasil.
# Option A: undo the bad commit and push it through the normal pipeline
git log --oneline -5
git revert 7b1e9d4
git push origin main
# Option B (GitHub web page): Actions > the last GOOD run
# > Re-run jobs > Re-run all jobs
# Same commit SHA as the original run, allowed up to 30 days later.Dokumentasi GitHub menyebut re-run memakai commit SHA yang sama dengan run aslinya dan bisa dilakukan sampai 30 hari setelah run pertama. Cara git revert lebih lambat tetapi meninggalkan jejak jelas di riwayat. Pipeline lengkapnya dibahas di panduan deploy Next.js ke VPS dengan Docker dan GitHub Actions.
Hasil yang diharapkan: Workflow baru selesai hijau dengan IMAGE yang sehat, dan docker compose ps di server menunjukkan tag yang sama dengan yang kamu harapkan. Setelah itu perbaiki bug-nya dengan tenang dan rilis versi baru.
Apa yang tidak bisa dibatalkan rollback
Rollback hanya menukar kode. Migrasi database yang sudah dijalankan tetap berlaku, dan data yang ditulis oleh versi baru tetap ada. Kalau versi baru menghapus atau mengubah nama kolom, versi lama yang kembali berjalan bisa error karena skema yang sudah berubah. Dalam kasus itu rollback berhasil tetapi aplikasi tetap rusak.
Cegahnya ada di cara menulis migrasi: ubah skema dalam dua tahap sehingga versi lama dan baru sama-sama bisa berjalan, seperti di panduan migrasi zero-downtime dan rolling deploy. Untuk data, anggap rollback bukan pengganti backup. Ambil backup sebelum rilis yang menyentuh database, dan uji restore-nya, seperti di strategi backup 3-2-1 untuk VPS.
Jangan menjalankan rollback dengan menghapus volume atau memakai docker compose down -v. Itu tidak mengembalikan kode lama, hanya menghapus data aplikasi dan database.
Error umum dan cara memperbaikinya
Ini yang paling sering terjadi saat rollback pertama kali. Cocokkan gejalamu dengan baris pertama, lalu lihat penyebab dan perbaikannya.
| Gejala | Penyebab | Perbaikan |
|---|---|---|
| Rollback dijalankan tetapi aplikasi tetap rusak | IMAGE tidak berubah (pola sed tidak cocok, atau salah folder), atau kamu memakai latest yang sudah menunjuk build rusak. | Jalankan grep IMAGE .env dan docker compose config, pastikan tag lama tampil, lalu pakai tag SHA, bukan latest. |
| Compose gagal menarik image saat rollback | Tag salah ketik, belum login ke registry, atau image itu sudah dihapus dari registry. | Cocokkan tag dengan deploy-history.txt, lakukan docker login ke registry, lalu ulangi docker compose pull. |
| Image lama tidak ada di disk | Pembersihan seperti docker image prune -a tanpa filter menghapus semua image yang tidak dipakai container. | Tarik lagi dengan docker compose pull. Ke depannya, pakai filter umur seperti until=72h dan jangan prune volume. |
| Setelah rollback, up -d --wait tetap keluar dengan kode bukan nol | Versi lama ikut tidak sehat: skema database sudah berubah oleh migrasi, atau konfigurasi di .env sudah berbeda. | Baca docker compose logs --tail 50 web. Kalau karena skema, rollback saja tidak cukup; pulihkan dari backup atau rilis perbaikan maju. |
| Deploy berikutnya membawa kode rusak kembali | Pipeline menulis ulang IMAGE di .env setiap deploy, dan commit rusak itu masih ada di branch. | Lakukan git revert lalu push, atau perbaiki bug-nya sebelum deploy lagi. |
| Data aplikasi hilang setelah rollback | Rollback dijalankan dengan docker compose down -v, yang menghapus named volume di file Compose. | Restore dari backup terakhir. Untuk rollback, cukup ganti IMAGE dan jalankan up -d --wait, tanpa down -v. |
| Container healthy tetapi pengunjung masih melihat error | Health check hanya memeriksa di dalam container, jadi masalah di DNS, TLS, atau reverse proxy tidak terlihat. | Jalankan curl ke domain publik, bukan hanya ke 127.0.0.1, dan periksa konfigurasi proxy-nya. |
Checklist sebelum rilis berikutnya
- Setiap image di-push dengan tag commit SHA, dan server membaca tag itu dari IMAGE di .env.
- Tag yang sedang berjalan dicatat di deploy-history.txt sebelum mengganti IMAGE.
- Image versi sebelumnya masih ada di disk, atau pasti ada di registry dan kamu sudah login.
- Deploy memakai docker compose up -d --wait dengan --wait-timeout, dan kode keluarnya diperhatikan.
- Setelah rollback kamu verifikasi dengan docker compose ps, curl lokal, curl ke domain publik, dan log.
- Rilis yang menyentuh database punya backup yang sudah teruji, dan migrasinya bisa berjalan dengan versi lama.
Belajar lebih dalam
Panduan ini menjawab satu pertanyaan: bagaimana mengembalikan rilis yang rusak. Lesson gratis kami menempatkannya dalam gambaran yang lebih besar, termasuk drill rilis rusak yang bisa kamu jalankan di laptop dan daftar apa saja yang perlu di-backup di satu server.
Buka lesson: Monitoring, Rollback, dan Backup
Latih rollback ini sekali di laptop sebelum kamu membutuhkannya di server yang sedang bermasalah. Rollback yang pernah dicoba jauh lebih tenang daripada yang pertama kali dijalankan saat pengunjung sedang melihat error.
Sumber
- Dokumentasi Docker: docker compose up (--wait, --wait-timeout)
- Dokumentasi Docker: docker compose down (opsi --volumes)
- Dokumentasi Docker: docker image ls (kolom, --format, --filter)
- Dokumentasi Docker: HEALTHCHECK (interval, retries, start period)
- Dokumentasi GitHub: menjalankan ulang workflow dan job

