Lima chapter sebelumnya mengurus satu keluarga pertanyaan: bagaimana caranya membentuk perilaku container sejak lahir — menimpa perintah bawaan lewat Command Override, menyuntik konfigurasi lewat Environment Variable, memilih identitas lewat User dan Permissions, membuka pintu ke luar lewat Port Publishing, dan mematok titik berpijak lewat Working Directory. Semua flag itu bersifat statis: nilainya dipilih sekali pada docker run dan berlaku selama container hidup. Pertanyaan yang diwariskan penutup chapter sebelumnya kini menanti jawaban: siapa yang membangkitkan container setelah kejatuhan — crash, sinyal 9 dari OOM killer, atau host yang restart di tengah malam? Sebuah server sungguhan tidak hanya hidup; server reboot untuk pemeliharaan kernel, aplikasi mati karena bug, dan pada pukul tiga pagi tidak ada manusia yang bangun untuk mengetik docker start. Docker menjawab kebutuhan itu dengan satu flag kecil bernama --restart, yang di baliknya berdiri empat kebijakan dengan perilaku yang sangat berbeda satu sama lain — dan salah satunya menyimpan perbedaan halus yang hanya terlihat ketika daemon sendiri mati lalu hidup kembali.
Daemon sebagai Pengawas Container #
Mari kita luruskan dulu pelakunya, karena jawaban atas “siapa yang membangkitkan container” tidak berada di dalam container itu sendiri. Container adalah proses biasa di host — sebagaimana kita telusuri di Chapter Arsitektur Docker — dan proses yang mati tidak bisa membangkitkan dirinya sendiri. Yang mengawasi adalah dockerd: lewat containerd, daemon menerima kabar setiap kali PID 1 sebuah container berakhir, lengkap dengan exit code-nya. Pada momen itu daemon membuka catatan konfigurasi container, membaca field HostConfig.RestartPolicy, dan memutuskan: bangkitkan ulang, atau biarkan tergeletak.
Kebijakan restart karenanya hidup di sisi daemon, bukan di sisi image atau aplikasi. Konsekuensinya ada dua. Pertama, kebijakan bisa kita baca dan bahkan ubah setelah container diciptakan — docker update akan membuktikannya sebentar lagi. Kedua, keputusan daemon ikut berperan ketika daemon itu sendiri mati dan menyala kembali: daemon yang bangun akan menoleh ke daftar container yang ditinggalkannya, lalu memperlakukan masing-masing sesuai kebijakannya — dan di sinilah perbedaan paling halus antar kebijakan akan tampak.
Empat Kebijakan Restart #
Dokumentasi resmi Docker menyediakan tepat empat kebijakan, dan ketiganya yang aktif bekerja dengan cara yang berbeda tergantung dua hal: exit code yang ditinggalkan proses utama, dan apakah container berhenti karena mati sendiri atau karena dihentikan tangan manusia. Tabel berikut merangkum keempatnya:
| Kebijakan | Exit ≠ 0 | Exit 0 | Di-stop manual, lalu daemon start ulang |
|---|---|---|---|
no (default) | tetap berhenti | tetap berhenti | tetap berhenti |
on-failure[:maks] | dibangkitkan ulang, maksimal maks kali | tetap berhenti | tetap berhenti |
always | dibangkitkan ulang | dibangkitkan ulang | dibangkitkan kembali oleh daemon |
unless-stopped | dibangkitkan ulang | dibangkitkan ulang | tetap berhenti |
Mari kita telaah baris demi baris tabel di atas:
noadalah kebijakan bawaan — tanpa flag--restart, container yang mati tetap mati; seluruh perilaku yang kita kenal sepanjang course sejauh ini memang berasal dari kebijakan ini.on-failuremenimbang exit code — container dibangkitkan hanya bila PID 1 keluar dengan kode gagal; keluar dengan kode0berarti tugas selesai dan tidak ada alasan untuk mengulang. Angka setelah titik dua membatasi jumlah percobaan.alwayspaling setia — container dibangkitkan apa pun exit code-nya, termasuk keluar rapi dengan0; bahkan container yang kita hentikan sendiri secara manual akan dihidupkan kembali oleh daemon ketika daemon menyala ulang.unless-stoppedmenghormati keputusan manusia — perilakunya serupaalwaysselama container dibiarkan hidup, tetapi keputusan kita untuk menghentikannya dicatat permanen: daemon yang start ulang pun tidak akan membangkitkannya.- Seluruh kebijakan menghormati penghentian manual saat itu terjadi —
docker stopdandocker killtidak diikuti kebangkitan seketika; kebijakan baru “menagih” kewajibannya saat daemon restart, dan hanyaalwaysyang membayar tagihan itu.
Satu catatan lagi sebelum praktik: container yang sedang berjalan ketika daemon dimatikan — misalnya karena server reboot — akan dihidupkan kembali oleh daemon yang baru menyala, untuk kebijakan always maupun unless-stopped. Dengan begitu, skenario “server reboot dini hari” tertangani tanpa satu pun perintah manusia, selama image tersimpan lokal di mesin itu.
Menjalankan web dengan unless-stopped #
Sebagai bahan latihan, kita hidupkan kembali container web yang setia menemani kita — kali ini dengan kebijakan yang membuatnya tahan banting. Meja kita mesti dibersihkan lebih dulu: tiga container masih menyala dari lima chapter pertama section ini — web pembawa nginx dari Chapter Working Directory, serta myapp-prod dan db-katalog bekas demo environment variable. Ketiganya kita pamitkan sekaligus supaya setiap tabel pada chapter ini hanya menampilkan aktor yang sedang dibicarakan:
$ docker rm -f web myapp-prod db-katalogOutput yang Dihasilkan:
web
myapp-prod
db-katalogKini web pengganti lahir dengan kebijakan bangkit otomatis. Kita sengaja tidak menerbitkan port apa pun — pemetaan port adalah urusan yang sudah kita kuasai pada Chapter Port Publishing — karena fokus kita murni pada hidup-matinya proses, bukan pada lalu lintas jaringan:
$ docker run -d --restart unless-stopped --name web nginxOutput yang Dihasilkan:
f7c2a91b3e55913c4a5f1c6c0a4d8e2b3f4a5b6c7d8e9f0a1b2c3d4e5f60718a$ docker psOutput yang Dihasilkan:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f7c2a91b3e55 nginx "/docker-entrypoint.…" About a minute ago Up 54 seconds webMari kita telaah rangkaian di atas:
--restart unless-stopped— kebijakan dibaca sekali oleh daemon saat container diciptakan, lalu disimpan pada konfigurasi container; flag ini tidak mengubah apa pun yang terlihat padadocker ps.- ID yang akrab itu kembali —
f7c2a91b3e55adalah containerwebbaru dari image nginx yang sudah menetap lama di mesin kita, sehingga tidak ada satu baris unduhan pun. - Kolom
PORTSsengaja kosong — tanpa flag-p, nginx mendengarkan di dalam jaringan container saja; kebijakan restart dan penerbitan port adalah dua urusan yang saling bebas.
Kebijakan yang barusan kita pasang bisa dibaca kembali dari konfigurasinya, memakai template docker inspect yang kita kuasai sejak Section Docker Image:
$ docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' webOutput yang Dihasilkan:
unless-stoppedMensimulasikan Crash #
Kebijakan pasif terlihat seperti hiasan sampai container benar-benar mati — maka kita jadikan mati. Cara paling jujur mensimulasikan crash adalah membunuh PID 1 dari dalam container itu sendiri, bukan lewat docker kill yang dihitung sebagai penghentian manual. docker exec membuka pintu ke dalam, dan kill -9 1 menjatuhkan sinyal SIGKILL tepat ke kepala proses utama:
$ docker exec web sh -c 'kill -9 1'
$ docker psOutput yang Dihasilkan:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f7c2a91b3e55 nginx "/docker-entrypoint.…" 3 minutes ago Up 5 seconds webMari kita telaah apa yang barusan terjadi:
docker exectidak mencetak apa pun — perintah di dalam container berjalan senyap; satu-satunya jejaknya ada pada kolomSTATUS.- PID 1 tumbang oleh
SIGKILL— nginx master mati seketika tanpa kesempatan beres-beres, dan container berhenti dengan exit code137alias128 + 9, persis konvensi yang kita bedah pada Chapter Memahami Exit Code. - Daemon membangkitkannya dalam hitungan detik — kolom
STATUSkini membacaUp 5 seconds; container yang sama, ID yang sama, proses PID 1 yang baru — dan seluruh proses ini berlangsung tanpa satu tombol pun kita sentuh. - Kebangkitan itu tercatat — daemon menghitung setiap restart pada field
RestartCount, yang bisa kita baca dengan template inspect:
$ docker inspect --format '{{.RestartCount}}' webOutput yang Dihasilkan:
1Membatasi Percobaan dengan on-failure #
Kebijakan unless-stopped membangkitkan apa pun sebab kematiannya — termasuk keluar dengan kode sukses. Bagi pekerjaan batch, perilaku itu justru salah: skrip laporan mingguan yang selesai dengan exit 0 seharusnya dibiarkan pulang, bukan dijadwalkan ulang. Kebijakan on-failure membedakan kedua keadaan itu dengan menimbang exit code, dan angka setelah titik dua membatasi percobaannya agar container yang gagal permanen tidak berputar selamanya. Kita uji keduanya dengan alpine yang sudah lama menjadi korban sukarela kita.
Uji on-failure:3 dengan exit 1 #
Container berikut sengaja lahir untuk gagal: PID 1-nya hanya menjalankan satu instruksi exit 1 lalu tumbang, dan daemon diperintahkan mencoba maksimal tiga kali:
$ docker run -d --name gagal --restart on-failure:3 alpine:3.20 sh -c 'exit 1'Output yang Dihasilkan:
00aa78d878205b7e8490175c3a5d3511752de544825530b6430b1f1bca01105fBila kita buru-buru membuka docker ps -a, kita sempat menangkap kebijakan itu sedang bekerja — status Restarting dengan exit code kegagalan di dalam kurung:
$ docker ps -aOutput yang Dihasilkan:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9e1c4a7b2d58 alpine:3.20 "sh -c exit 1" 9 seconds ago Restarting (1) 2 seconds ago gagal
f7c2a91b3e55 nginx "/docker-entrypoint.…" 4 minutes ago Up 3 minutes webBeberapa saat kemudian, seluruh jatah percobaan habis dan container menyerah. Kita baca catatan akhirnya:
$ docker inspect --format '{{.RestartCount}} {{.State.Status}}' gagalOutput yang Dihasilkan:
3 exited$ docker inspect --format '{{.HostConfig.RestartPolicy.MaximumRetryCount}}' gagalOutput yang Dihasilkan:
3Mari kita telaah rangkaian pembuktian di atas:
Restarting (1)adalah status transisi — container terlihat padadocker ps -adalam keadaan menunggu jeda ulang, dengan angka1di dalam kurung menandai exit code terakhir; status ini hanya muncul sesaat dan akan kita petakan formal pada Chapter Lifecycle Container.3 exitedmembuktikan batas percobaan bekerja — daemon mencoba menghidupkan ulang tepat tiga kali, gagal tiga kali, lalu berhenti mencoba; container kini berstatusexiteddan akan tetap demikian.MaximumRetryCountmenyimpan angka dari flag — nilaion-failure:3terurai menjadi nama kebijakanon-failuredan batas percobaan3; tanpa angka, daemon mencoba tanpa batas selama exit code tetap gagal.
Exit 0 tidak dibangkitkan #
Sekarang kembarannya yang sukses — perintah yang sama, kebijakan yang sama, hanya exit code yang berbeda:
$ docker run -d --name sukses --restart on-failure:3 alpine:3.20 sh -c 'exit 0'Output yang Dihasilkan:
e8265d85490c2432d83996119c8e6c6389bd1b5943e25015d3673e07188102f6$ docker inspect --format '{{.RestartCount}} {{.State.Status}}' suksesOutput yang Dihasilkan:
0 exitedMari kita telaah kontras kedua saudara itu:
0 exitedtanpa perlawanan — container keluar dengan kode sukses, kebijakanon-failuretidak menemukan alasan untuk bangkit, danRestartCounttetap nol; keluar rapi memang bukan kegagalan.- Inilah pembeda kunci dari
alwaysdanunless-stopped— kedua kebijakan itu justru membangkitkan container yang keluar dengan0, karena bagi keduanya “berhenti” sendiri sudah cukup alasan untuk bangkit kembali.
Mengubah Kebijakan Saat Berjalan #
Kebijakan restart tidak terkunci pada momen docker run. Perintah docker update menulis ulang bagian konfigurasi container kapan pun — container sedang berjalan maupun sedang tergeletak — tanpa menciptakan container baru. Kita ubah web menjadi container yang boleh mati damai:
$ docker update --restart=no webOutput yang Dihasilkan:
web$ docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' webOutput yang Dihasilkan:
noOutput web adalah konfirmasi baku perintah docker update: identifier yang kita sebut dicetak kembali, dan konfigurasi berubah seketika. Kini kita ulang percobaan crash yang tadi membangkitkan web:
$ docker exec web sh -c 'kill -9 1'
$ docker ps -aOutput yang Dihasilkan:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0a4b8c2d6e1f alpine:3.20 "sh -c exit 0" 2 minutes ago Exited (0) About a minute ago sukses
9e1c4a7b2d58 alpine:3.20 "sh -c exit 1" 4 minutes ago Exited (1) 3 minutes ago gagal
f7c2a91b3e55 nginx "/docker-entrypoint.…" 11 minutes ago Exited (137) 8 seconds ago webMari kita telaah baris web pada tabel di atas:
Exited (137)dan tetap tergeletak — crash yang sama yang tadi membangkitkanwebdalam lima detik kini meninggalkannya mati permanen; satu-satunya hal yang berubah adalah kebijakan yang terbaca daemon.- Angka
137tetap setia melaporkan sebab kematian —SIGKILLdarikill -9 1kita; yang berubah bukan cara matinya, melainkan tanggapan daemon sesudahnya.
always vs unless-stopped Saat Daemon Start Ulang #
Perbedaan paling halus antara dua kebijakan setia itu baru berbicara setelah daemon mati. Kita susun panggungnya: web kita kembalikan ke unless-stopped lalu dihidupkan, kemudian dilahirkan web2 dengan always — dua kembar nginx dengan nasib yang akan berbeda. Empat perintah berikut dijalankan berurutan, dan lima baris output-nya masing-masing berasal dari perintah yang berbeda:
$ docker update --restart unless-stopped web
$ docker start web
$ docker run -d --name web2 --restart always nginx
$ docker stop web web2Output yang Dihasilkan:
web
web
32cbf8f11650921d2be9053b5a8a80f1fd3ba9b2a6d4d780771ded457259a17b
web
web2Mari kita telaah asal tiap barisnya:
- Baris pertama dan kedua
web— konfirmasi daridocker updatedandocker start; kebijakan kembali menjadiunless-stoppeddan proses PID 1 menyala kembali. - Baris panjang heksadesimal — ID penuh container
web2yang baru diciptakandocker rundengan kebijakanalways. - Dua baris terakhir —
docker stopmenghentikanwebdanweb2secara sopan; keduanya kini berstatusExited (0)karena nginx menutup dirinya dengan rapi.
Sekarang momen yang dinanti: kita matikan daemon itu sendiri, lalu bangkitkan. Pada host Linux dengan systemd, perintahnya adalah sudo systemctl restart docker — perintah ini tidak mencetak apa-apa; daemon dimatikan dan dinyalakan kembali dalam diam beberapa detik, dan sepanjang masa transisinya CLI bisa sempat menjawab bahwa daemon belum bisa dihubungi. Pengguna Docker Desktop menggantinya dengan keluar dari aplikasi lalu membukanya kembali. Setelah daemon siap, kita lihat nasib kedua kembar itu:
$ docker ps -aOutput yang Dihasilkan:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a3f5c8e91d47 nginx "/docker-entrypoint.…" 2 minutes ago Up 14 seconds web2
0a4b8c2d6e1f alpine:3.20 "sh -c exit 0" 5 minutes ago Exited (0) 4 minutes ago sukses
9e1c4a7b2d58 alpine:3.20 "sh -c exit 1" 7 minutes ago Exited (1) 6 minutes ago gagal
f7c2a91b3e55 nginx "/docker-entrypoint.…" 14 minutes ago Exited (0) 2 minutes ago webMari kita telaah perbedaan nasib pada dua baris nginx:
web2bangkit sendiri —Up 14 seconds— kebijakanalwaysmemuat janji yang berlaku bahkan melintasi kematian daemon: container yang pernah dihentikan manual sekalipun akan dihidupkan kembali ketika daemon menyala ulang.webtetap tergeletak —Exited (0) 2 minutes ago— kebijakanunless-stoppedmencatat keputusan kita menghentikannya secara permanen; daemon yang baru bangun membaca catatan itu dan menghormatinya.gagaldansuksestidak ikut terlibat — keduanya berhenti karena kehabisan percobaan dan exit sukses, bukan karena dihentikan manual; keduanya tetap diam karena kebijakanon-failurememang tidak menagih apa-apa dari daemon yang bangkit.- Inilah alasan
unless-stoppedmenjadi kebijakan paling populer untuk server — container otomatis bangkit saat crash dan saat reboot, tetapi tetap tunduk saat kita dengan sengaja mematikannya untuk pemeliharaan.
Jeda Retry yang Berlipat Ganda #
Daemon tidak membangkitkan container yang gagal secepat kilat berulang-ulang. Antara tiap percobaan ada jeda tunggu yang dimulai dari 100 milidetik dan berlipat ganda pada tiap kegagalan — strategi backoff yang mencegah container bermasalah membanjiri daemon dan host dengan percobaan sia-sia:
| Percobaan restart ke- | Jeda tunggu sebelumnya |
|---|---|
| 1 | 100 ms |
| 2 | 200 ms |
| 3 | 400 ms |
| 4 | 800 ms |
| 5 | 1600 ms |
| seterusnya | terus berlipat ganda |
Pada uji gagal kita, tiga percobaan hanya memakan waktu sekitar tujuh ratus milidetik jeda total — terasa seketika. Tetapi bayangkan container yang gagal setiap kali menyala selama sepuluh detik: jeda yang berlipat ganda membuat percobaan kesepuluh menunggu hampir dua menit, memberi napas bagi host dan bagi kita untuk menyelidiki sambil kebijakan masih bekerja.
Seluruh aktor chapter ini kita bereskan dulu sebelum pindah topik — docker rm -f membersihkan empat bekas latihan dalam satu ayunan, baik yang masih hidup (web2 yang barusan dibangkitkan daemon) maupun yang sudah tergeletak:
$ docker rm -f web web2 gagal suksesOutput yang Dihasilkan:
web
web2
gagal
suksesKini container kita bangkit sendiri saat crash dan saat reboot — tetapi kebangkitan itu punya musuh yang lebih sunyi: container yang bangkit hanya untuk menabrak langit-langit resource, atau justru tetangga yang merampas jatah sampai container kita sendiri tersedak. Masalah itu tidak diselesaikan dengan kebijakan hidup-mati, melainkan dengan batas jumlah — berapa core CPU dan berapa banyak memori yang boleh dimakan sebuah container. Chapter berikutnya, Resource Limit, memperkenalkan wilayah itu: noisy neighbor, cgroups sang penegak, dan perbedaan antara langit-langit keras dan jaminan kursi lunak.
Referensi #
- Start containers automatically — dokumentasi resmi empat kebijakan restart, jeda backoff, dan perilaku saat daemon start ulang.
- docker container run
— referensi resmi flag
--restartbeserta bentukon-failure[:maks]. - docker container update — mengubah restart policy pada container yang sudah diciptakan.