SSD Nodes Learn Hosting plans →
تعلیمی Matt Connorتحریر: Matt Connor · اپ ڈیٹ شدہ 2026-08-07

Docker Compose healthcheck: درست readiness checks

جانیں Docker Compose healthchecks کیسے evaluate کرتا ہے، depends_on کیوں readiness کا انتظار نہیں کرتا، اور Postgres و app کے لیے درست checks کیسے لکھیں۔

Docker Compose کا healthcheck دراصل کیا کرتا ہے

Docker Compose کا healthcheck ایک command ہے جسے Docker مقررہ وقفے سے container کے اندر چلاتا ہے۔ Docker آپ کے logs نہیں پڑھتا، آپ کے port کو monitor نہیں کرتا، اور process list کا جائزہ نہیں لیتا۔ یہ command چلاتا ہے، exit code پڑھتا ہے، اور container میں صرف ایک state محفوظ کرتا ہے: starting، healthy، یا unhealthy۔ Exit code 0 کا مطلب healthy ہے۔ کوئی بھی دوسرا exit code unhealthy کا مطلب رکھتا ہے، جبکہ exit code 2 Docker کے لیے reserved ہے، اس لیے اسے جان بوجھ کر کبھی return نہ کریں۔

یہی مکمل mechanism ہے۔ تقریباً ہر healthcheck کا مسئلہ ایک ہی نوعیت کا ہوتا ہے: آپ کی لکھی ہوئی command اس سوال کا جواب دیتی ہے جو آپ پوچھنا نہیں چاہتے تھے۔ یہ guide فرض کرتی ہے کہ آپ پہلے ہی VPS پر compose file لکھنے کا طریقہ جانتے ہیں، اور وہاں سے آگے بڑھتی ہے جہاں stack غلط ترتیب سے start ہوتا ہے۔

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 value کی دو مفید صورتیں ہیں۔ CMD سے شروع ہونے والی list command کو براہِ راست چلاتی ہے، shell کے بغیر، اس لیے pipes، && اور variable expansion کام نہیں کرتے۔ CMD-SHELL سے شروع ہونے والی list باقی حصے کو ایک string کے طور پر container کے اندر /bin/sh -c کو دیتی ہے۔ جب check میں shell syntax درکار ہو تو یہی طریقہ استعمال کریں۔ Plain string کو CMD-SHELL سمجھا جاتا ہے۔ صرف ["NONE"] پر مشتمل list اس healthcheck کو ہٹا دیتی ہے جسے image نے اپنی Dockerfile کے ذریعے شامل کیا ہو۔

Check container کے اندر چلتا ہے، اس لیے اس میں درج ہر binary اسی image میں موجود ہونی چاہیے۔ پہلے اس کی تصدیق کریں، کیونکہ curl کے بغیر slim image ایسا container بناتی ہے جو مستقل unhealthy رہتا ہے، اور اس کی وجہ application log میں کبھی ظاہر نہیں ہوتی۔ اسے ہاتھ سے test کریں:

docker compose exec api curl --version

Missing binary کا جواب OCI runtime exec failed: exec: "curl": executable file not found in $PATH: unknown ہوتا ہے۔ Alpine پر مبنی images عموماً BusyBox کا wget فراہم کرتی ہیں، اس لیے check یوں بن جاتا ہے: ["CMD", "wget", "-q", "-O", "-", "http://localhost:8080/healthz"]۔

interval، retries اور start_period ایک ساتھ کیسے کام کرتے ہیں

