SSD Nodes Learn Hosting plans →
নির্দেশিকা Matt Connorদ্বারা Matt Connor · আপডেট করা হয়েছে 2026-10-23

Docker Compose healthcheck কাজ করানোর সঠিক উপায়

Docker Compose healthcheck কীভাবে exit code মূল্যায়ন করে, কেন depends_on readiness নিশ্চিত করে না, এবং Postgres ও app-এর জন্য নির্ভরযোগ্য check লেখার নিয়ম জানুন।

Docker Compose healthcheck আসলে যা করে

Docker Compose healthcheck হলো একটি কমান্ড, যা Docker নির্দিষ্ট বিরতিতে container-এর ভেতরে চালায়। Docker আপনার log পড়ে না, port monitor করে না এবং process list পরীক্ষা করে না। এটি কমান্ডটি চালায়, exit code পড়ে এবং container-এ একটি মাত্র state সংরক্ষণ করে: starting, healthy অথবা unhealthy। Exit code 0 হলে container healthy। অন্য যেকোনো exit code হলে container unhealthy। Docker exit code 2 নিজের জন্য সংরক্ষণ করে, তাই ইচ্ছাকৃতভাবে এটি ফেরত দেবেন না।

এটাই সম্পূর্ণ প্রক্রিয়া। প্রায় সব healthcheck সমস্যার মূল কারণ এক: আপনি যে কমান্ড লিখেছেন, সেটি আপনার জানতে চাওয়া প্রশ্নের বদলে অন্য একটি প্রশ্নের উত্তর দেয়। এই নির্দেশিকায় ধরে নেওয়া হয়েছে যে আপনি ইতিমধ্যে VPS-এ compose file কীভাবে লিখতে হয় জানেন। এখানে stack ভুল ক্রমে শুরু হওয়ার পরের ধাপ থেকে আলোচনা করা হয়েছে।

services:
  api:
    image: ghcr.io/example/api:1.4.0
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost:8080/healthz"]
      interval: 10s
      timeout: 3s
      retries: 5
      start_period: 30s

test মানটি দুটি উপযোগী রূপে ব্যবহৃত হয়। CMD দিয়ে শুরু হওয়া list কমান্ডটি সরাসরি চালায়, কোনো shell ছাড়াই। তাই pipe, && এবং variable expansion কাজ করে না। CMD-SHELL দিয়ে শুরু হওয়া list-এর বাকি অংশ একটি string হিসেবে container-এর ভেতরে /bin/sh -c-এ পাঠানো হয়। Check-এর জন্য shell syntax প্রয়োজন হলে এটিই ব্যবহার করবেন। সাধারণ string-কে CMD-SHELL হিসেবে বিবেচনা করা হয়। ঠিক ["NONE"] উপাদানযুক্ত একটি list image-এর Dockerfile-এর মাধ্যমে যোগ করা healthcheck সরিয়ে দেয়।

Check-টি container-এর ভেতরে চলে। তাই এতে উল্লেখ করা প্রতিটি binary-কে ওই image-এ থাকতে হবে। আগে এটি যাচাই করুন। কারণ curl ছাড়া slim image এমন একটি container তৈরি করে, যা স্থায়ীভাবে unhealthy থাকে, অথচ এর কারণ application log-এ কখনো দেখা যায় না। হাতে পরীক্ষা করুন:

docker compose exec api curl --version

কোনো binary না থাকলে OCI runtime exec failed: exec: "curl": executable file not found in $PATH: unknown ফেরত আসে। Alpine ভিত্তিক image-গুলোতে সাধারণত BusyBox-এর wget থাকে। তাই check-টি হবে ["CMD", "wget", "-q", "-O", "-", "http://localhost:8080/healthz"]।

interval, retries এবং start_period কীভাবে একসঙ্গে কাজ করে

পাঁচটি সেটিংস সময় নির্ধারণ করে। এগুলোর ডিফল্ট Docker Engine থেকে আসে, Compose থেকে নয়।

  • interval: কনটেইনারের start period শেষ হওয়ার পর দুটি চেকের মধ্যবর্তী সময়। ডিফল্ট 30s।
  • timeout: একটি চেক চালাতে সর্বোচ্চ যত সময় লাগতে পারে, তার পর Docker সেটি বন্ধ করে ওই রানকে ব্যর্থতা হিসেবে গণনা করে। ডিফল্ট 30s।
  • retries: unhealthy অবস্থায় পরিবর্তনের আগে পরপর যতবার ব্যর্থতা প্রয়োজন। ডিফল্ট 3।
  • start_period: কনটেইনার চালু হওয়ার পরের একটি grace window। ডিফল্ট 0s।
  • start_interval: start period চলাকালে চেকটি কত ঘন ঘন চলবে। ডিফল্ট 5s, এবং এর জন্য Docker Engine 25.0 বা পরবর্তী সংস্করণ প্রয়োজন।

