SSD Nodes Learn 8GB RAM — $66/taon
Mga Gabay Matt ConnorNi Matt Connor · Na-update 2026-08-02

Docker Compose healthcheck na talagang gumagana

Alamin kung paano sinusuri ang healthcheck, bakit walang hinihintay na useful ang depends_on, at paano gumawa ng readiness check para sa Postgres at app.

Ano talaga ang ginagawa ng Docker Compose healthcheck

Ang Docker Compose healthcheck ay isang command na pinapatakbo ng Docker sa loob ng container ayon sa itinakdang pagitan ng oras. Hindi binabasa ng Docker ang iyong mga log, hindi nito mino-monitor ang iyong port, at hindi nito sinusuri ang listahan ng mga process. Pinapatakbo nito ang command, binabasa ang exit code, at nag-iimbak ng isang state sa container: starting, healthy, o unhealthy. Ang exit code na 0 ay nangangahulugang healthy. Anumang ibang exit code ay nangangahulugang unhealthy, at nakalaan sa Docker ang exit code na 2, kaya huwag itong ibalik nang sadya.

Iyan ang buong mekanismo. Halos lahat ng problema sa healthcheck ay iisang problema: ibang tanong ang sinasagot ng command na isinulat mo kaysa sa tanong na nais mong itanong. Ipinapalagay ng gabay na ito na alam mo na kung paano magsulat ng compose file sa isang VPS, at nagpapatuloy ito sa puntong nagsisimula ang stack sa maling pagkakasunod-sunod.

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

Ang value na test ay may dalawang kapaki-pakinabang na anyo. Ang listahang nagsisimula sa CMD ay direktang nagpapatakbo ng command, nang walang shell, kaya hindi gumagana ang mga pipe, &&, at variable expansion. Ang listahang nagsisimula sa CMD-SHELL ay ipinapasa ang natitirang bahagi bilang isang string sa /bin/sh -c sa loob ng container. Ito ang dapat gamitin kapag kailangan ng check ng shell syntax. Ang plain string ay itinuturing bilang CMD-SHELL. Ang listahang eksaktong may ["NONE"] ay nag-aalis ng healthcheck na isinama ng image sa Dockerfile nito.

Tumatakbo ang check sa loob ng container, kaya dapat umiiral sa image na iyon ang bawat binary na tinutukoy nito. I-verify muna ito, dahil ang slim image na walang curl ay lumilikha ng container na palaging unhealthy dahil sa isang problemang hindi kailanman lumilitaw sa application log. Subukan ito nang mano-mano:

docker compose exec api curl --version

Ang nawawalang binary ay nagbabalik ng OCI runtime exec failed: exec: "curl": executable file not found in $PATH: unknown. Karaniwang may kasamang BusyBox wget ang mga Alpine-based image, kaya nagiging ["CMD", "wget", "-q", "-O", "-", "http://localhost:8080/healthz"] ang check.

Paano pinagsasama ang interval, retries at start_period

Limang setting ang kumokontrol sa timing. Ang mga default ng mga ito ay mula sa Docker Engine, hindi sa Compose.

  • interval: pagitan ng dalawang check kapag lumampas na ang container sa start period nito. Default: 30s.
  • timeout: pinakamahabang oras na maaaring tumakbo ang isang check bago ito ihinto ng Docker at ituring na failure ang run na iyon. Default: 30s.
  • retries: bilang ng magkakasunod na failure na kailangan bago maging unhealthy ang state. Default: 3.
  • start_period: grace window pagkatapos magsimula ang container. Default: 0s.
  • start_interval: gaano kadalas tumatakbo ang check habang nasa start period. Default: 5s, at kailangan nito ang Docker Engine 25.0 o mas bago.

Ang mahalagang rule: habang nasa start period, ang failing check ay hindi ibinibilang sa retries, at nananatili ang container sa starting. Sa unang pagkakataong magtagumpay ang check, nagiging healthy ang container at agad na nagtatapos ang start period, kahit hindi pa nagagamit ang malaking bahagi ng oras nito. Kung matapos ang start period habang failing pa rin ang check, magsisimula ang normal na countdown, at kailangan ng container ng retries magkakasunod na failure bago ito mamarkahang unhealthy.

