SSD Nodes Learn 🎉 VPS mulai $5.50/bln
Panduan Matt ConnorOleh Matt Connor

Docker Compose exec: Masuk ke Shell Interaktif

Pelajari cara memakai docker compose exec untuk membuka shell pada service yang berjalan, serta run --rm saat service berhenti atau tidak boleh diganggu.

Dapatkan shell interaktif dengan docker compose exec

docker compose exec web bash membuka shell interaktif di dalam container yang sedang berjalan sebagai service web. Nama setelah exec adalah nama service dari compose.yaml, bukan nama container. Jika image tidak memiliki bash, gunakan sh.

docker compose ps
docker compose exec web bash

Jalankan docker compose ps terlebih dahulu. Perintah tersebut harus menampilkan web dengan status running. Kemudian, perintah kedua menempatkan Anda pada prompt di dalam container. exit atau Ctrl-D mengembalikan Anda ke host. Service tetap berjalan setelah Anda keluar karena exec memulai proses kedua di samping proses utama. Menutup shell tidak memengaruhi PID 1 (process id 1), yaitu proses yang dirancang untuk dijalankan oleh container.

Itu adalah salah satu dari dua cara untuk masuk. exec bergabung dengan container yang sudah ada. docker compose run membuat container baru dari definisi service yang sama. Hampir semua hal lain dalam panduan ini mengikuti satu perbedaan tersebut.

Mengapa -it bersifat opsional di Compose, tetapi diperlukan dengan docker biasa

Dua flag mengatur bagian interaktif suatu sesi. -i menjaga stdin tetap terbuka, sehingga input yang Anda ketik diteruskan ke proses. -t mengalokasikan pseudo-terminal yang disebut TTY, sehingga shell menampilkan prompt dan menangani tombol panah. docker exec biasa menonaktifkan keduanya secara default. Karena itu, setiap contoh yang telah Anda lihat menuliskan docker exec -it. docker compose exec mengaktifkan keduanya untuk Anda, sehingga docker compose exec -it web bash dan docker compose exec web bash menghasilkan hal yang sama. Compose tetap menerima -it agar kebiasaan lama tetap dapat digunakan.

Anda akan segera mengetahui jika TTY tidak tersedia. Shell berjalan, tetapi tidak menampilkan prompt dan Ctrl-C tidak pernah diteruskan ke proses. Kasus sebaliknya, ketika Anda harus meminta Compose untuk tidak mengalokasikan TTY, memiliki flag tersendiri dan bagian tersendiri di bawah.

Apa yang harus dilakukan jika image tidak memiliki bash

Jika Anda meminta bash dari image berbasis Alpine, perintah exec akan gagal seperti ini:

OCI runtime exec failed: exec failed: unable to start container process: exec: "bash": executable file not found in $PATH: unknown

Pesan tersebut bukan menunjukkan masalah pada exec. Pesan itu menyatakan bahwa biner yang Anda minta tidak ada di dalam image. Alpine menggunakan BusyBox, yang menyediakan ash sebagai /bin/sh dan sama sekali tidak menyediakan bash. Karena itu, minta sh:

docker compose exec web sh

Image berbasis Debian dan Ubuntu, termasuk tag -slim, menyediakan bash. bash juga menyediakan riwayat perintah dan pelengkapan yang lebih baik. Jadi, coba bash terlebih dahulu, lalu gunakan sh jika gagal. sh tersedia di hampir semua image untuk keperluan umum.

Beberapa image sama sekali tidak memiliki shell. Image distroless dan image yang dibuat FROM scratch hanya berisi biner aplikasi beserta library-nya. Ini memang disengaja karena shell yang tidak tersedia tidak dapat digunakan untuk menyerang Anda. Pada image tersebut, sh gagal dengan pesan yang sama dan tidak ada pilihan lain untuk dicoba. Ada 2 pendekatan yang dapat digunakan. Image distroless milik Google menyediakan tag :debug yang menambahkan shell BusyBox. Dengan demikian, Anda dapat mengganti tag tersebut sementara untuk masuk ke image. Atau, jalankan container terpisah di dalam namespace milik target:

CID=$(docker compose ps -q web)
docker run --rm -it --network "container:$CID" --pid "container:$CID" nicolaka/netshoot

Kini Anda memiliki tool dari netshoot yang diarahkan ke jaringan aplikasi. Karena itu, curl localhost:8080 dan ss -lntp berperilaku seolah-olah Anda berada di dalam aplikasi tersebut. Filesystem yang Anda lihat adalah milik netshoot, bukan milik aplikasi. Karena namespace proses digunakan bersama, ls /proc/1/root/ dapat mengakses file milik target ketika Anda adalah root.

