Docker Compose exec দিয়ে interactive shell নিন
চলমান Compose service-এর container-এ docker compose exec দিয়ে shell খুলুন। service বন্ধ থাকলে বা বদলাতে না চাইলে run --rm ব্যবহার করুন।
docker compose exec দিয়ে একটি interactive shell নিন
docker compose exec web bash ইতিমধ্যে web service হিসেবে চলমান container-এর ভেতরে একটি interactive shell খুলে। exec-এর পরের নামটি আপনার compose.yaml-এ থাকা service name, container name নয়। image-এ bash না থাকলে তার বদলে sh ব্যবহার করুন।
docker compose ps
docker compose exec web bashপ্রথমে docker compose ps চালান। এতে web-কে running state-এ দেখানোর কথা। এরপর দ্বিতীয় command আপনাকে container-এর ভেতরের prompt-এ নিয়ে যাবে। exit অথবা Ctrl-D চাপলে host-এ ফিরে আসবেন। আপনি shell থেকে বের হওয়ার পরও service চলতে থাকবে, কারণ exec মূল process-এর পাশে একটি দ্বিতীয় process চালু করেছে। আপনার shell বন্ধ হলেও PID 1 (process id 1), অর্থাৎ container যে process চালানোর জন্য তৈরি হয়েছে, সেটিতে কোনো প্রভাব পড়বে না।
এটি container-এ প্রবেশের দুটি পদ্ধতির একটি। exec আগে থেকেই থাকা container-এ যুক্ত হয়। docker compose run একই service definition থেকে একটি নতুন container তৈরি করে। এই guide-এর প্রায় সব বিষয়ই এই একক পার্থক্য থেকে বোঝা যায়।
Compose-এ -it ঐচ্ছিক, কিন্তু সাধারণ docker-এ আবশ্যক কেন
সেশনের interactive অংশ দুটি flag নিয়ন্ত্রণ করে। -i stdin খোলা রাখে, তাই আপনি যা টাইপ করেন তা process-এ পৌঁছায়। -t একটি pseudo terminal বরাদ্দ করে, যাকে TTY বলা হয়; ফলে shell prompt দেখায় এবং arrow key পরিচালনা করে। সাধারণ docker exec ডিফল্টভাবে দুটিই বন্ধ রাখে। তাই আপনি দেখা প্রতিটি উদাহরণে docker exec -it লেখা হয়েছে। docker compose exec আপনার হয়ে দুটিই চালু করে। তাই docker compose exec -it web bash এবং docker compose exec web bash একই কাজ করে। পুরোনো ব্যবহারের অভ্যাস বজায় রাখার জন্য Compose এখনও -it গ্রহণ করে।
কয়েক সেকেন্ডের মধ্যেই TTY অনুপস্থিত কি না বুঝতে পারবেন। shell চলে, কিন্তু কোনো prompt দেখায় না এবং Ctrl-C process-এ পৌঁছায় না। বিপরীত পরিস্থিতিতে, অর্থাৎ Compose-কে TTY বরাদ্দ না করতে বললে, তার জন্য আলাদা flag এবং নিচে আলাদা section রয়েছে।
যখন image-এ bash থাকে না
Alpine ভিত্তিক image-এ bash চেয়ে exec চালালে এভাবে ব্যর্থ হয়:
OCI runtime exec failed: exec failed: unable to start container process: exec: "bash": executable file not found in $PATH: unknownএই বার্তাটি exec-এর সমস্যা নির্দেশ করে না। এর অর্থ, আপনি যে binary চেয়েছেন সেটি image-এ নেই। Alpine-এ BusyBox থাকে। এটি ash-কে /bin/sh হিসেবে দেয় এবং এতে bash একেবারেই নেই। তাই sh চাইুন:
docker compose exec web shDebian ও Ubuntu ভিত্তিক image-এ, যার মধ্যে -slim tag-ও রয়েছে, bash থাকে। bash আপনাকে command history এবং উন্নত completion দেয়। তাই আগে bash ব্যবহার করে দেখুন। ব্যর্থ হলে sh-এ fallback করুন। প্রায় সব general-purpose image-এ sh থাকে।
কিছু image-এ কোনো shell-ই থাকে না। Distroless image এবং FROM scratch তৈরি image-এ ইচ্ছাকৃতভাবে শুধু application binary ও তার library থাকে। কারণ অনুপস্থিত shell ব্যবহার করে আক্রমণ করা যায় না। এসব image-এ sh একই বার্তাসহ ব্যর্থ হয়, এবং চেষ্টা করার মতো আর কিছু থাকে না। দুটি পদ্ধতি কার্যকর। Google-এর distroless image-গুলো এমন :debug tag প্রকাশ করে, যাতে BusyBox shell যোগ থাকে। তাই সাময়িকভাবে tag পরিবর্তন করে container-এ প্রবেশ করা যায়। অথবা target-এর namespace-এর ভিতরে একটি পৃথক container চালান:
CID=$(docker compose ps -q web)
docker run --rm -it --network "container:$CID" --pid "container:$CID" nicolaka/netshootএখন netshoot-এর tool-গুলো application-এর network-এ নির্দেশিত অবস্থায় আছে। তাই curl localhost:8080 এবং ss -lntp এমন আচরণ করবে যেন আপনি application-এর ভিতরেই আছেন। আপনি যে filesystem দেখছেন সেটি netshoot-এর, app-এর নয়। Process namespace শেয়ার করা থাকায় root হিসেবে ls /proc/1/root/ চালালে target-এর নিজস্ব file-এ পৌঁছানো যায়।
service চলমান না থাকলে docker compose run --rm ব্যবহার করুন
exec চালানোর জন্য একটি চলমান container প্রয়োজন। বন্ধ থাকা কোনো service-এ এটি চালালে কমান্ডটি প্রত্যাখ্যান করা হয়:
service "web" is not runningএটি আপনার জন্য কোনো কিছু চালু করবে না। docker compose run চালু করবে:
docker compose run --rm web bashrun web service definition থেকে একটি নতুন container তৈরি করে। এতে একই image, environment, volumes এবং networks থাকে। আপনি যে command লিখেছেন, সেটি service-এর command-এর পরিবর্তে ব্যবহার করা হয়। বের হয়ে গেলে --rm ওই container মুছে দেয়। --rm বাদ দিলে অবশিষ্ট container-গুলো myproject-web-run-4f1c2b-এর মতো নামে জমতে থাকে। docker compose ps -a সেগুলো দেখাবে, কিন্তু অন্য কোনো প্রক্রিয়া সেগুলো পরিষ্কার করবে না।
run-এর দুটি আচরণ অনেককে অবাক করে। আপনি --service-ports যোগ না করলে এটি service-এর ports publish করে না। এটি ইচ্ছাকৃত। প্রথম container host port 8080 ধরে রাখার সময় দ্বিতীয় container একই port bind করতে গেলে bind: address already in use-এর কারণে ব্যর্থ হবে। এটি shell দেখানোর আগে depends_on-এর অধীনে service তালিকাভুক্ত সবকিছু চালু করে। তাই দ্রুত কোনো container-এর ভেতর দেখার সময় database এবং cache-ও চালু হয়ে যেতে পারে। --no-deps এটি এড়িয়ে যায়।
run image-এর ENTRYPOINT-এর মধ্য দিয়ে চলে, কিন্তু exec তা করে না। exec বিদ্যমান container-এর মধ্যে সরাসরি আপনার command চালু করে। তাই entrypoint script সেটি দেখতে পায় না। run-এর ক্ষেত্রে আপনার bash ওই script-এর argument হিসেবে পৌঁছায়। অনেক official image তাদের entrypoint-এর শেষে exec "$@" রাখে। ফলে command সরাসরি পরবর্তী পর্যায়ে চলে যায় এবং আপনি আপনার shell পান। তবে কোনো script যদি নিজের argument নিজে interpret করে, তাহলে সেটি ওই argument নিয়ে অন্য কাজ করবে। সেই একবারের run-এর জন্য entrypoint প্রতিস্থাপন করতে পারেন:
docker compose run --rm --entrypoint sh webএটাই exec-এর অধীনে কাজ করা কোনো command run-এর অধীনে ভিন্ন আচরণ করার সবচেয়ে সাধারণ কারণ। command এবং entrypoint-এর বিভাজন প্রতিবার image configuration-এর কোন অংশটি আপনি প্রতিস্থাপন করছেন, তা ব্যাখ্যা করে।
exec বা run: কোনটি বেছে নেবেন
execচালানোর জন্য একটি running container প্রয়োজন।run-এর জন্য তা প্রয়োজন হয় না এবংrunপ্রয়োজনে dependency-ও চালু করতে পারে।execবর্তমান live process list এবং এই মুহূর্তের file state দেখতে পায়। service চালু হওয়ার পর application যা লিখেছে, তাও এতে দেখা যায়।runimage-এর একটি clean copy ব্যবহার করে, তাই এসব পরিবর্তন সেখানে থাকে না।execentrypoint এড়িয়ে যায়।runentrypoint চালায়।- `
--rmpass না করলেrun` একটি container রেখে যায়।
বাস্তবে কী ঘটছে তা দেখতে exec ব্যবহার করুন। একই environment-এর অস্থায়ী copy, একবারের migration command, অথবা আসল service যথেষ্ট সময় running না থাকায় exec করা না গেলে `run --rm` ব্যবহার করুন।
প্রয়োজনীয় exec flag: user, working directory এবং replicas
বেশিরভাগ image non-root user হিসেবে চলে। তাই আপনার exec shell-এর ভেতরে diagnostic tool install করার প্রক্রিয়া এখানে থেমে যায়:
E: Could not open lock file /var/lib/dpkg/lock-frontend - open (13: Permission denied)-u root একই container-এ root shell দেয়:
docker compose exec -u root web sh-w /srv/app শুধু ওই command-এর জন্য working directory নির্ধারণ করে। -e KEY=value আপনার session-এ একটি environment variable যোগ করে, service-এ নয়। কোনো service একাধিক replica হিসেবে চললে --index 2 নির্ধারণ করে আপনি কোন container-এ প্রবেশ করবেন। Mounted directory-র file ownership নিয়ে অনুসন্ধান করলে container image-এ PUID এবং PGID ব্যাখ্যা করে কেন user name নয়, numeric id-ই নির্ধারণ করে সেখানে কে লিখতে পারবে।
ডেটাবেস কনটেইনারের ভেতরে psql বা mysql shell খুলুন
client ইতিমধ্যে database image-এর ভেতরে আছে। তাই host-এ client ইনস্টল করার প্রয়োজন নেই এবং port publish করারও প্রয়োজন নেই:
docker compose exec db psql -U postgres -d app
docker compose exec db mariadb -u root -pPostgres image-এ psql, MySQL image-এ mysql এবং MariaDB image-এ mariadb থাকে। সংযোগটি কনটেইনারের ভেতর থেকেই তৈরি হয়। তাই compose file-এ কোনো database port publish না করলেও এটি কাজ করে। এটাই বেশি নিরাপদ ব্যবস্থা। আপনি যে port publish করেননি, Internet থেকে কেউ সেই port-এ পৌঁছাতে পারবে না।
একটি ভুলের কারণে অনেকের কয়েক ঘণ্টা নষ্ট হয়। Docker command দেখার আগে host-এর shell variable expand করে। তাই কোনো variable শুধু কনটেইনারের ভেতরে থাকলে -U "$POSTGRES_USER" একটি empty string পাঠায়। Single quote ব্যবহার করে কনটেইনারের ভেতরে shell চালালে variable সঠিক জায়গায় expand হয়:
docker compose exec db sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB"'এখানে কোনো command ছাড়া docker compose run --rm db ব্যবহার করবেন না। এটি একই data volume-এর বিরুদ্ধে দ্বিতীয় Postgres server চালু করে। ফলে server start হতে ব্যর্থ হয়:
FATAL: lock file "postmaster.pid" already existsLock file সঠিক কাজ করছে। একই data directory-তে দুটি server লিখলে data corrupt হয়ে যেত। Database চালু থাকা অবস্থায় running container-এ exec করুন। Database আদৌ Compose-এ রাখা উচিত কি না, এটি আলাদা সিদ্ধান্ত। Docker-এ বা host-এ database চালানো অংশে এই দুই ব্যবস্থার সুবিধা-অসুবিধা ব্যাখ্যা করা হয়েছে।
স্টার্টআপের সময় console প্রয়োজন এমন service: stdin_open এবং tty
exec এবং run হাতে খোলা shell-এর জন্য ব্যবহৃত হয়। যে service-এর প্রধান process স্বভাবগতভাবেই interactive, তার compose file-এ দুটি key প্রয়োজন:
services:
console:
image: python:3.12-slim
command: python
stdin_open: true
tty: truestdin_open: true হলো docker run -i এবং tty: true হলো docker run -t। এগুলো না থাকলে container শুরু হয়ে সঙ্গে সঙ্গে code 0 দিয়ে বন্ধ হয়ে যায়, এবং docker compose ps -a-এ Exited (0) দেখা যায়। কোনো ত্রুটি ঘটেনি। stdin-এ terminal না থাকলে python সঙ্গে সঙ্গে end of file পড়ে স্বাভাবিকভাবে বন্ধ হয়ে যায়। কেউ program-এ input দিচ্ছে না থাকলে এটিই সঠিক আচরণ।
দুটি key সেট করা থাকলে চলমান process-এ সংযুক্ত হন:
docker attach $(docker compose ps -q console)Ctrl-P তারপর Ctrl-Q চাপলে detach করা যায়। এতে process চলতে থাকে। এই পদ্ধতি শুধু তখনই কাজ করে, যখন container-এ TTY এবং stdin দুটিই খোলা থাকে। Ctrl-C চাপলে PID 1-এ interrupt পাঠানো হয় এবং service বন্ধ হয়ে যায়।
সাধারণ service-এর ক্ষেত্রে দুটি key-ই বন্ধ রাখুন। Web server কখনো stdin পড়ে না। tty: true অনেক program-কে color output এবং line buffering চালু করতে বাধ্য করে, কারণ program ধরে নেয় যে কেউ output দেখছে। এতে docker compose logs escape code-এ পূর্ণ হয়ে যেতে পারে।
cron এবং CI-তে scripted exec কেন ব্যর্থ হয়: -T flag
আপনার terminal-এ কাজ করা একটি exec command cron job বা continuous integration (CI) runner-এর ভেতরে ব্যর্থ হয়:
the input device is not a TTYCompose ডিফল্টভাবে একটি pseudo terminal চায়। কিন্তু cron job-কে কোনো terminal দেয় না। তাই আপনার command চালানোর আগেই অনুরোধটি ব্যর্থ হয়। -T এই অনুরোধ বন্ধ করে:
0 3 * * * docker compose -f /srv/app/compose.yaml exec -T db pg_dump -U postgres -Fc app > /srv/backups/app.dump-T আরও একটি কারণে গুরুত্বপূর্ণ। TTY বাইরে পাঠানোর সময় byte stream-এর বিন্যাস পরিবর্তন করে। তাই এর মধ্য দিয়ে যাওয়া compressed dump ক্ষতিগ্রস্ত অবস্থায় পৌঁছায়। Redirect করা বা pipe করা যেকোনো output-এর জন্য -T প্রয়োজন।
cron-এর আরও দুটি বিষয় মনে রাখুন। -f-এর সঙ্গে একটি absolute path দিন। কারণ cron job-টি home directory থেকে চালায়, যেখানে কোনো compose file নেই। ফলে Compose no configuration file provided: not found দেখিয়ে থেমে যায়। এছাড়া exec যে command চালায়, তার exit code-ই ফেরত দেয়। তাই কোনো pg_dump ব্যর্থ হলে set -e-এর অধীনে আপনার script-ও ব্যর্থ হবে; খালি backup লিখে success জানানোর মতো আচরণ হবে না। দৈনন্দিন ব্যবহারের বাকি command-গুলো Compose command cheat sheet-এ সংকলিত আছে। এই cheat sheet script-গুলোর পাশে রাখা উপযোগী।
কনটেইনারের ভেতরে করা পরিবর্তন কেন হারিয়ে যায়
আপনি exec ব্যবহার করে একটি টুল ইনস্টল করেন, configuration file সম্পাদনা করেন, সমস্যাটি ঠিক করেন, এবং এক সপ্তাহ পর দেখেন যে সমাধানটি আর নেই। এটি কনটেইনারের writable layer-এর স্বাভাবিক আচরণ। docker compose up -d image tag বা service definition পরিবর্তনের পর পুরোনো কনটেইনারটি ধ্বংস করে এবং image থেকে নতুন কনটেইনার তৈরি করে; ফলে পুরোনো কনটেইনারে করা প্রতিটি হাতে-কলমে পরিবর্তনও হারিয়ে যায়।
docker compose restart ভিন্নভাবে কাজ করে। এটি একই কনটেইনার বন্ধ করে আবার চালু করে, তাই হাতে করা পরিবর্তন এতে টিকে থাকে। এ কারণেই কোনো manual fix কয়েক সপ্তাহ কার্যকর আছে বলে মনে হতে পারে, কিন্তু সম্পর্কহীন কোনো update-এর সময় তা অদৃশ্য হয়ে যায়। Named volume এবং bind mount উভয় operation-এর পরেও টিকে থাকে, কারণ তাদের data কনটেইনারের বাইরে থাকে। কোন data-এর জন্য কোনটি বেছে নেবেন, তা bind mount এবং named volume-এ ব্যাখ্যা করা হয়েছে।
তাই exec shell-কে পড়া ও পরীক্ষা করার স্থান হিসেবে ব্যবহার করুন। সমাধানটি নিশ্চিত হওয়ার পর সেটি এমন জায়গায় লিখুন যেখানে তা টিকে থাকবে: package হলে Dockerfile-এ, setting হলে compose file-এ। এরপর docker compose up -d চালিয়ে পরিবর্তনটি প্রয়োগ করুন এবং আরেকটি exec ব্যবহার করে নিশ্চিত করুন যে নতুন কনটেইনারে সেটি সত্যিই আছে।
FAQ
docker compose exec এবং docker compose run-এর মধ্যে পার্থক্য কী?
exec ইতিমধ্যে চলমান একটি container-এর ভেতরে মূল process-এর পাশাপাশি একটি command চালায় এবং image entrypoint এড়িয়ে যায়। run একই service definition থেকে একই image, environment, volumes এবং networks ব্যবহার করে একটি নতুন container তৈরি করে, আপনার command-টি entrypoint-এর মাধ্যমে চালায় এবং আগে যেকোনো depends_on service চালু করে। আপনি --service-ports যোগ না করলে run service-এর ports publish করে না। চলমান service পরীক্ষা করতে exec ব্যবহার করুন। service বন্ধ থাকলে বা সেটিকে বিঘ্নিত করতে না চাইলে run --rm ব্যবহার করুন।
docker compose exec কেন service running নয় বলে জানায়?
exec একটি বিদ্যমান container-এর সঙ্গে যুক্ত হয় এবং নতুন container তৈরি করতে পারে না। তাই বন্ধ বা crash করা service-এর ক্ষেত্রে service "web" is not running দেখা যায়। docker compose ps -a পরীক্ষা করুন। এটি Exited (1)-এর মতো status-সহ exited container তালিকাভুক্ত করে। service কেন বন্ধ হয়েছে, তা জানতে docker compose logs web পড়ুন। তবু shell খুলতে docker compose run --rm --entrypoint sh web চালান। এটি একই service definition থেকে একটি নতুন container তৈরি করে এবং ত্রুটিপূর্ণ start command চালতে দেয় না।
image-এ bash না থাকলে কীভাবে shell খুলব?
docker compose exec web bash চালিয়ে exec: "bash": executable file not found in $PATH দেখা গেলে বোঝায় যে image-এ bash নেই। Alpine-এর ওপর তৈরি image-এ এটি স্বাভাবিক। docker compose exec web sh ব্যবহার করুন, কারণ BusyBox-এ /bin/sh রয়েছে। Distroless এবং scratch image-এ কোনো shell-ই থাকে না। তাই কোনো exec command কাজ করবে না। publisher এমন tag দিলে image-এর :debug tag ব্যবহার করুন। অন্যথায় docker run --rm -it --network "container:$CID" --pid "container:$CID" nicolaka/netshoot দিয়ে target-এর namespaces-এ একটি debug container চালু করুন, যেখানে $CID আসে docker compose ps -q web থেকে।
cron-এ exec command চালালে "the input device is not a TTY" কেন দেখা যায়?
docker compose exec ডিফল্টভাবে একটি pseudo terminal চায়, কিন্তু cron কোনো pseudo terminal দেয় না। তাই আপনার command চালানোর আগেই অনুরোধ ব্যর্থ হয়। এটি বন্ধ করতে -T যোগ করুন: docker compose exec -T db pg_dump -U postgres app। Redirect বা pipe করা output-এর ক্ষেত্রেও -T ব্যবহার করুন। TTY byte stream পরিবর্তন করে এবং binary dump নষ্ট করতে পারে। cron-এ compose file-এর absolute path-সহ -f-ও দিন। না হলে Compose no configuration file provided: not found দিয়ে বন্ধ হয়ে যায়।
exec দিয়ে container-এর ভেতরে করা পরিবর্তন কি restart-এর পরও থাকে?
docker compose restart-এর পর পরিবর্তনগুলো থাকে, কারণ এতে একই container পুনরায় ব্যবহার করা হয়। কোনো image বা configuration পরিবর্তনের পর docker compose up -d-এ পরিবর্তনগুলো হারিয়ে যায়। কারণ এতে image থেকে container পুনরায় তৈরি হয় এবং তার writable layer বাদ পড়ে। named volumes বা bind mounts-এ লেখা data উভয় ক্ষেত্রেই থাকে, কারণ তা container-এর বাইরে সংরক্ষিত হয়। exec দিয়ে diagnostic পরিবর্তন করুন। এরপর স্থায়ী সংস্করণটি Dockerfile বা compose file-এ রাখুন।