Kaya ang pinakamatagal na oras mula sa pagsisimula ng container hanggang sa unhealthy ay start_period plus retries na minultiply sa interval, plus timeout. Gamit ang mga value sa file sa itaas, iyon ay 30 plus 5 times 13, na katumbas ng 95 segundo. Isulat muna ang numerong ito bago magtakda ng deploy timeout, dahil hindi kailanman makikitang umabot sa final state ang container na ito kung susuko ang rollout pagkalipas ng 60 segundo.

Ang karaniwang pagkakamali rito ay taasan ang retries para masaklaw ang mabagal na pagsisimula. Gumagana iyon minsan, pero nagdudulot ng problema habang tumatagal: ang service na nangailangan ng 8 retries para mag-boot ay magtitiis na ngayon ng 8 magkakasunod na failure sa production bago ito mapansin. Sa halip, gamitin ang start_period, dahil nalalapat lamang ito bago ang unang tagumpay.

Bakit walang garantiyang ibinibigay ang depends_on kapag ito lang ang ginagamit

Ang maikling anyo ng depends_on ang pangunahing pinagmumulan ng kalituhan.

  api:
    depends_on:
      - db

Isang bagay lang ang ibig sabihin nito: simulan ang db container bago ang api container. Hinihintay ng Compose na malikha at masimulan ang container. Hindi nito hinihintay na matapos ng PostgreSQL ang unang initialization nito, at hindi rin nito hinihintay na tumanggap ng connection ang port 5432. Magsisimula ang app mo makalipas ang humigit-kumulang isang segundo, susubukang kumonekta sa port na wala pang nakikinig, at lalabas. Sa log, makikita mo ang Connection refused, o ang FATAL: the database system is starting up kapag umaandar na ang server pero nagre-recover pa.

Ang long form ang karaniwang talagang kailangan ng mga tao:

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

May tatlong value ang condition. Ang service_started ay kapareho ng short form. Pinipigilan ng service_healthy na magsimula ang dependent service hanggang sa maiulat ng dependency na healthy ito. Makabuluhan lamang ito kapag may tinukoy na healthcheck ang dependency, alinman sa compose file o sa image nito. Hinihintay ng service_completed_successfully na matapos ang one-shot container, gaya ng database migration, at mag-exit ito nang may status 0.

May dalawang karagdagang field sa tabi ng condition. Sinasabi ng restart: true sa Compose na i-restart ang service na ito kapag na-update nito ang dependency service. Ginagawang warning ng required: false ang nawawalang dependency sa halip na error.

Narito ang limitasyong madalas nakalilito. Sinusuri ang mga condition na ito kapag umaandar ang stack. Para ito sa pagkakasunod-sunod ng pagsisimula, hindi para sa supervision. Kung mag-restart ang database nang alas-3 ng umaga, hindi muling susuriin ang service_healthy at hindi ire-restart ang app mo para muling matugunan ito. Kailangan pa ring awtomatikong muling kumonekta ng application code mo. Sadyang nilalampasan ng docker compose up --no-deps api ang buong mekanismong ito, gayundin ang direktang pagsisimula ng container gamit ang docker start.

Sumulat ng check na sumusuri sa pagiging handa, hindi sa pagkakaroon ng process

Pinatutunayan ng check na tulad ng pgrep nginx na may entry ang isang process sa process table. Wala itong pinatutunayan tungkol sa kakayahan ng service na sumagot sa request. Maaaring panatilihing bukas ng isang web application ang listening socket nito kahit matagal nang hindi gumagana ang database pool nito, at mananatiling green ang process check sa buong outage.