গুরুত্বপূর্ণ নিয়মটি হলো: start period চলাকালে ব্যর্থ চেক retries-এর হিসাবে গণনা হয় না, এবং কনটেইনার starting অবস্থায় থাকে। চেক প্রথমবার সফল হলে কনটেইনার healthy অবস্থায় যায় এবং start period সঙ্গে সঙ্গে শেষ হয়, এর নির্ধারিত সময়ের বেশির ভাগ অব্যবহৃত থাকলেও। চেক ব্যর্থ থাকা অবস্থায় start period শেষ হয়ে গেলে স্বাভাবিক গণনা শুরু হয়, এবং কনটেইনারকে unhealthy হিসেবে চিহ্নিত করার আগে পরপর retries বার ব্যর্থতা প্রয়োজন।

তাই কনটেইনার চালু হওয়া থেকে unhealthy পর্যন্ত সর্বোচ্চ সময় হলো start_period, তার সঙ্গে retries-কে interval দিয়ে গুণ করে timeout যোগ করতে হবে। উপরের ফাইলের মান অনুযায়ী এটি 30 plus 5 times 13, অর্থাৎ 95 seconds। deploy timeout নির্ধারণের আগে এই সংখ্যা লিখে রাখুন, কারণ 60 seconds পর যে rollout বন্ধ হয়ে যায়, সেটি এই কনটেইনারের final state-এ পৌঁছানো কখনো দেখতে পাবে না।

এখানে সাধারণ ভুল হলো ধীর start সামলাতে retries-এর মান বাড়ানো। এটি একবার সমস্যার সমাধান করে, কিন্তু পরে স্থায়ী ক্ষতি করে: boot হতে 8টি retry প্রয়োজন হওয়া একটি service এখন production-এ পরপর 8টি ব্যর্থতা সহ্য করবে, তার আগে কেউ বিষয়টি বুঝতে পারবে না। এর পরিবর্তে start_period ব্যবহার করুন, কারণ এটি কেবল প্রথম সফলতার আগের সময়ে প্রযোজ্য।

কেন depends_on একা কোনো নিশ্চয়তা দেয় না

depends_on-এর সংক্ষিপ্ত রূপই অধিকাংশ বিভ্রান্তির মূল কারণ।

  api:
    depends_on:
      - db

এর অর্থ একটিই: db container-টি api container-টির আগে চালু করতে হবে। Compose container তৈরি ও চালু হওয়া পর্যন্ত অপেক্ষা করে। PostgreSQL-এর প্রথমবারের initialization শেষ হওয়া পর্যন্ত এটি অপেক্ষা করে না। Port 5432 connection গ্রহণ করার উপযোগী হওয়া পর্যন্তও অপেক্ষা করে না। আপনার app প্রায় এক সেকেন্ড পরে চালু হয়, এমন একটি port-এ connection করার চেষ্টা করে যেখানে তখনও কোনো service listen করছে না, এবং বন্ধ হয়ে যায়। Log-এ আপনি Connection refused দেখতে পারেন। Server চালু থাকলেও recovery চলতে থাকলে FATAL: the database system is starting up দেখতে পারেন।

দীর্ঘ রূপটিই সাধারণত প্রয়োজন:

  api:
    depends_on:
      db:
        condition: service_healthy
        restart: true
      migrate:
        condition: service_completed_successfully

condition-এর তিনটি মান রয়েছে। service_started সংক্ষিপ্ত রূপের মতোই কাজ করে। service_healthy dependency healthy হওয়ার খবর না দেওয়া পর্যন্ত নির্ভরশীল service-কে আটকে রাখে। এটি তখনই অর্থবহ, যখন dependency-তে healthcheck সংজ্ঞায়িত থাকে, compose file-এ অথবা তার image-এ। service_completed_successfully one shot container-এর জন্য অপেক্ষা করে, যেমন database migration, যাতে সেটি status 0 দিয়ে বন্ধ হয়।

condition-এর পাশে আরও দুটি field থাকে। restart: true dependency service update করার পরে এই service-টি restart করতে Compose-কে নির্দেশ দেয়। required: false অনুপস্থিত dependency-কে error-এর বদলে warning হিসেবে গণ্য করে।

