Docker Compose: Perbedaan command dan entrypoint
ENTRYPOINT menjalankan program, sedangkan command memasok argumennya. Pahami empat kombinasi penggantian di Compose dan mengapa entrypoint menghapus CMD.
Perintah Docker Compose vs entrypoint, dalam satu aturan
Dalam Docker Compose, entrypoint: menetapkan program yang dijalankan, sedangkan command: menetapkan argumen yang diberikan kepada program tersebut. Proses container terdiri atas daftar entrypoint dengan daftar command yang ditambahkan di bagian akhirnya. Semua perilaku lain pada halaman ini mengikuti kalimat tersebut.
Kedua key tersebut dipetakan ke dua instruksi Dockerfile. entrypoint: menggantikan ENTRYPOINT dari image. command: menggantikan CMD dari image. Keduanya tidak berdiri sendiri. Di sinilah banyak orang mengalami masalah: menetapkan entrypoint: juga membuang CMD dari image. Spesifikasi Compose menyatakannya secara langsung. Jika entrypoint bukan null, Compose mengabaikan command default apa pun dari image.
Baca deklarasi bawaan image
Sebelum mengganti apa pun, periksa hal-hal yang sudah disediakan image.
docker image inspect --format '{{json .Config.Entrypoint}}' postgres:16
docker image inspect --format '{{json .Config.Cmd}}' postgres:16Anda mendapatkan ["docker-entrypoint.sh"] dan ["postgres"], sehingga container menjalankan docker-entrypoint.sh postgres. Skrip tersebut membuat direktori data saat boot pertama, membaca variabel POSTGRES_*, menurunkan hak akses ke user postgres, lalu menjalankan argumen yang diberikan kepadanya dengan exec. Menentukan bagian mana yang ingin Anda ubah merupakan inti keputusannya. Untuk meneruskan flag ke database, ganti command:. Jika Anda mengganti entrypoint:, seluruh proses penyiapan tersebut tidak akan berjalan.
Empat kombinasi, ditampilkan dalam gambar kecil
Buat image yang satu-satunya tugasnya adalah mencetak daftar argumen saat image tersebut dijalankan.
FROM alpine:3.20
ENTRYPOINT ["/bin/echo", "ep"]
CMD ["cmd"]docker build -t argdemo .services:
demo:
image: argdemoJalankan docker compose up setelah setiap pengeditan, lalu baca satu baris yang dicatatnya.
- Tidak ada key yang ditetapkan. Prosesnya adalah
/bin/echo ep cmddan log menampilkanep cmd. - Hanya
command: ["cmd2"]. Prosesnya adalah/bin/echo ep cmd2. entrypoint tidak berubah dan hanya argumennya yang berubah. - Hanya
entrypoint: ["/bin/echo", "ep2"]. Prosesnya adalah/bin/echo ep2dan log menampilkanep2.cmddari image hilang, dan tidak ada peringatan. - Kedua key ditetapkan. Prosesnya adalah
/bin/echo ep2 cmd2. Hanya dalam kasus ini Anda mengendalikan seluruh daftar argumen.
Mengapa menetapkan entrypoint menghapus CMD image
CMD pada sebuah image ditulis sebagai daftar argumen default untuk ENTRYPOINT image tersebut. Jika entrypoint diganti, argumen tersebut menjadi milik program yang tidak lagi dijalankan. Karena itu, Compose menghapusnya daripada menyusun baris perintah yang tidak pernah dimaksudkan oleh pembuat image. docker run --entrypoint berperilaku sama. Jadi, ini adalah perilaku Docker, bukan keanehan Compose.
Dampaknya konkret. nginx:1.27 mendeklarasikan ENTRYPOINT ["/docker-entrypoint.sh"] dan CMD ["nginx", "-g", "daemon off;"]. Tetapkan entrypoint: /custom-init.sh, lalu skrip Anda dimulai dengan daftar argumen kosong. Skrip yang biasanya diakhiri dengan exec "$@" tidak memiliki apa pun untuk dieksekusi. Akibatnya, exec tidak melakukan apa-apa, skrip mencapai baris terakhirnya, lalu container keluar dengan kode 0 tanpa pesan error di mana pun. Kembalikan argumen tersebut secara manual:
services:
web:
image: nginx:1.27
entrypoint: /custom-init.sh
command: ["nginx", "-g", "daemon off;"]Aturan yang perlu diingat: setiap kali Anda menetapkan entrypoint:, tentukan juga command: dalam perubahan yang sama.
Bentuk exec dan bentuk shell, serta perbedaan Compose
Dockerfile menerima dua sintaks. CMD ["nginx", "-g", "daemon off;"] adalah bentuk exec: biner dijalankan secara langsung tanpa melibatkan shell. CMD nginx -g "daemon off;" adalah bentuk shell: Docker menulis ulang instruksi tersebut menjadi /bin/sh -c 'nginx -g "daemon off;"', sehingga shell dijalankan terlebih dahulu dan program Anda menjadi proses anaknya.
Compose tidak mengikuti aturan tersebut, dan hal ini sering mengejutkan pengguna. String dalam command: dipecah menjadi argumen lalu dijalankan secara langsung tanpa pembungkus /bin/sh -c. Referensi Compose menjelaskannya secara tegas: kolom command tidak dijalankan dalam konteks SHELL yang ditentukan di dalam image. Jadi, jika Anda memerlukan fitur shell, Anda harus memanggil shell secara eksplisit.
Itulah sebabnya command: echo "hello $$HOSTNAME" mencetak teks literal hello $HOSTNAME. Tidak ada shell yang menerima string tersebut, sehingga tidak ada ekspansi yang dilakukan. Gunakan shell jika Anda memerlukannya:
services:
demo:
image: alpine:3.20
command: /bin/sh -c 'echo "hello $$HOSTNAME"'Signal, PID 1, dan docker compose down yang bersih
docker compose stop dan docker compose down mengirim SIGTERM ke PID 1 di dalam setiap container, menunggu stop_grace_period, lalu mengirim SIGKILL. Masa tenggang default adalah 10 detik.
PID 1 bersifat khusus di Linux. Kernel tidak menerapkan tindakan default signal kepada PID 1. Karena itu, proses yang tidak memasang handler SIGTERM akan mengabaikan SIGTERM saat berjalan sebagai PID 1. Proses tersebut akan tetap berjalan selama masa tenggang penuh, lalu langsung dihentikan. Akibatnya, koneksi yang masih terbuka atau transaksi yang belum di-commit dapat terputus.
Shell yang berjalan di depan program Anda membuat kondisi ini lebih mungkin terjadi, karena shell menjadi PID 1 dan sebagian besar shell tidak meneruskan signal kepada child process. Beberapa shell menggantikan dirinya dengan command terakhir dalam string -c. Karena itu, terkadang program Anda tetap menjadi PID 1. Hasilnya bergantung pada shell dan string yang digunakan. Jangan menebak. Periksa:
docker compose exec -T web cat /proc/1/cmdline | tr '\0' ' '; echoJika PID 1 ditampilkan sebagai /bin/sh -c ..., bukan sebagai program Anda, ada dua solusi. Gunakan exec form dalam image, atau tetap gunakan shell dan serahkan proses tersebut dengan exec:
services:
web:
image: myapp:1.4
command: /bin/sh -c 'exec myapp --config /etc/myapp.toml'exec menggantikan proses shell dengan program Anda, bukan membuat child process. Dengan demikian, program Anda mewarisi PID 1 dan menerima signal.
Beberapa program membuat child process dan tidak pernah me-reap-nya. Hal ini meninggalkan proses zombie, karena PID 1 juga berfungsi sebagai reaper. Compose menyediakan opsi untuk kondisi tersebut:
services:
web:
image: myapp:1.4
init: true
stop_grace_period: 30sinit: true menjalankan proses init kecil sebagai PID 1 yang meneruskan signal kepada program Anda dan me-reap child process. stop_grace_period memberikan waktu tambahan jika shutdown benar-benar berlangsung lambat. Jika program Anda mengharapkan signal yang berbeda, stop_signal: SIGQUIT mengubah signal yang dikirim Compose. Periksa permintaan yang sudah ditetapkan oleh image dengan docker image inspect --format '{{.Config.StopSignal}}' nginx:1.27.
Jika sebuah stack selalu memerlukan waktu sepuluh detik untuk setiap service saat docker compose down dijalankan, hal itu menunjukkan bahwa tidak ada proses yang menangani SIGTERM. Perbaiki masalah tersebut sebelum menyalahkan tooling, lalu lihat perbedaan antara docker compose down dan stop untuk mengetahui resource yang dihapus oleh setiap subcommand.
Pemisahan antara exec dan shell yang sama juga muncul di satu tempat lain. Healthcheck yang ditulis sebagai test: ["CMD", "curl", "-f", "http://localhost/"] menjalankan binary secara langsung, sedangkan test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"] menjalankannya melalui shell agar || berfungsi. Menulis healthcheck Compose yang melaporkan kegagalan secara jujur membahas bagian tersebut lebih lanjut.
Menambahkan flag ke image resmi
Inilah yang dicari oleh sebagian besar pembaca. Anda ingin menambahkan satu flag ke postgres tanpa mengganggu skrip inisialisasi.
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
command: postgres -c max_connections=200 -c shared_buffers=256MB
volumes:
pgdata:Hanya command: yang berubah. Karena itu, docker-entrypoint.sh tetap dijalankan dan tetap mengeksekusi perintah yang Anda berikan. Periksa hasilnya, jangan hanya berasumsi:
docker compose up -d db
docker compose exec -T db psql -U postgres -c 'show max_connections;'Output seharusnya menampilkan 200. Jika masih menampilkan 100, jalankan docker compose config dan pastikan command yang Anda harapkan terdapat dalam output gabungan. Compose menggabungkan file override dengan mengganti command secara keseluruhan, bukan dengan menambahkannya. Jadi, file kedua yang juga menetapkan command: akan menang secara diam-diam.
${POSTGRES_PASSWORD} di atas diperluas oleh Compose pada host dari file .env Anda, sebelum container dibuat. File env dan secret di Compose menjelaskan tempat penyimpanan nilai tersebut yang aman.
Menjalankan migrasi satu kali dengan docker compose run
docker compose run membuat container baru dari definisi service yang sama dan mengganti perintahnya dengan apa pun yang Anda ketik setelah nama service. Entrypoint image tetap dijalankan, sehingga container disiapkan sama persis seperti container yang berjalan terus-menerus.
docker compose run --rm app python manage.py migrate--rmmenghapus container saat perintah selesai. Tanpa opsi ini, setiap eksekusi meninggalkan container yang berhenti, yang terlihat didocker compose ps -a.- Port tidak dipublikasikan. Container
runmengabaikanports:milik service, kecuali Anda menambahkan--service-ports, sehingga container tersebut tidak dapat berbenturan dengan service yang sudah berjalan. - Dependensi dijalankan terlebih dahulu. Semua yang tercantum dalam
depends_ondijalankan sebelum perintah Anda, sedangkan--no-depsmenonaktifkan perilaku tersebut. - Container mendapatkan nama yang dibuat secara otomatis, seperti
myproject-app-run-9f2c1a, sehingga tidak pernah berbenturan dengan container service.
Untuk mengganti entrypoint juga, tersedia sebuah flag:
docker compose run --rm --entrypoint /bin/sh app -c 'python manage.py migrate'Daftar argumen yang dihasilkan adalah /bin/sh -c 'python manage.py migrate', karena kata-kata setelah nama service tetap dianggap sebagai perintah. docker compose exec adalah tool lain dan bekerja secara berbeda: tool tersebut menjalankan proses di dalam container yang sudah berjalan, serta sepenuhnya mengabaikan entrypoint: dan command:. Gunakan run untuk tugas yang memerlukan container baru, dan exec untuk memeriksa container yang sedang berjalan. Lembar contekan perintah Compose memuat subperintah lainnya secara berdampingan.
Mengapa container saya langsung berhenti?
Mulai dengan kode keluar karena kode ini dapat mempersempit penyebab dengan cepat.
docker compose ps -a
docker compose logs appKode keluar 0 tanpa output. Perintah berhasil dijalankan dan selesai. Penyebab yang paling umum adalah override entrypoint: menghapus CMD milik image, sehingga entrypoint dijalankan dengan daftar argumen kosong dan tidak memiliki apa pun untuk diteruskan.
Error yang diakhiri dengan permission denied. Script tidak memiliki bit executable di dalam image. Biasanya, bit tersebut belum pernah ditetapkan pada file di repository. Tetapkan saat build dengan COPY --chmod=0755 entrypoint.sh /entrypoint.sh.
Error yang diakhiri dengan no such file or directory untuk file yang terlihat jelas di dalam image. Script menggunakan akhiran baris Windows. Akibatnya, baris pertamanya dibaca sebagai #!/bin/sh ditambah byte carriage return. Kernel kemudian mencari interpreter yang namanya mengandung byte tersebut dan tidak menemukannya. Jalankan dos2unix entrypoint.sh, lalu tambahkan * text eol=lf ke .gitattributes agar masalah ini tidak muncul lagi.
executable file not found in $PATH. Binary yang disebut dalam command: tidak ada di dalam image, atau Anda menulis shell built-in seperti cd di tempat yang hanya dapat menerima program nyata.
Membuka shell pada image yang entrypoint-nya gagal
Jika entrypoint berhenti sebelum Anda dapat memeriksa apa pun, ganti entrypoint tersebut:
docker compose run --rm --entrypoint /bin/sh appJika perintah itu mengembalikan executable file not found in $PATH, image tidak memiliki shell sama sekali. Image berbasis Distroless dan scratch sering kali tidak menyertakannya. Anda tetap dapat membaca filesystem dari luar tanpa menjalankan entrypoint:
docker create --name probe myapp:1.4
docker export probe | tar -tv | head -40
docker rm probeJika Anda memerlukan container tetap berjalan agar dapat meng-attach ke container tersebut berulang kali, jalankan proses yang tidak pernah berhenti. Letakkan konfigurasi ini dalam file override yang tidak Anda commit:
services:
app:
entrypoint: ["tail", "-f", "/dev/null"]
command: []command: [] sebenarnya tidak wajib karena pengaturan entrypoint: sudah menghapus CMD dari image. Namun, penulisan ini mencatat maksud tersebut bagi orang yang membaca file ini nanti. Jalankan container lalu masuk ke dalamnya:
docker compose -f compose.yaml -f compose.debug.yaml up -d app
docker compose exec app /bin/shSekarang jalankan entrypoint sebenarnya secara manual dan monitor bagian tempat proses berhenti. Dengan demikian, pesan error muncul di terminal, bukan di container yang sudah berhenti setengah detik sebelumnya. Jika Anda masih menyusun stack pertama, stack Compose pertama pada VPS membahas struktur file yang diasumsikan oleh semua langkah di atas.
FAQ
Mengapa container saya langsung berhenti setelah docker compose up?
Periksa docker compose ps -a untuk melihat kode keluar. Kode keluar 0 tanpa output biasanya berarti Anda menetapkan entrypoint: pada service, yang juga menghapus CMD milik image. Akibatnya, entrypoint berjalan dengan daftar argumen kosong lalu selesai. Tambahkan kembali argumen tersebut dengan command:. Error yang diakhiri permission denied berarti skrip entrypoint tidak memiliki bit executable. Error yang diakhiri no such file or directory pada file yang ada berarti skrip menggunakan akhiran baris Windows. Akibatnya, baris shebang merujuk interpreter yang tidak tersedia.
Apakah menetapkan entrypoint di Compose menghapus CMD milik image?
Ya. Jika entrypoint tidak bernilai null, Compose mengabaikan perintah default yang dideklarasikan oleh image. Perilaku ini terdokumentasi dan sesuai dengan docker run --entrypoint. Alasannya, CMD milik image ditulis sebagai argumen untuk ENTRYPOINT milik image tersebut. Setelah entrypoint diganti, argumen lama tidak lagi memiliki tujuan. Tetapkan command: pada service yang sama jika entrypoint baru masih memerlukan argumen.
Apakah string dalam command Compose dijalankan melalui shell?
Tidak. Berbeda dengan CMD pada Dockerfile, string dalam command: di Compose dipecah menjadi argumen dan dieksekusi secara langsung, tanpa pembungkus /bin/sh -c. Karena itu, $VARIABLE tidak pernah diperluas oleh shell di dalam container. Panggil shell secara eksplisit jika diperlukan, seperti pada command: /bin/sh -c 'echo "hello $$HOSTNAME"'. $$ ganda meng-escape tanda dolar agar Compose meneruskannya ke container, bukan memperluasnya pada host.
Mengapa docker compose down memerlukan sepuluh detik untuk satu container?
Compose mengirim SIGTERM ke PID 1, menunggu stop_grace_period (10 detik secara default), lalu mengirim SIGKILL. Kernel tidak menerapkan tindakan sinyal default pada PID 1. Karena itu, program tanpa handler SIGTERM mengabaikan sinyal tersebut dan selalu menunggu hingga periode itu berakhir. Cari tahu proses yang sebenarnya menjadi PID 1 dengan docker compose exec -T app cat /proc/1/cmdline | tr '\0' ' '. Jika proses tersebut adalah shell, ubah image ke bentuk exec atau tulis exec di dalam string shell. Jika proses membuat proses turunan tetapi tidak pernah melakukan reaping, tetapkan init: true pada service.