پانچ settings وقت کا تعین کرتی ہیں۔ ان کی default values Compose سے نہیں بلکہ Docker Engine سے آتی ہیں۔

  • interval: container کے start period سے گزرنے کے بعد دو checks کے درمیان وقفہ۔ Default 30s ہے۔
  • timeout: check کے ایک run کے لیے زیادہ سے زیادہ وقت۔ یہ وقت گزرنے پر Docker اسے ختم کر کے اس run کو failure شمار کرتا ہے۔ Default 30s ہے۔
  • retries: مسلسل failures کی وہ تعداد جس کے بعد state تبدیل ہو کر unhealthy ہو جاتی ہے۔ Default 3 ہے۔
  • start_period: container start ہونے کے بعد رعایتی مدت۔ Default 0s ہے۔
  • start_interval: start period کے دوران check چلنے کا وقفہ۔ Default 5s ہے، اور اس کے لیے Docker Engine 25.0 یا اس کے بعد کا version درکار ہے۔

اہم اصول یہ ہے: start period کے دوران failing check کو retries میں شمار نہیں کیا جاتا، اور container starting میں رہتا ہے۔ Check پہلی بار کامیاب ہوتے ہی container healthy بن جاتا ہے اور start period فوراً ختم ہو جاتا ہے، چاہے اس کا زیادہ تر وقت باقی ہو۔ اگر start period اس وقت ختم ہو جائے جب check اب بھی failing ہو، تو معمول کا countdown شروع ہوتا ہے، اور container کو مسلسل retries failures کے بعد unhealthy mark کیا جاتا ہے۔

لہٰذا container start ہونے سے unhealthy تک زیادہ سے زیادہ وقت start_period جمع retries کو interval سے ضرب دینے کے برابر، پھر اس میں timeout جمع کرنے کے برابر ہے۔ اوپر دی گئی file کی values کے ساتھ یہ 30 جمع 5 ضرب 13، یعنی 95 seconds ہے۔ Deploy timeout مقرر کرنے سے پہلے یہ number لکھ لیں، کیونکہ 60 seconds بعد give up کرنے والا rollout اس container کو final state تک پہنچتے ہوئے کبھی نہیں دیکھے گا۔

یہاں عام غلطی slow start کو accommodate کرنے کے لیے retries بڑھانا ہے۔ یہ ایک بار مسئلہ حل کرتا ہے، لیکن بعد میں مستقل نقصان پہنچاتا ہے: boot ہونے کے لیے 8 retries درکار رکھنے والی service production میں کسی کے notice کرنے سے پہلے 8 مسلسل failures برداشت کرے گی۔ اس کے بجائے start_period استعمال کریں، کیونکہ یہ صرف پہلی کامیابی سے پہلے لاگو ہوتا ہے۔

اپنے طور پر depends_on کوئی ضمانت فراہم نہیں کرتا

depends_on کی مختصر شکل زیادہ تر الجھن کی وجہ ہے۔

  api:
    depends_on:
      - db

اس کا صرف ایک مطلب ہے: api container سے پہلے db container شروع کریں۔ Compose، container کے create اور start ہونے تک انتظار کرتا ہے۔ یہ PostgreSQL کی پہلی بار ہونے والی initialization مکمل ہونے تک انتظار نہیں کرتا، اور نہ ہی port 5432 پر connection قبول ہونے تک انتظار کرتا ہے۔ آپ کی app تقریباً ایک سیکنڈ بعد شروع ہوتی ہے، ایسے port سے connect کرنے کی کوشش کرتی ہے جہاں ابھی کوئی چیز 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 کی تین values ہیں۔ service_started مختصر شکل کے برابر ہے۔ service_healthy dependent service کو اس وقت تک روکے رکھتا ہے جب تک dependency healthy رپورٹ نہ کرے۔ یہ صرف اس وقت بامعنی ہوتا ہے جب dependency میں healthcheck موجود ہو، خواہ وہ compose file میں تعریف کیا گیا ہو یا اس کی image میں۔ service_completed_successfully one shot container، مثلاً database migration، کے status 0 کے ساتھ exit کرنے تک انتظار کرتا ہے۔

condition کے ساتھ دو اضافی fields موجود ہیں۔ restart: true Compose کو بتاتا ہے کہ dependency service update ہونے کے بعد اس service کو restart کرنا ہے۔ required: false missing dependency کو error کے بجائے warning بنا دیتا ہے۔