Saat service tidak berjalan, gunakan docker compose run --rm

exec memerlukan container yang sedang berjalan. Jika diarahkan ke service yang berhenti, perintah ini akan menolak:

service "web" is not running

Perintah ini tidak akan menjalankan apa pun untuk Anda. docker compose run akan:

docker compose run --rm web bash

run membuat container baru dari definisi service web, dengan image, environment, volume, dan network yang sama, lalu mengganti perintah service dengan perintah yang Anda ketik. --rm menghapus container tersebut saat Anda keluar. Jika --rm tidak digunakan, container yang tersisa akan menumpuk dengan nama seperti myproject-web-run-4f1c2b. docker compose ps -a akan menampilkannya, dan tidak ada proses lain yang akan membersihkannya.

Dua perilaku run sering mengejutkan pengguna. Perintah ini tidak memublikasikan port service kecuali Anda menambahkan --service-ports. Hal ini memang disengaja. Container kedua yang mengikat port host 8080 saat container pertama masih menggunakannya akan gagal dengan bind: address already in use. run juga menjalankan semua service yang tercantum di bawah depends_on sebelum shell Anda muncul. Pemeriksaan singkat dapat menyebabkan database dan cache ikut berjalan. --no-deps menonaktifkan perilaku tersebut.

run menjalankan ENTRYPOINT image, sedangkan exec tidak. exec langsung menjalankan perintah Anda di dalam container yang sudah ada. Karena itu, skrip entrypoint tidak pernah menerima perintah tersebut. Dengan run, bash diteruskan sebagai argumen ke skrip itu. Banyak image resmi mengakhiri entrypoint-nya dengan exec "$@". Karena itu, argumen tersebut langsung diteruskan dan Anda mendapatkan shell. Skrip yang menafsirkan argumennya sendiri akan melakukan hal lain. Dalam kasus tersebut, Anda dapat mengganti entrypoint untuk satu kali eksekusi:

docker compose run --rm --entrypoint sh web

Inilah alasan paling umum mengapa perintah yang berjalan dengan exec berperilaku berbeda dengan run. Perbedaan antara command dan entrypoint menjelaskan bagian konfigurasi image mana yang Anda ganti setiap kali.

exec atau run: cara memilih

  • exec memerlukan container yang sedang berjalan. run tidak memerlukannya, dan run dapat memulai dependensi.
  • exec melihat daftar proses yang sedang berjalan dan file dalam kondisi saat ini, termasuk apa pun yang telah ditulis aplikasi sejak mulai berjalan. run mendapatkan salinan image yang bersih, sehingga semua itu tidak tersedia.
  • exec melewati entrypoint. run menjalankannya.
  • run meninggalkan container kecuali Anda memberikan --rm.

Gunakan exec untuk melihat apa yang sebenarnya sedang terjadi. Gunakan run --rm untuk salinan sementara dari lingkungan yang sama, untuk menjalankan perintah migrasi satu kali, atau ketika service sebenarnya tidak akan bertahan cukup lama sehingga Anda dapat menjalankan exec di dalamnya.

Flag exec yang berguna: user, direktori kerja, dan replica

Sebagian besar image beralih ke user non-root. Karena itu, pemasangan alat diagnostik di dalam shell exec berhenti pada tahap ini:

E: Could not open lock file /var/lib/dpkg/lock-frontend - open (13: Permission denied)

-u root memberi Anda shell root di container yang sama:

docker compose exec -u root web sh

-w /srv/app menetapkan direktori kerja hanya untuk perintah tersebut. -e KEY=value menambahkan variabel lingkungan ke sesi Anda, bukan ke service. Jika sebuah service menjalankan lebih dari satu replica, --index 2 menentukan container yang Anda gunakan. Jika yang sedang Anda telusuri adalah kepemilikan file pada direktori yang di-mount, PUID dan PGID pada image container menjelaskan mengapa ID numerik, bukan nama user, menentukan pihak yang dapat menulis di sana.

Mendapatkan shell psql atau mysql di dalam container database

Client sudah tersedia di dalam image database. Karena itu, Anda tidak memerlukannya di host dan tidak perlu memublikasikan port:

docker compose exec db psql -U postgres -d app
docker compose exec db mariadb -u root -p