এখন সেই সীমাবদ্ধতা, যা অনেককে সমস্যায় ফেলে। Stack চালু হওয়ার সময় এই condition-গুলো মূল্যায়ন করা হয়। এগুলো start ordering, supervision rule নয়। Database ভোর 3টায় restart হলে service_healthy নতুন করে মূল্যায়ন করা হয় না এবং এটি আবার পূরণ করার জন্য app-ও restart করা হয় না। আপনার application code-কেই নিজে থেকে reconnect করতে হবে। docker compose up --no-deps api নকশা অনুযায়ী পুরো প্রক্রিয়াটি এড়িয়ে যায়। docker start দিয়ে সরাসরি container চালু করলেও একই ফল হয়।

এমন check লিখুন যা readiness পরীক্ষা করে, শুধু process আছে কি না তা নয়

pgrep nginx-এর মতো check প্রমাণ করে যে process table-এ একটি entry আছে। এটি service কোনো request-এর উত্তর দিতে পারে কি না, সে বিষয়ে কিছুই প্রমাণ করে না। কোনো web application তার database pool বন্ধ হয়ে যাওয়ার পরও listening socket খোলা রাখতে পারে, এবং পুরো outage চলাকালীন process check সবুজ থাকতে পারে।

Container-কে তার নির্ধারিত কাজটি করতে বলুন:

  • HTTP service-এর ক্ষেত্রে একটি বাস্তব endpoint-এ request পাঠান। -f-এর কারণে server 400 বা তার বেশি status-এ উত্তর দিলে curl -fsS code 22 নিয়ে exit করে। তাই ভেঙে যাওয়া app-এর 500-ও failed check হিসেবে ধরা পড়ে। curl-এর নিজস্ব documentation অনুযায়ী, -f fail-safe নয়, বিশেষ করে 401 এবং 407 authentication response-এর ক্ষেত্রে। তাই এমন endpoint-এ check চালান, যেখানে login প্রয়োজন নেই।
  • PostgreSQL-এর জন্য pg_isready ব্যবহার করুন। Server connection গ্রহণ করলে এটি 0, connection প্রত্যাখ্যান করলে 1, একেবারেই উত্তর না দিলে 2, এবং দেওয়া parameter ভুল হলে 3 exit code দেয়।
  • Redis-এর জন্য redis-cli ping ব্যবহার করুন। এটি PONG মুদ্রণ করে এবং 0 exit করে।
  • MariaDB-এর official image-এ healthcheck.sh script থাকে, এবং healthcheck.sh --connect --innodb_initialized হলো এর maintainers-দের document করা form।

pg_isready-এর ক্ষেত্রে একটি গুরুত্বপূর্ণ ফাঁদ আছে। খালি data directory নিয়ে প্রথমবার start হওয়ার সময় official postgres image একটি temporary server-এর বিরুদ্ধে initialisation চালায়। ওই server শুধু Unix socket-এ listen করে। কোনো host argument ছাড়া pg_isready ওই socket ব্যবহার করে। ফলে TCP port 5432 তখনও আপনার application-এর জন্য বন্ধ থাকলেও এটি “accepting connections” উত্তর দিতে পারে। Check-টি সরাসরি TCP-তে নির্দেশ করুন। তাহলে সমস্যা থাকবে না, কারণ temporary server সেখানে উত্তর দেয় না।

    healthcheck:
      test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -p 5432 -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 10
      start_period: 30s

দুটি dollar sign থাকা কোনো typo নয়। Compose file পড়ার সময় নিজেই $VAR expand করে। এতে আপনার host environment-এর একটি value check-এর মধ্যে স্থায়ীভাবে বসে যেতে পারে। $$ এটিকে একটি মাত্র $-এ escape করে। ফলে container-এর ভেতরের shell container-এর নিজস্ব environment অনুযায়ী এটিকে expand করে।

সঠিক ক্রমে start হওয়া postgres এবং app stack

services:
  db:
    image: postgres:17.5
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: ${DB_PASSWORD:?set DB_PASSWORD in .env}
      POSTGRES_DB: appdb
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -p 5432 -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 10
      start_period: 30s
    restart: unless-stopped

  api:
    image: ghcr.io/example/api:1.4.0
    environment:
      DATABASE_URL: postgres://appuser:${DB_PASSWORD}@db:5432/appdb
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost:8080/healthz"]
      interval: 10s
      timeout: 3s
      retries: 5
      start_period: 30s
    ports:
      - "127.0.0.1:8080:8080"
    restart: unless-stopped

volumes:
  pgdata:

Stack চালু করুন এবং state পরিবর্তন দেখুন:

docker compose up -d
docker compose ps