Ipagawa sa container ang tungkuling dapat nitong gampanan:

  • Para sa HTTP service, mag-request ng aktuwal na endpoint. Nag-e-exit ang curl -fsS na may non-zero status para sa anumang status na 400 o mas mataas dahil sa -f, kaya failed check ang 500 mula sa sirang app.
  • Para sa PostgreSQL, gamitin ang pg_isready. Nag-e-exit ito sa 0 kapag tumatanggap ng connections ang server, sa 1 kapag nire-reject nito ang mga ito, sa 2 kapag hindi ito tumutugon, at sa 3 kapag mali ang mga parameter na ipinasa.
  • Para sa Redis, gamitin ang redis-cli ping. Nagpi-print ito ng PONG at nag-e-exit sa 0.
  • Para sa MariaDB, may kasamang healthcheck.sh script ang official image, at ang healthcheck.sh --connect --innodb_initialized ang form na idinodokumento ng mga maintainer nito.

May isang mahalagang dapat malaman tungkol sa pg_isready. Sa unang pagsisimula nito na walang laman ang data directory, pinapatakbo ng official postgres image ang initialization nito laban sa temporary server na nakikinig lamang sa Unix socket. Ginagamit ng pg_isready na walang host argument ang socket na iyon, kaya maaari itong sumagot ng “tumatanggap ng connections” habang sarado pa ang TCP port 5432 para sa application mo. Ituro ang check sa TCP nang tahasan at mawawala ang problema, dahil hindi sumasagot doon ang 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

Hindi typo ang dobleng dollar signs. Ine-expand mismo ng Compose ang $VAR habang binabasa nito ang file, kaya mailalagay sa check ang value mula sa environment ng host mo. Ine-escape ito ng $$ bilang isang $, kaya ine-expand ito ng shell sa loob ng container batay sa sarili nitong environment ng container.

Isang postgres at app stack na nagsisimula sa tamang pagkakasunod-sunod

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:

I-start ito at obserbahan ang pagbabago ng mga state:

docker compose up -d
docker compose ps

Ang column na STATUS ay naglalaman ng health state sa loob ng bracket. Ang healthy na pares ay may Up 41 seconds (healthy) sa parehong row. Habang nag-i-initialise pa ang database, ang db ay may Up 4 seconds (health: starting) at wala pa sa listahan ang api dahil hindi pa ito nalilikha ng Compose.

Para makita kung bakit nag-pass o nag-fail ang isang check, basahin ang health log:

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

Pinapanatili ng Docker ang huling ilang resulta. Bawat isa ay may start time, end time, isang ExitCode, at Output ng command. Truncated ang naka-store na output. Kaya magiging walang silbi ang log entry kapag nag-print ang check ng malaking page body. Panatilihing tahimik ang mga check.

Ano ang ginagawa ng Docker kapag naging unhealthy ang container

Wala. Ito ang sagot na pinakanakakagulat sa mga tao.

Hindi nire-restart ng Docker Engine sa iisang host ang isang unhealthy na container. Tumutugon ang restart: unless-stopped policy kapag lumalabas ang main process, at hindi lumabas ang unhealthy na container. Maaari itong manatili sa unhealthy nang isang linggo habang hindi ito ginagalaw ng Compose. Pinapalitan ng Swarm mode ang mga unhealthy task, ngunit hindi ito ginagawa ng isang plain Compose stack sa isang server.

Dalawa ang praktikal na opsyon. Ipa-exit ang process kapag alam nitong may sira ito, para may maaksiyunan ang restart policy. O subaybayan ang state mula sa labas at magpadala ng alert kapag nagbago ito. Kapag itinuro ang isang Uptime Kuma monitor sa parehong endpoint na tinatawag ng healthcheck, makikita ang sirang dependency sa parehong lugar, at malalaman mo ito mula sa monitor sa halip na mula sa user. Kung dumadaan ang traffic ng app sa isang Traefik reverse proxy, tandaan na hiwalay ang pagtingin ng proxy sa backend mula sa Docker health state. Kaya hindi napapalitan ng isa ang isa pa.

Pag-debug ng check na hindi kailanman nagiging healthy

Patakbuhin mismo ang eksaktong command sa parehong container, at tingnan ang exit code:

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