اب اس حد کو سمجھیں جو اکثر لوگوں کو متاثر کرتی ہے۔ ان conditions کا جائزہ stack کے start ہونے کے وقت لیا جاتا ہے۔ یہ start ordering ہے، supervision rule نہیں۔ اگر database صبح تین بجے restart ہو جائے تو service_healthy کا دوبارہ جائزہ نہیں لیا جاتا، اور نہ ہی اسے دوبارہ پورا کرنے کے لیے آپ کی app restart کی جاتی ہے۔ آپ کے application code کو خود reconnect کرنا ہوگا۔ docker compose up --no-deps api جان بوجھ کر پورے mechanism کو bypass کرتا ہے، اور container کو براہ راست docker start کے ساتھ start کرنے پر بھی یہی ہوتا ہے۔

ایسی check لکھیں جو readiness کی جانچ کرے، صرف process کے موجود ہونے کی نہیں

pgrep nginx جیسی check صرف یہ ثابت کرتی ہے کہ process table میں ایک entry موجود ہے۔ اس سے یہ ثابت نہیں ہوتا کہ service کسی request کا جواب دے سکتی ہے۔ کوئی web application database pool ختم ہو جانے کے کافی دیر بعد تک listening socket کھلا رکھ سکتی ہے، اور پورے outage کے دوران process check green رہ سکتی ہے۔

Container سے وہ کام کروائیں جس کے لیے وہ موجود ہے:

  • HTTP service کے لیے حقیقی endpoint کو request کریں۔ curl -fsS، -f کی وجہ سے 400 یا اس سے زیادہ کے کسی بھی status پر non zero exit کرتی ہے، اس لیے خراب application کی طرف سے آنے والا 500 failed check شمار ہوتا ہے۔
  • PostgreSQL کے لیے pg_isready استعمال کریں۔ جب server connections قبول کر رہا ہو تو یہ 0، connections مسترد کر رہا ہو تو 1، بالکل جواب نہ دے رہا ہو تو 2، اور فراہم کیے گئے parameters غلط ہوں تو 3 exit code دیتی ہے۔
  • Redis کے لیے redis-cli ping استعمال کریں۔ یہ PONG print کرتی ہے اور 0 exit code دیتی ہے۔
  • MariaDB کی official image میں healthcheck.sh script شامل ہوتی ہے، اور healthcheck.sh --connect --innodb_initialized وہ form ہے جسے اس کے maintainers document کرتے ہیں۔

pg_isready میں ایک اہم مسئلہ ہے۔ خالی data directory کے ساتھ پہلی start کے دوران official postgres image اپنی initialization ایک temporary server کے خلاف چلاتی ہے، جو صرف Unix socket پر listening کرتا ہے۔ pg_isready کو host argument کے بغیر چلانے پر یہی socket استعمال ہوتی ہے۔ اس لیے یہ اس وقت "accepting connections" کا جواب دے سکتی ہے جب TCP port 5432 ابھی آپ کی application کے لیے بند ہو۔ 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 signs کوئی typo نہیں ہیں۔ Compose file پڑھتے وقت خود $VAR کو expand کرتا ہے، جس سے آپ کے host environment کی value check میں شامل ہو جاتی۔ $$ اسے ایک single $ تک escape کرتا ہے، تاکہ container کے اندر shell اسے container کے اپنے environment کے مطابق expand کرے۔

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:

اسے شروع کریں اور states میں ہونے والی تبدیلی دیکھیں:

docker compose up -d
docker compose ps

STATUS کالم brackets میں health state دکھاتا ہے۔ صحت مند pair میں دونوں rows پر Up 41 seconds (healthy) دکھائی دیتا ہے۔ جب database ابھی initialising ہو رہا ہو تو db، Up 4 seconds (health: starting) دکھاتا ہے اور api فہرست میں موجود نہیں ہوتا، کیونکہ Compose نے اسے ابھی create نہیں کیا ہوتا۔