Image Postgres menyediakan psql, image MySQL menyediakan mysql, dan image MariaDB menyediakan mariadb. Koneksi dibuat dari dalam container. Karena itu, cara ini tetap berfungsi meskipun file compose sama sekali tidak memublikasikan port database. Ini adalah pengaturan yang lebih aman: tidak ada pihak di Internet yang dapat mengakses port yang tidak pernah Anda publikasikan.

Ada satu jebakan yang dapat menghabiskan waktu hingga berjam-jam. Shell memperluas variabel di host sebelum Docker menerima perintah. Karena itu, -U "$POSTGRES_USER" mengirim string kosong jika variabel tersebut hanya tersedia di dalam container. Gunakan tanda kutip tunggal dan shell di dalam container agar variabel diperluas di tempat yang benar:

docker compose exec db sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB"'

Jangan gunakan docker compose run --rm db tanpa perintah di sini. Perintah tersebut akan menjalankan server Postgres kedua pada volume data yang sama, sehingga gagal dijalankan:

FATAL:  lock file "postmaster.pid" already exists

File lock menjalankan fungsinya. Dua server yang menulis ke satu direktori data dapat merusak data tersebut. Saat database aktif, gunakan exec untuk masuk ke container yang sedang berjalan. Apakah database sebaiknya dijalankan di Compose atau tidak merupakan keputusan terpisah. Menjalankan database di Docker atau di host menjelaskan pertimbangannya.

Layanan yang memerlukan konsol saat startup: stdin_open dan tty

exec dan run digunakan untuk shell yang Anda buka secara manual. Service yang proses utamanya secara alami bersifat interaktif memerlukan dua key dalam file compose:

services:
  console:
    image: python:3.12-slim
    command: python
    stdin_open: true
    tty: true

stdin_open: true adalah docker run -i dan tty: true adalah docker run -t. Tanpa keduanya, container langsung start lalu exit dengan code 0, dan docker compose ps -a menampilkan Exited (0). Tidak ada yang crash. python yang tidak memiliki terminal pada stdin langsung membaca end of file lalu berhenti secara normal. Ini adalah perilaku yang benar untuk program yang tidak menerima input dari pengguna.

Jika kedua key telah disetel, attach ke proses yang sedang berjalan:

docker attach $(docker compose ps -q console)

Lakukan detach dengan Ctrl-P lalu Ctrl-Q. Proses akan tetap berjalan. Urutan ini hanya berfungsi jika container memiliki TTY dan stdin yang terbuka. Sebaliknya, Ctrl-C mengirimkan interrupt ke PID 1 dan menghentikan service.

Biarkan kedua key tersebut tidak disetel untuk service biasa. Web server tidak pernah membaca stdin. Selain itu, tty: true membuat banyak program beralih ke output berwarna dan line buffering karena program tersebut menganggap ada orang yang sedang mengawasi. Akibatnya, docker compose logs dipenuhi escape code.

Mengapa exec terjadwal gagal di cron dan CI: flag -T

Perintah exec yang berhasil dijalankan di terminal gagal di dalam cron job atau runner continuous integration (CI):

the input device is not a TTY

Compose secara default meminta pseudo-terminal, sedangkan cron tidak memberikan terminal kepada job. Karena itu, permintaan tersebut gagal sebelum perintah Anda dijalankan. -T menonaktifkan permintaan tersebut:

0 3 * * * docker compose -f /srv/app/compose.yaml exec -T db pg_dump -U postgres -Fc app > /srv/backups/app.dump

-T penting karena alasan lain. TTY mengubah aliran byte saat keluar, sehingga dump terkompresi yang melewatinya akan rusak. Output apa pun yang dialihkan atau disalurkan melalui pipe memerlukan -T.

Ada 2 detail cron lainnya. Berikan -f dengan path absolut, karena cron menjalankan job dari direktori home yang tidak memiliki file compose. Compose kemudian berhenti dengan no configuration file provided: not found. Selain itu, exec mengembalikan kode keluar dari perintah yang dijalankannya. Karena itu, pg_dump yang gagal menyebabkan script gagal di bawah set -e, bukan menulis backup kosong dan melaporkan keberhasilan. Perintah sehari-hari lainnya dirangkum dalam lembar contekan perintah Compose yang sebaiknya disimpan di dekat script tersebut.

Mengapa perubahan yang Anda buat di dalam container hilang

