Docker Compose Otomatis Berjalan Saat Boot
Pelajari cara membuat service Docker Compose aktif setelah reboot dengan restart policy, mengapa on-failure tidak cukup, dan kapan perlu unit systemd.
Jawaban singkat
Service Docker Compose akan berjalan saat boot jika dua kondisi terpenuhi secara bersamaan. Docker daemon harus diaktifkan sebagai system service, dan setiap service dalam file harus memiliki restart policy unless-stopped atau always. Tambahkan restart: unless-stopped ke setiap service, jalankan docker compose up -d satu kali, dan container akan berjalan kembali secara otomatis setelah reboot. Untuk kasus umum, tidak diperlukan hal lain.
Anda hanya memerlukan unit systemd jika urutan startup penting: misalnya stack bergantung pada disk yang di-mount, interface VPN, atau network share yang belum siap saat Docker daemon berjalan. Kasus ini memang dapat terjadi, dan bagian kedua panduan ini membahasnya. Jika Anda masih mempelajari definisi service dan volume, mulai dengan dasar-dasar Docker Compose pada VPS, lalu lanjutkan kembali ke panduan ini.
Atur kebijakan restart di compose.yaml
Kebijakan ini ditetapkan satu baris untuk setiap service. Tidak ada opsi global. Jadi, service yang terlewat akan tetap berhenti setelah reboot, sementara service lain dalam stack berjalan kembali.
services:
app:
image: nginx:1.27
restart: unless-stopped
ports:
- "8080:80"
db:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_PASSWORD: change-me
volumes:
- dbdata:/var/lib/postgresql/data
volumes:
dbdata:Terapkan perubahan, lalu baca kembali kebijakan dari container yang sedang berjalan:
docker compose up -d
docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' $(docker compose ps -q app)Perintah tersebut menampilkan unless-stopped. Jika menampilkan no, file sudah diedit, tetapi container belum pernah dibuat ulang.
Ini adalah kegagalan yang paling umum. Kebijakan restart disimpan pada container, bukan dalam file YAML. Mengedit compose.yaml tidak mengubah container yang sudah ada. docker compose restart juga tidak membantu karena perintah tersebut hanya menghentikan dan menjalankan kembali objek container yang sama tanpa mengubah konfigurasinya. Hanya docker compose up -d yang membandingkan file dengan container yang sedang berjalan, mendeteksi perubahan kebijakan, lalu membuat ulang container tersebut.
Untuk container yang belum ingin Anda buat ulang sekarang, ubah kebijakannya secara langsung:
docker update --restart unless-stopped my-containerTetap edit file YAML. docker update mengubah container yang sedang berjalan, sedangkan docker compose up -d berikutnya akan membaca file dan mengembalikan nilai lama.
Apa yang sebenarnya dilakukan setiap nilai restart
Docker menetapkan empat nilai. Perbedaannya hanya terlihat saat mesin melakukan reboot atau daemon dimulai ulang.
noadalah nilai default. Container tidak pernah dimulai ulang secara otomatis dalam kondisi apa pun.alwaysmemulai ulang container setiap kali container berhenti. Jika Anda menghentikannya secara manual, container tetap akan berjalan kembali saat daemon Docker dimulai berikutnya. Hal ini sering mengejutkan: container yang sengaja Anda hentikan minggu lalu berjalan kembali setelah reboot.unless-stoppedberperilaku sepertialways, tetapi container yang dihentikan secara manual tetap berhenti setelah daemon dimulai ulang. Gunakan nilai ini untuk service yang sesekali dihentikan untuk pemeliharaan.on-failurememulai ulang container hanya jika container keluar dengan kode exit non-zero. Anda dapat membatasi jumlah percobaan, seperti padarestart: on-failure:3.
Untuk stack yang harus selalu aktif selama server aktif, unless-stopped adalah default yang tepat. Pilih always hanya jika Anda ingin container yang tidak mudah dibiarkan berhenti.
Mengapa restart: on-failure tidak bertahan setelah reboot
Banyak orang memilih on-failure karena terdengar hati-hati, lalu mendapati semua container berhenti setelah reboot pertama. Penyebabnya terdapat dalam definisinya. on-failure hanya merespons satu hal: proses container keluar dengan kode error.
Reboot bukanlah error. Saat host dimatikan, systemd menghentikan docker.service, lalu daemon menghentikan setiap container secara sengaja. Container tersebut tidak gagal, sehingga tidak ada yang perlu direspons oleh kebijakan ini. Saat sistem kembali aktif, daemon mencari container yang perlu dilanjutkan, dan container on-failure yang dihentikan secara normal bukan salah satunya. Container tersebut tetap berada dalam status exited.
Anda dapat melihatnya secara langsung. Tetapkan restart: on-failure pada sebuah service, jalankan docker compose up -d, lakukan reboot, lalu jalankan:
docker compose ps -aService tersebut tercantum dengan state Exited dan status seperti Exited (0) 2 minutes ago. Tidak ada yang rusak dan tidak ada yang dicatat sebagai error. Inilah yang membuat masalah ini sulit didiagnosis. Kebijakan tersebut berjalan tepat sesuai definisinya.
on-failure tetap berguna. Kebijakan ini cocok untuk container yang menjalankan job dan mungkin mengalami crash. Dengan sendirinya, restart: on-failure tidak menetapkan batas dan akan terus me-restart job yang terus gagal. Karena itu, tambahkan batas seperti restart: on-failure:3 jika Anda menginginkan jumlah percobaan ulang yang terbatas dan tanpa restart loop. Kebijakan ini bukan alat yang tepat untuk menjaga service yang berjalan lama tetap aktif setelah reboot.
Kebijakan restart hanya berfungsi jika service Docker berjalan saat boot
Kebijakan restart diberlakukan oleh daemon Docker. Jika daemon tidak berjalan, tidak ada yang memberlakukannya. Periksa:
systemctl is-enabled docker
systemctl is-enabled containerdKeduanya harus menampilkan enabled. Paket dari repositori resmi Docker mengaktifkan keduanya saat instalasi, sehingga pada server baru pemeriksaan ini biasanya berhasil. Jika salah satunya menampilkan disabled, perbaiki:
sudo systemctl enable --now docker containerdAda hal penting yang perlu dipahami. Ubuntu juga menyediakan docker.socket, yang menjalankan daemon sesuai permintaan saat pertama kali ada sesuatu yang berkomunikasi dengan Docker API. Banyak orang melihat docker.socket aktif, mengira daemon sudah tercakup, lalu menonaktifkan docker.service untuk menghemat memori. Saat boot, tidak ada yang memanggil API. Akibatnya, socket tidak pernah diakses, daemon tidak pernah berjalan, dan tidak ada container yang dijalankan sampai Anda mengetik perintah docker pertama. Aktivasi socket bukan pengganti untuk mengaktifkan docker.service.
Kapan unit systemd menjadi pilihan yang lebih tepat
Kebijakan restart tidak memiliki konsep pengurutan terhadap sistem lainnya. Daemon dijalankan, lalu segera menjalankan container setelah memungkinkan. Jika stack Anda melakukan bind mount direktori dari volume terpisah, share NFS (network file system), atau disk terenkripsi, container dapat dijalankan sebelum path tersebut tersedia. Docker akan membuat direktori kosong pada titik mount dan menjalankan container dengan direktori tersebut, sehingga database Anda berjalan tanpa data.
Tulis unit systemd jika salah satu kondisi berikut berlaku. Stack memerlukan mount, interface VPN, atau unit lain yang harus siap terlebih dahulu. Anda ingin systemctl stop myapp dan systemctl start myapp berfungsi seperti pada service lain di server. Atau, Anda ingin stack dihentikan dengan benar saat shutdown, bukan dihentikan secara paksa bersama daemon. Jika unit systemd masih baru bagi Anda, menulis service dan timer systemd menjelaskan format file tersebut secara lebih mendalam.
Menulis unit systemd
Tempatkan stack pada path tetap di luar direktori home. /srv/myapp adalah pilihan yang baik karena unit yang berjalan sebelum siapa pun login tidak seharusnya membaca /home.
Buat /etc/systemd/system/myapp.service:
[Unit]
Description=myapp docker compose stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
RequiresMountsFor=/srv/myapp/data
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/srv/myapp
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.targetAktifkan dan jalankan unit tersebut:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
systemctl status myapp.serviceUnit yang sehat menampilkan Active: active (exited). Hasil ini tampak keliru saat pertama kali dilihat. Hasil tersebut benar: Type=oneshot dengan RemainAfterExit=yes berarti unit telah menjalankan perintahnya, perintah tersebut telah selesai, dan systemd mempertahankan unit dalam status active agar ExecStop dijalankan saat shutdown.
Setiap baris memiliki kegunaan. Requires=docker.service berarti unit gagal dengan cepat, bukan menjalankan docker compose terhadap socket yang sudah tidak aktif. After= menetapkan urutannya karena Requires= saja tidak melakukannya. RequiresMountsFor= membuat systemd memuat unit mount untuk path tersebut dan menunggunya. Inilah alasan utama menggunakan unit, bukan restart policy. TimeoutStartSec=0 mencegah systemd menghentikan start job saat image berukuran besar masih di-pull.
Perhatikan penggunaan kedua mekanisme secara bersamaan. Dokumentasi Docker menyarankan agar restart policy tidak digabungkan dengan process manager pada host dan hanya menyebutkan bahwa kombinasi tersebut menimbulkan konflik. Konflik terjadi jika process manager memantau proses container secara langsung dan me-restart-nya saat daemon juga berusaha melakukan hal yang sama. Unit Type=oneshot tidak memantau proses apa pun, sehingga mempertahankan restart: unless-stopped dalam file compose bersama unit ini tidak masalah. Bahkan, itulah konfigurasi yang diinginkan. systemd menangani pengurutan saat boot, sedangkan daemon menangani container yang mengalami crash pada dini hari.
Unit tersebut terlihat berbeda jika yang ingin Anda pertahankan tetap aktif adalah proses biasa yang berjalan lama, bukan stack. Dalam kasus itu, tidak ada daemon di bawahnya, sehingga Restart= milik systemd sendiri harus melakukan pemantauan. menjalankan dsh tanpa antarmuka di belakang systemd adalah contoh penerapan pola tersebut, termasuk penggunaan user khusus dan journal.
Verifikasi dengan reboot nyata
Tidak ada pengganti untuk pengujian nyata. systemctl restart docker tidak menguji urutan pemasangan, sedangkan docker compose down yang diikuti docker compose up -d sama sekali tidak menguji proses boot.
sudo rebootTunggu, sambungkan kembali, lalu lakukan pemeriksaan dalam urutan berikut:
uptime
systemctl is-active docker
docker compose psuptime memastikan bahwa Anda sedang memeriksa mesin yang benar-benar telah reboot. docker compose ps, jika dijalankan dari direktori stack, seharusnya menampilkan setiap service dengan status running dan uptime yang mendekati uptime mesin. Service yang menampilkan Exited adalah service yang perlu diperiksa.
Jika ada sesuatu yang tidak berhasil berjalan, log daemon mencakup periode boot:
journalctl -u docker.service -b --no-pager | tail -50Untuk stack yang dikelola oleh unit, journalctl -u myapp.service -b --no-pager menampilkan output docker compose yang tepat sejak boot, termasuk kegagalan mengambil image atau file .env yang tidak ada. Reboot yang Anda jadwalkan adalah reboot yang Anda pantau, jadi biarkan unit memberi tahu Anda tentang reboot yang tidak Anda pantau: baris OnFailure= yang diarahkan ke server ntfy yang di-host sendiri mengubah stack yang gagal kembali berjalan menjadi notifikasi push, bukan masalah yang baru Anda temukan beberapa hari kemudian.
Hal-hal yang dapat diam-diam menghambat auto-start
Container yang dibuat dengan docker compose run tidak pernah mendapatkan restart policy dari file tersebut. Compose memperlakukannya sebagai container sekali jalan. Jika sebuah service tampaknya mengabaikan policy-nya, periksa apakah service tersebut dijalankan dengan run, bukan up.
Relative path dalam volume atau entri env_file di-resolve berdasarkan direktori file compose. Ini berfungsi dari shell Anda dan dari unit yang menetapkan WorkingDirectory. Namun, ini gagal dari unit yang tidak menetapkannya karena working directory-nya adalah /.
Rootless Docker adalah kasus yang berbeda. Daemon berjalan sebagai user service, dan user service berhenti ketika sesi terakhir untuk user tersebut berakhir. Aktifkan service tersebut untuk user itu dan izinkan daemon tetap berjalan saat tidak ada user yang login:
systemctl --user enable docker
sudo loginctl enable-linger $USERTanpa enable-linger, rootless daemon akan berhenti saat Anda logout dan container ikut berhenti. Kondisi ini tampak persis seperti restart policy yang rusak.
Satu hal terakhir. Automatic security updates dapat me-reboot server pada jam yang ditentukan. Ini hanya bermanfaat jika stack Anda dapat berjalan kembali secara otomatis. Konfigurasi ini pada mesin baru termasuk bagian dari pekerjaan satu jam pertama, bersama sepuluh menit pertama pada VPS baru.
FAQ
Apa perbedaan antara restart: always dan restart: unless-stopped?
Keduanya me-restart container saat container berhenti sendiri. Perbedaannya terlihat setelah Anda menghentikan container secara manual. Dengan always, container akan berjalan lagi saat Docker daemon berikutnya dimulai. Artinya, reboot membatalkan penghentian manual Anda. Dengan unless-stopped, daemon mengingat bahwa container dihentikan secara sengaja dan membiarkannya tetap berhenti. Gunakan unless-stopped kecuali Anda secara khusus menginginkan container yang selalu berjalan kembali.
Saya menambahkan restart: unless-stopped, tetapi container tetap tidak berjalan setelah reboot. Mengapa?
Kebijakan tersebut tersimpan pada container, bukan di dalam file. Container yang sudah ada tidak diperbarui hanya dengan mengedit YAML. Jalankan docker compose up -d agar Compose membuat ulang container, lalu konfirmasikan dengan docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' $(docker compose ps -q app). Jika perintah tersebut menampilkan no, berarti container dibuat sebelum perubahan Anda. Penyebab umum lainnya adalah docker.service belum diaktifkan. Anda dapat memeriksanya dengan systemctl is-enabled docker.
Apakah saya memerlukan unit systemd jika sudah menggunakan kebijakan restart?
Biasanya tidak. Kebijakan restart cukup untuk stack yang hanya memerlukan jaringan, dan sebagian besar stack termasuk dalam kategori ini. Tambahkan unit jika container bergantung pada sesuatu yang belum siap saat Docker daemon dimulai, seperti disk eksternal, volume terenkripsi, share NFS, atau antarmuka VPN. Unit tersebut menyediakan pengurutan melalui After= dan RequiresMountsFor=, yang tidak dapat dinyatakan oleh kebijakan restart.
Bagaimana cara menghentikan stack secara permanen agar tidak berjalan lagi saat reboot berikutnya?
Dengan unless-stopped, docker compose stop sudah cukup karena container yang dihentikan secara manual tidak dijalankan kembali saat daemon dimulai ulang. Dengan always, penghentian saja tidak cukup dan container akan berjalan lagi setelah reboot. Jalankan docker compose down untuk menghapus container, atau ubah kebijakannya terlebih dahulu dengan docker update --restart no my-container. Jika unit systemd mengelola stack tersebut, jalankan sudo systemctl disable myapp.service juga. Jika tidak, unit akan menjalankannya kembali.