یہ دیکھنے کے لیے کہ check کامیاب یا ناکام کیوں ہوا، health log پڑھیں:

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

Docker آخری چند results محفوظ رکھتا ہے۔ ہر result میں start time، end time، ایک ExitCode اور command کا Output شامل ہوتا ہے۔ محفوظ output truncate ہو جاتا ہے، اس لیے جو check بڑا page body print کرے، اس کی log entry بے فائدہ ہوتی ہے۔ Checks کو خاموش رکھیں۔

جب کوئی container unhealthy ہو جائے تو Docker کیا کرتا ہے

کچھ نہیں۔ یہی وہ جواب ہے جو لوگوں کو سب سے زیادہ حیران کرتا ہے۔

ایک ہی host پر Docker Engine، unhealthy container کو restart نہیں کرتا۔ restart: unless-stopped policy اس وقت کارروائی کرتی ہے جب main process exit ہو جائے، جبکہ unhealthy container exit نہیں ہوا ہوتا۔ وہ ایک ہفتے تک unhealthy پر موجود رہ سکتا ہے اور Compose اسے نظرانداز کرتا رہتا ہے۔ Swarm mode unhealthy tasks کو replace کرتا ہے، لیکن ایک server پر چلنے والا سادہ Compose stack ایسا نہیں کرتا۔

اس صورت میں دو واضح راستے ہیں۔ جب process کو معلوم ہو کہ وہ خراب ہو چکا ہے تو اسے exit کرا دیں، تاکہ restart policy کارروائی کر سکے۔ یا بیرونی طور پر اس state کی نگرانی کریں اور اس پر alert جاری کریں۔ اسی endpoint پر Uptime Kuma monitor لگانے سے جسے آپ کا healthcheck call کرتا ہے، خراب dependency دونوں جگہ ظاہر ہو جاتی ہے، اور آپ کو اس کا علم user کے بجائے monitor سے ہو جاتا ہے۔ اگر traffic Traefik reverse proxy کے ذریعے app تک پہنچتا ہے تو یاد رکھیں کہ backend کے بارے میں proxy کا اپنا view، Docker health state سے الگ ہوتا ہے۔ اس لیے ایک چیز دوسری کا متبادل نہیں ہے۔

ایسے check کی debugging جو کبھی healthy نہیں ہوتا

خود اسی container میں وہی exact command چلائیں اور exit code دیکھیں:

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

اگر container ابھی بھی unhealthy رپورٹ کر رہا ہو اور یہاں exit=0 نظر آئے، تو آپ کی compose test ابھی ٹائپ کیے گئے command سے مختلف ہے۔ عموماً اس کی وجہ یہ ہوتی ہے کہ shell syntax درکار ہونے کے باوجود CMD استعمال کیا گیا۔

باقی زیادہ تر مسائل کی وجہ دو غلطیاں ہوتی ہیں۔ پہلی غلطی port کی ہوتی ہے۔ healthcheck container کے اندر چلتا ہے، اس لیے اس میں container port استعمال کرنا ضروری ہے، published host port نہیں۔ ports: - "8080:3000" کے ساتھ application، 3000 پر listen کرتی ہے۔ اگر check http://localhost:8080 کے خلاف ہو تو یہ ہمیشہ fail ہوگا، حالانکہ site browser میں درست کام کرتی رہے گی۔ دوسری غلطی host کی ہوتی ہے۔ check کے اندر localhost اسی container کو ظاہر کرتا ہے۔ اپنے container کو check کرنے کے لیے یہ درست ہے، لیکن دوسرے container کو check کرنے کے لیے غلط ہے۔ دوسرے container کے لیے service name استعمال کریں، مثلاً db۔