Ang exit=0 dito habang unhealthy pa rin ang ulat ng container ay nangangahulugang iba ang iyong compose test sa iyong bagong na-type. Karaniwan itong nangyayari kapag ginamit ang CMD sa halip na shell syntax.

Dalawang pagkakamali ang sanhi ng karamihan sa iba pang kaso. Ang una ay maling port. Tumatakbo ang healthcheck sa loob ng container, kaya dapat nitong gamitin ang container port, hindi kailanman ang published host port. Sa ports: - "8080:3000", nakikinig ang application sa port 3000. Dahil dito, walang katapusang mabibigo ang check laban sa http://localhost:8080 kahit gumagana nang maayos ang site sa browser. Ang pangalawa ay maling host. Sa loob ng check, ang localhost ay ang mismong container. Tama ito kapag chine-check ang container mismo, pero mali kapag chine-check ang katabing service. Sa ganitong kaso, gamitin ang service name, gaya ng db.

May isa pang kaso na kailangang tukuyin: pumapasa ang healthcheck habang nakikita ng mga user ang mga error. Nangyayari ito kapag nagbabalik ang endpoint ng static na 200 nang walang aktuwal na sinusuri. Hindi malalaman ng readiness endpoint na hindi na available ang database kung hindi ito nagsasagawa ng query sa database. Patakbuhin dito ang isang magaan ngunit totoong query.

FAQ

Bakit hindi pa rin makakonekta ang app ko kahit sinasabi ng depends_on na healthy ang database?

Dahil isang beses lang sinusuri ang condition: service_healthy, kapag nagsisimula ang stack. Hindi nito mino-monitor ang anuman pagkatapos nito. Kung mag-restart ang database container sa kalaunan, hindi nire-restart ng Compose ang application para muling matugunan ang kondisyon. Kaya kailangan ng application code mo ng sarili nitong reconnect at retry logic. Wala ring epekto ang kondisyon kapag isang container lang ang sinimulan mo gamit ang docker start o docker compose up --no-deps.

Kailangan ko ba ng healthcheck kung may nakadefine na ang image?

Karaniwan, hindi na. Madalas ay hakbang paatras ang pag-override nito dahil alam ng image maintainer kung ano ang ibig sabihin ng pagiging ready para sa software na iyon. Magdagdag ka lang ng sarili mong healthcheck kapag mali para sa setup mo ang check ng image, halimbawa kung sinusuri nito ang port na inilipat mo. Para i-off ang healthcheck ng image, itakda ang test: ["NONE"] o disable: true sa service.

Dapat bang curl o wget ang gamitin ng healthcheck?

Gamitin kung alinman ang mayroon na sa image, at kumpirmahin ito gamit ang docker compose exec <service> curl --version bago ka umasa rito. Maraming Debian based image ang walang alinman sa dalawa. May BusyBox wget ang Alpine based image. Huwag magdagdag ng package sa image para lang magpatakbo ng healthcheck kung may sarili nang client ang software, gaya ng pg_isready o redis-cli.

Awtomatiko bang nire-restart ang unhealthy container?

Hindi ng Docker Engine sa isang host lang. Tumutugon ang restart policies sa pag-exit ng process, hindi sa health state. Kaya nananatiling tumatakbo at sira ang unhealthy container hanggang may ibang kumilos dito. Ipa-exit ang process kapag natukoy nito ang failure, o magpatakbo ng external monitor na nag-aalerto kapag nagbago ang state.

Gaano dapat katagal ang start_period?

Dapat sapat ito para sa pinakamabagal na lehitimong unang start na nasukat mo, kasama ang margin. Sukatin ito gamit ang docker compose up laban sa empty volume, dahil mas mabagal ang unang start ng database kaysa sa lahat ng kasunod na start. Ang sobrang habang start period ay nagde-delay lang sa unang unhealthy verdict. Ang sobrang daming retry ay nagpapahina sa check sa buong buhay ng container. Ito ang mas malalang failure.

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