STATUS column-এ বন্ধনীর মধ্যে health state থাকে। সুস্থ জোড়ার উভয় row-তেই Up 41 seconds (healthy) দেখা যায়। Database এখনও initialising অবস্থায় থাকলে db-এ Up 4 seconds (health: starting) দেখা যায় এবং তালিকায় api থাকে না। Compose ইতিমধ্যে api container তৈরি করেছে এবং শুধু সেটির start আটকে রেখেছে, যতক্ষণ না db healthy report করে। তালিকায় এটি নেই, কারণ docker compose ps defaultভাবে শুধু running container দেখায়। সেই সময় docker compose ps -a চালালে api created state-এ দেখা যায়।

কোনো check কেন pass বা fail করেছে তা দেখতে health log পড়ুন:

docker inspect --format '{{json .State.Health}}' "$(docker compose ps -q db)"

Docker শেষ কয়েকটি result সংরক্ষণ করে। প্রতিটিতে start time, end time, একটি ExitCode এবং command-এর Output থাকে। সংরক্ষিত output-এর দৈর্ঘ্য সীমিত। তাই বড় page body মুদ্রণ করা check থেকে log entry কোনো কাজে আসে না। Check সংক্ষিপ্ত রাখুন।

একটি container unhealthy হলে Docker যা করে

কিছুই নয়। এই উত্তরটিই অধিকাংশ মানুষকে সবচেয়ে বেশি অবাক করে।

একটি host-এ চলমান Docker Engine কোনো unhealthy container পুনরায় চালু করে না। restart: unless-stopped policy মূল process বন্ধ হয়ে গেলে প্রতিক্রিয়া জানায়, কিন্তু একটি unhealthy container বন্ধ হয়নি। Compose সেটিকে উপেক্ষা করে রাখলে এটি এক সপ্তাহ unhealthy অবস্থায় থাকতে পারে। Swarm mode unhealthy task প্রতিস্থাপন করে, কিন্তু একটি server-এ চলমান সাধারণ Compose stack তা করে না।

এতে দুটি বাস্তবসম্মত বিকল্প থাকে। Process ত্রুটিপূর্ণ অবস্থায় পৌঁছেছে বুঝতে পারলে সেটিকে বন্ধ করে দিন, যাতে restart policy কাজ করার মতো পরিস্থিতি পায়। অথবা বাইরের কোনো ব্যবস্থা থেকে state পর্যবেক্ষণ করুন এবং এটির জন্য alert তৈরি করুন। আপনার healthcheck যে endpoint-এ কল করে, সেই একই endpoint-এ একটি Uptime Kuma monitor নির্দেশ করলে ত্রুটিপূর্ণ dependency উভয় স্থানেই দেখা যাবে। তখন কোনো user-এর কাছ থেকে জানার পরিবর্তে monitor থেকেই বিষয়টি জানতে পারবেন। যদি একটি Traefik reverse proxy-এর মাধ্যমে traffic app-এ পৌঁছায়, মনে রাখবেন যে backend সম্পর্কে proxy-এর নিজস্ব দৃষ্টিভঙ্গি Docker health state থেকে আলাদা। তাই একটি ব্যবস্থা অন্যটিকে প্রতিস্থাপন করে না।

কখনও সুস্থ না হওয়া চেক ডিবাগ করা

নিজে, একই container-এ, হুবহু command চালান এবং exit code দেখুন:

docker compose exec api curl -fsS http://localhost:8080/healthz; echo "exit=$?"

container এখনও unhealthy দেখালেও এখানে exit=0 থাকলে, আপনার compose test এইমাত্র টাইপ করা কমান্ডের থেকে আলাদা। সাধারণত shell syntax-এর জায়গায় CMD ব্যবহার করার কারণে এটি হয়।

বাকি বেশিরভাগ সমস্যার জন্য আরও দুটি ভুল দায়ী। প্রথমটি হলো ভুল port। healthcheck container-এর ভিতরে চলে। তাই এতে অবশ্যই container port ব্যবহার করতে হবে, প্রকাশিত host port নয়। ports: - "8080:3000"-এ application 3000 port-এ শোনে। তাই http://localhost:8080-এর বিরুদ্ধে চেকটি চিরকাল ব্যর্থ হবে, যদিও browser-এ site ঠিকমতো কাজ করে। দ্বিতীয়টি হলো ভুল host। চেকের ভিতরে localhost বলতে একই container বোঝায়। নিজের container পরীক্ষা করার জন্য এটি সঠিক। কিন্তু অন্য container পরীক্ষা করতে এটি ভুল। সে ক্ষেত্রে service name ব্যবহার করতে হবে, যেমন db।