ایک آخری صورت بھی قابلِ ذکر ہے: healthcheck پاس ہو جاتا ہے، لیکن users کو errors دکھائی دیتے ہیں۔ ایسا اس وقت ہوتا ہے جب endpoint کسی حقیقی عمل کے بغیر static 200 واپس کرتا ہے۔ ایسا readiness endpoint جو database سے query نہ کرے، یہ نہیں بتا سکتا کہ database دستیاب نہیں رہا۔ اسے ایک سستی مگر حقیقی query چلانے کے لیے configure کریں۔

FAQ

میری ایپ اس وقت بھی connect کیوں نہیں کر پاتی جب depends_on کہتا ہے کہ database healthy ہے؟

کیونکہ condition: service_healthy کی evaluation stack شروع ہوتے وقت صرف ایک بار ہوتی ہے۔ اس کے بعد یہ کسی چیز کی نگرانی نہیں کرتا۔ اگر database container بعد میں restart ہو جائے تو Compose condition دوبارہ پوری کرنے کے لیے آپ کی application restart نہیں کرتا۔ اس لیے application code میں reconnect اور retry logic خود شامل کریں۔ جب آپ docker start یا docker compose up --no-deps کے ساتھ ایک single container شروع کرتے ہیں تو یہ condition کوئی اثر نہیں ڈالتی۔

اگر image پہلے ہی healthcheck define کرتی ہے تو کیا مجھے اپنی healthcheck درکار ہے؟

عموماً نہیں۔ اسے override کرنا اکثر پچھلا قدم ہوتا ہے، کیونکہ image maintainer جانتا ہے کہ اس software کے لیے readiness کا مطلب کیا ہے۔ اپنی healthcheck صرف اس وقت شامل کریں جب image کی check آپ کے setup کے لیے درست نہ ہو، مثلاً وہ ایسے port کو probe کرتی ہو جسے آپ نے تبدیل کر دیا ہے۔ کسی image کی healthcheck بند کرنے کے لیے service پر test: ["NONE"] یا disable: true set کریں۔

کیا healthcheck میں curl یا wget استعمال کرنا چاہیے؟

جو tool image میں پہلے سے موجود ہو، وہ استعمال کریں۔ اس پر انحصار کرنے سے پہلے docker compose exec <service> curl --version سے اس کی تصدیق کریں۔ Debian based بہت سی images میں دونوں میں سے کوئی بھی موجود نہیں ہوتا۔ Alpine based images میں BusyBox wget موجود ہوتا ہے۔ صرف healthcheck چلانے کے لیے image میں package شامل نہ کریں، جب software اپنا client فراہم کرتا ہو، جیسے pg_isready یا redis-cli۔

کیا unhealthy container خودکار طور پر restart ہو جاتا ہے؟

Single host پر Docker Engine ایسا نہیں کرتا۔ Restart policies process کے exit ہونے پر ردعمل دیتی ہیں، health state پر نہیں۔ اس لیے unhealthy container چلتا رہتا ہے اور خراب حالت میں رہتا ہے، جب تک کوئی دوسرا نظام اس پر کارروائی نہ کرے۔ یا تو failure معلوم ہونے پر process کو exit کرائیں، یا ایسا external monitor چلائیں جو اس state پر alert دے۔

start_period کتنا طویل ہونا چاہیے؟

اتنا طویل کہ آپ کے مشاہدے کے مطابق سب سے سست جائز first start مکمل ہو سکے، اور اس کے علاوہ کچھ اضافی گنجائش بھی ہو۔ Empty volume کے ساتھ docker compose up استعمال کر کے وقت ناپیں، کیونکہ database کا first start اس کے بعد ہونے والے ہر start کے مقابلے میں بہت سست ہوتا ہے۔ بہت طویل start period صرف پہلے unhealthy verdict میں تاخیر کرتا ہے۔ بہت زیادہ retries پوری container life کے دوران check کو کم مؤثر بنا دیتی ہیں، اور یہی زیادہ خراب failure ہے۔

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