Anda menginstal alat dengan exec, mengedit file konfigurasi, memperbaiki masalah, lalu seminggu kemudian perbaikan tersebut hilang. Ini adalah perilaku writable layer milik container yang berjalan sesuai desain. docker compose up -d setelah ada perubahan pada image tag atau service definition akan menghancurkan container lama dan membuat container baru dari image. Semua pengeditan manual ikut hilang bersama container lama.

docker compose restart berbeda. Operasi ini menghentikan dan menjalankan container yang sama, sehingga pengeditan manual tetap ada. Karena itu, perbaikan manual dapat terlihat bertahan selama berminggu-minggu, lalu hilang saat pembaruan yang tidak terkait. Named volumes dan bind mounts tetap ada setelah kedua operasi tersebut karena datanya disimpan di luar container. bind mounts dan named volumes menjelaskan cara memilih salah satunya untuk data yang ingin Anda pertahankan.

Anggap shell exec sebagai tempat untuk membaca dan menguji. Setelah Anda mengetahui perbaikannya, tuliskan perbaikan tersebut di tempat yang dapat mempertahankannya: masukkan package ke dalam Dockerfile dan masukkan setting ke dalam compose file. Lalu docker compose up -d untuk menerapkannya, kemudian lakukan exec lain untuk memastikan container baru benar-benar memilikinya.

FAQ

Apa perbedaan antara docker compose exec dan docker compose run?

exec menjalankan perintah di dalam container yang sudah berjalan, terpisah dari proses utama, dan melewati entrypoint image. run membuat container baru dari definisi service yang sama dengan image, environment, volume, dan network yang sama, meneruskan perintah Anda melalui entrypoint, lalu menjalankan service depends_on terlebih dahulu. run juga tidak memublikasikan port service, kecuali Anda menambahkan --service-ports. Gunakan exec untuk memeriksa service yang sedang berjalan. Gunakan run --rm saat service berhenti atau saat Anda tidak ingin mengganggunya.

Mengapa docker compose exec melaporkan bahwa service tidak berjalan?

exec terhubung ke container yang sudah ada dan tidak dapat membuat container baru. Karena itu, service yang berhenti atau mengalami crash menghasilkan service "web" is not running. Periksa docker compose ps -a, yang mencantumkan container yang sudah keluar dengan status seperti Exited (1), lalu baca docker compose logs web untuk mengetahui alasan service tersebut berhenti. Untuk tetap mendapatkan shell, jalankan docker compose run --rm --entrypoint sh web. Perintah itu membuat container baru dari definisi service yang sama tanpa menjalankan perintah start yang bermasalah.

Bagaimana cara membuka shell jika image tidak memiliki bash?

Kegagalan docker compose exec web bash dengan exec: "bash": executable file not found in $PATH berarti bash tidak tersedia di dalam image. Kondisi ini normal untuk image yang dibangun di atas Alpine. Gunakan docker compose exec web sh karena BusyBox menyediakan /bin/sh. Image Distroless dan scratch sama sekali tidak memiliki shell, sehingga tidak ada perintah exec yang dapat berfungsi. Beralihlah ke tag :debug milik image tersebut jika penerbitnya menyediakannya, atau jalankan container debug dalam namespace target menggunakan docker run --rm -it --network "container:$CID" --pid "container:$CID" nicolaka/netshoot, dengan $CID yang berasal dari docker compose ps -q web.

Mengapa perintah exec saya gagal dengan "the input device is not a TTY" di cron?

docker compose exec secara default meminta terminal semu, sedangkan cron tidak menyediakannya. Karena itu, permintaan tersebut gagal sebelum perintah Anda dijalankan. Tambahkan -T untuk menonaktifkannya: docker compose exec -T db pg_dump -U postgres app. Gunakan -T untuk output yang dialihkan atau disalurkan melalui pipe, karena TTY mengubah aliran byte dan dapat merusak dump biner. Di cron, teruskan juga -f dengan path absolut ke file compose Anda. Jika tidak, Compose keluar dengan no configuration file provided: not found.

Apakah perubahan yang saya buat di dalam container dengan exec tetap ada setelah restart?

Perubahan tersebut tetap ada setelah docker compose restart karena operasi itu menggunakan kembali container yang sama. Perubahan tersebut hilang setelah docker compose up -d jika ada perubahan pada image atau konfigurasi, karena operasi itu membuat ulang container dari image dan membuang writable layer-nya. Data yang ditulis ke named volume atau bind mount tetap ada setelah keduanya, karena data tersebut berada di luar container. Lakukan perubahan diagnostik dengan exec, lalu masukkan versi permanennya ke Dockerfile atau file compose.