আরেকটি পরিস্থিতির আলাদা করে উল্লেখ করা দরকার: healthcheck পাস করে, কিন্তু ব্যবহারকারীরা error দেখেন। endpoint কোনো বাস্তব উপাদান পরীক্ষা না করে static 200 ফেরত দিলে এমন হয়। যে readiness endpoint কখনও database-এ query চালায় না, সেটি database অচল হয়েছে কি না জানাতে পারে না। এটিকে একটি ছোট, বাস্তব query চালানোর ব্যবস্থা করুন।

FAQ

depends_on-এ ডেটাবেস healthy বলা থাকলেও আমার অ্যাপ সংযোগ করতে ব্যর্থ হয় কেন?

কারণ condition: service_healthy স্ট্যাক শুরু হওয়ার সময় একবার মূল্যায়ন করা হয়। এরপর এটি আর কোনো কিছু তদারকি করে না। পরে ডেটাবেস কনটেইনার পুনরায় চালু হলে, শর্তটি আবার পূরণ করার জন্য Compose আপনার অ্যাপ্লিকেশন পুনরায় চালু করে না। তাই আপনার অ্যাপ্লিকেশন কোডে নিজস্ব reconnect এবং retry logic থাকতে হবে। আপনি docker start দিয়ে বা docker compose up --no-deps দিয়ে একটি মাত্র কনটেইনার চালু করলেও এই শর্ত কোনো কাজ করে না।

ইমেজে আগে থেকেই healthcheck সংজ্ঞায়িত থাকলে কি আমার healthcheck প্রয়োজন?

সাধারণত প্রয়োজন হয় না। এটি override করাও প্রায়ই উল্টো পদক্ষেপ, কারণ ওই সফটওয়্যারের জন্য readiness-এর অর্থ ইমেজের maintainer-ই সবচেয়ে ভালো জানেন। আপনার সেটআপের জন্য ইমেজের check ভুল হলে তবেই নিজস্ব check যোগ করুন। যেমন, এটি এমন একটি port পরীক্ষা করলে যা আপনি পরিবর্তন করেছেন। ইমেজের healthcheck বন্ধ করতে service-এ test: ["NONE"] বা disable: true সেট করুন।

healthcheck-এ curl ব্যবহার করা উচিত, নাকি wget?

ইমেজে আগে থেকেই যেটি আছে, সেটি ব্যবহার করুন। নির্ভর করার আগে docker compose exec <service> curl --version দিয়ে তা নিশ্চিত করুন। Debian ভিত্তিক অনেক ইমেজে কোনোটিই থাকে না। Alpine ভিত্তিক ইমেজে BusyBox wget থাকে। শুধু healthcheck চালানোর জন্য কোনো ইমেজে package যোগ করবেন না, যখন সফটওয়্যার নিজস্ব client সরবরাহ করে, যেমন pg_isready বা redis-cli।

unhealthy কনটেইনার কি স্বয়ংক্রিয়ভাবে পুনরায় চালু হয়?

একটি host-এ Docker Engine নিজে থেকে তা করে না। Restart policy process বন্ধ হওয়ার প্রতিক্রিয়া দেখায়, health state-এর নয়। তাই অন্য কোনো ব্যবস্থা না নেওয়া পর্যন্ত unhealthy কনটেইনার চালু এবং অচল অবস্থায় থাকে। ব্যর্থতা শনাক্ত হলে process-টি নিজে থেকে বন্ধ হওয়ার ব্যবস্থা করুন। অথবা এমন একটি external monitor চালান, যা state পরিবর্তন হলে alert দেয়।

start_period কত দীর্ঘ হওয়া উচিত?

আপনার মাপা ধীরতম বৈধ প্রথম start সম্পন্ন করার জন্য যতটা সময় প্রয়োজন, তার সঙ্গে একটি অতিরিক্ত margin যোগ করে নির্ধারণ করুন। docker compose up দিয়ে একটি empty volume-এর ক্ষেত্রে সময় মাপুন, কারণ ডেটাবেসের প্রথম start পরবর্তী প্রতিটি start-এর তুলনায় অনেক ধীর। অতিরিক্ত দীর্ঘ start period শুধু প্রথম unhealthy verdict বিলম্বিত করে। অতিরিক্ত বেশি retries পুরো container-এর জীবনকালজুড়ে check-কে দুর্বল করে, যা আরও গুরুতর ব্যর্থতা।

এই লেখাটি কি কাজে লেগেছে?

#docker-compose#healthcheck#depends-on#docker#reliability