[{"data":1,"prerenderedAt":717},["ShallowReactive",2],{"mdc-yirgek-key":3,"mdc--w9zc59-key":21,"mdc--50hx0a-key":91,"mdc--bpam0v-key":132,"mdc-xwyr5l-key":144,"mdc--uf0g9y-key":231,"mdc-ko3shp-key":384,"mdc-3b9eno-key":394,"mdc--x3five-key":482,"mdc-4nppdo-key":494},{"data":4,"body":5},{},{"type":6,"children":7},"root",[8,16],{"type":9,"tag":10,"props":11,"children":12},"element","p",{},[13],{"type":14,"value":15},"text","Docker knows whether your process is running. It does not know whether your application works, and it will happily keep a wedged container in the rotation forever unless you tell it how to check.",{"type":9,"tag":10,"props":17,"children":18},{},[19],{"type":14,"value":20},"The other half of the same subject: containers have to stop as well as start, and stopping badly is where dropped requests and corrupted state come from.",{"data":22,"body":23},{},{"type":6,"children":24},[25,32,46,51,62],{"type":9,"tag":26,"props":27,"children":29},"h2",{"id":28},"healthchecks",[30],{"type":14,"value":31},"Healthchecks",{"type":9,"tag":33,"props":34,"children":40},"pre",{"className":35,"code":37,"language":38,"meta":39},[36],"language-dockerfile","HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \\\n  CMD curl -fsS http://localhost:3000/healthz || exit 1\n","dockerfile","",[41],{"type":9,"tag":42,"props":43,"children":44},"code",{"__ignoreMap":39},[45],{"type":14,"value":37},{"type":9,"tag":10,"props":47,"children":48},{},[49],{"type":14,"value":50},"Or in Compose, where it does not need to be baked into the image:",{"type":9,"tag":33,"props":52,"children":57},{"className":53,"code":55,"language":56,"meta":39},[54],"language-yaml","services:\n  api:\n    image: myapp\n    healthcheck:\n      test: [\"CMD\", \"curl\", \"-fsS\", \"http://localhost:3000/healthz\"]\n      interval: 30s\n      timeout: 3s\n      start_period: 40s\n      retries: 3\n","yaml",[58],{"type":9,"tag":42,"props":59,"children":60},{"__ignoreMap":39},[61],{"type":14,"value":55},{"type":9,"tag":10,"props":63,"children":64},{},[65,67,73,75,81,83,89],{"type":14,"value":66},"The container gets a status: ",{"type":9,"tag":42,"props":68,"children":70},{"className":69},[],[71],{"type":14,"value":72},"starting",{"type":14,"value":74},", then ",{"type":9,"tag":42,"props":76,"children":78},{"className":77},[],[79],{"type":14,"value":80},"healthy",{"type":14,"value":82}," or ",{"type":9,"tag":42,"props":84,"children":86},{"className":85},[],[87],{"type":14,"value":88},"unhealthy",{"type":14,"value":90},".",{"data":92,"body":93},{},{"type":6,"children":94},[95,100,127],{"type":9,"tag":96,"props":97,"children":99},"terminal-teaser",{":lines":98},"[{\"cmd\":\"docker ps\",\"out\":\"CONTAINER ID   IMAGE   STATUS\\na7f2c91e4b06   myapp   Up 2 minutes (healthy)\"},{\"cmd\":\"docker inspect -f '{{.State.Health.Status}}' api\",\"out\":\"healthy\"},{\"cmd\":\"docker inspect -f '{{range .State.Health.Log}}{{.ExitCode}} {{.Output}}{{end}}' api\",\"out\":\"0 OK\"}]",[],{"type":9,"tag":10,"props":101,"children":102},{},[103,109,111,117,119,125],{"type":9,"tag":42,"props":104,"children":106},{"className":105},[],[107],{"type":14,"value":108},"start_period",{"type":14,"value":110}," is the field people leave out and then fight. During it, failures do ",{"type":9,"tag":112,"props":113,"children":114},"strong",{},[115],{"type":14,"value":116},"not",{"type":14,"value":118}," count toward ",{"type":9,"tag":42,"props":120,"children":122},{"className":121},[],[123],{"type":14,"value":124},"retries",{"type":14,"value":126}," — it is the grace window for an app that takes thirty seconds to connect to its database and warm a cache. Without it, a slow-starting service is marked unhealthy before it ever had a chance.",{"type":9,"tag":10,"props":128,"children":129},{},[130],{"type":14,"value":131},"Engine 29 also surfaces health in the API's container list, so tooling can read it without inspecting each container individually.",{"data":133,"body":134},{},{"type":6,"children":135},[136],{"type":9,"tag":137,"props":138,"children":143},"quiz",{":answer":139,":options":140,"explanation":141,"question":142},"0","[\"Failures during it don't count toward `retries`, giving a slow-starting app time to come up\",\"It delays the first check by that duration\",\"It is the maximum time the container may take to start before being killed\"]","Checks still run during the period — a success ends it early — but failures are forgiven. Omit it on an app with a slow warmup and it is marked unhealthy while it is still legitimately starting.","What does `start_period` do in a healthcheck?",[],{"data":145,"body":146},{},{"type":6,"children":147},[148,154,159,168,187],{"type":9,"tag":26,"props":149,"children":151},{"id":150},"write-the-check-honestly",[152],{"type":14,"value":153},"Write the check honestly",{"type":9,"tag":10,"props":155,"children":156},{},[157],{"type":14,"value":158},"A healthcheck that always passes is worse than none, because it makes an outage look like a healthy service.",{"type":9,"tag":33,"props":160,"children":163},{"className":161,"code":162,"language":56,"meta":39},[54],"test: [\"CMD-SHELL\", \"exit 0\"]                    # meaningless\ntest: [\"CMD\", \"curl\", \"-f\", \"http://localhost:3000/\"]   # is the port open?\ntest: [\"CMD\", \"curl\", \"-fsS\", \"http://localhost:3000/healthz\"]  # does the app work?\n",[164],{"type":9,"tag":42,"props":165,"children":166},{"__ignoreMap":39},[167],{"type":14,"value":162},{"type":9,"tag":10,"props":169,"children":170},{},[171,173,179,181,185],{"type":14,"value":172},"A useful ",{"type":9,"tag":42,"props":174,"children":176},{"className":175},[],[177],{"type":14,"value":178},"/healthz",{"type":14,"value":180}," checks the things whose failure means this container cannot serve: the database connection, a required cache, a filesystem it must write to. It should ",{"type":9,"tag":112,"props":182,"children":183},{},[184],{"type":14,"value":116},{"type":14,"value":186}," check things it does not control — a downstream API being slow should not take your whole fleet out of rotation.",{"type":9,"tag":10,"props":188,"children":189},{},[190,192,198,200,206,208,213,215,221,223,229],{"type":14,"value":191},"Note ",{"type":9,"tag":42,"props":193,"children":195},{"className":194},[],[196],{"type":14,"value":197},"CMD",{"type":14,"value":199}," versus ",{"type":9,"tag":42,"props":201,"children":203},{"className":202},[],[204],{"type":14,"value":205},"CMD-SHELL",{"type":14,"value":207},": the array form execs directly and needs no shell in the image, while ",{"type":9,"tag":42,"props":209,"children":211},{"className":210},[],[212],{"type":14,"value":205},{"type":14,"value":214}," runs through ",{"type":9,"tag":42,"props":216,"children":218},{"className":217},[],[219],{"type":14,"value":220},"/bin/sh",{"type":14,"value":222}," and lets you use pipes and ",{"type":9,"tag":42,"props":224,"children":226},{"className":225},[],[227],{"type":14,"value":228},"||",{"type":14,"value":230},". Distroless images can only use the first, which is why healthchecks for them are usually a small static binary shipped alongside the app.",{"data":232,"body":233},{},{"type":6,"children":234},[235,249,281,293,306,339,365,375],{"type":9,"tag":26,"props":236,"children":238},{"id":237},"stopping-what-docker-stop-actually-does",[239,241,247],{"type":14,"value":240},"Stopping: what ",{"type":9,"tag":42,"props":242,"children":244},{"className":243},[],[245],{"type":14,"value":246},"docker stop",{"type":14,"value":248}," actually does",{"type":9,"tag":250,"props":251,"children":252},"ol",{},[253,265,270],{"type":9,"tag":254,"props":255,"children":256},"li",{},[257,263],{"type":9,"tag":42,"props":258,"children":260},{"className":259},[],[261],{"type":14,"value":262},"SIGTERM",{"type":14,"value":264}," to PID 1.",{"type":9,"tag":254,"props":266,"children":267},{},[268],{"type":14,"value":269},"Wait — ten seconds by default.",{"type":9,"tag":254,"props":271,"children":272},{},[273,279],{"type":9,"tag":42,"props":274,"children":276},{"className":275},[],[277],{"type":14,"value":278},"SIGKILL",{"type":14,"value":280}," if it is still alive.",{"type":9,"tag":10,"props":282,"children":283},{},[284,286,291],{"type":14,"value":285},"So a graceful shutdown means catching ",{"type":9,"tag":42,"props":287,"children":289},{"className":288},[],[290],{"type":14,"value":262},{"type":14,"value":292},", refusing new work, finishing in-flight requests, closing connections, and exiting. If your process does not, it gets killed mid-request every single deploy.",{"type":9,"tag":10,"props":294,"children":295},{},[296,298,304],{"type":14,"value":297},"Two things routinely break this even in applications that ",{"type":9,"tag":299,"props":300,"children":301},"em",{},[302],{"type":14,"value":303},"do",{"type":14,"value":305}," handle the signal:",{"type":9,"tag":10,"props":307,"children":308},{},[309,314,316,322,324,330,332,338],{"type":9,"tag":112,"props":310,"children":311},{},[312],{"type":14,"value":313},"Shell form in the Dockerfile.",{"type":14,"value":315}," ",{"type":9,"tag":42,"props":317,"children":319},{"className":318},[],[320],{"type":14,"value":321},"CMD node server.js",{"type":14,"value":323}," becomes ",{"type":9,"tag":42,"props":325,"children":327},{"className":326},[],[328],{"type":14,"value":329},"/bin/sh -c \"node server.js\"",{"type":14,"value":331},". The shell is PID 1, does not forward signals, and your app never hears anything. Use the exec form: ",{"type":9,"tag":42,"props":333,"children":335},{"className":334},[],[336],{"type":14,"value":337},"CMD [\"node\", \"server.js\"]",{"type":14,"value":90},{"type":9,"tag":10,"props":340,"children":341},{},[342,347,349,355,357,363],{"type":9,"tag":112,"props":343,"children":344},{},[345],{"type":14,"value":346},"Nothing reaping children.",{"type":14,"value":348}," A PID 1 that spawns processes must ",{"type":9,"tag":42,"props":350,"children":352},{"className":351},[],[353],{"type":14,"value":354},"wait()",{"type":14,"value":356}," on them or they accumulate as zombies. ",{"type":9,"tag":42,"props":358,"children":360},{"className":359},[],[361],{"type":14,"value":362},"--init",{"type":14,"value":364}," inserts a tiny init process that handles both reaping and signal forwarding:",{"type":9,"tag":33,"props":366,"children":370},{"className":367,"code":369,"language":14},[368],"language-text","docker run --init myapp\n",[371],{"type":9,"tag":42,"props":372,"children":373},{"__ignoreMap":39},[374],{"type":14,"value":369},{"type":9,"tag":33,"props":376,"children":379},{"className":377,"code":378,"language":56,"meta":39},[54],"services:\n  api:\n    init: true\n",[380],{"type":9,"tag":42,"props":381,"children":382},{"__ignoreMap":39},[383],{"type":14,"value":378},{"data":385,"body":386},{},{"type":6,"children":387},[388],{"type":9,"tag":137,"props":389,"children":393},{":answer":139,":options":390,"explanation":391,"question":392},"[\"PID 1 is ignoring SIGTERM, so Docker waits out the grace period and SIGKILLs it\",\"The healthcheck is blocking shutdown\",\"Docker always waits 10 seconds before stopping a container\"]","Ten seconds is the default grace period, and hitting it exactly every time means nothing acted on the SIGTERM. Usually shell form making `/bin/sh` PID 1; sometimes an application with no signal handler. Both drop in-flight requests on every deploy.","A container always takes exactly 10 seconds to stop, then dies. What is happening?",[],{"data":395,"body":396},{},{"type":6,"children":397},[398,404,413,422,431,464],{"type":9,"tag":26,"props":399,"children":401},{"id":400},"tuning-the-grace-period",[402],{"type":14,"value":403},"Tuning the grace period",{"type":9,"tag":33,"props":405,"children":408},{"className":406,"code":407,"language":14},[368],"docker run --stop-timeout 30 myapp\ndocker stop -t 30 api\n",[409],{"type":9,"tag":42,"props":410,"children":411},{"__ignoreMap":39},[412],{"type":14,"value":407},{"type":9,"tag":33,"props":414,"children":417},{"className":415,"code":416,"language":38,"meta":39},[36],"STOPSIGNAL SIGQUIT\n",[418],{"type":9,"tag":42,"props":419,"children":420},{"__ignoreMap":39},[421],{"type":14,"value":416},{"type":9,"tag":33,"props":423,"children":426},{"className":424,"code":425,"language":56,"meta":39},[54],"services:\n  api:\n    stop_grace_period: 30s\n    stop_signal: SIGQUIT\n",[427],{"type":9,"tag":42,"props":428,"children":429},{"__ignoreMap":39},[430],{"type":14,"value":425},{"type":9,"tag":10,"props":432,"children":433},{},[434,440,442,447,449,454,456,462],{"type":9,"tag":42,"props":435,"children":437},{"className":436},[],[438],{"type":14,"value":439},"STOPSIGNAL",{"type":14,"value":441}," matters for software that does not use the convention — nginx treats ",{"type":9,"tag":42,"props":443,"children":445},{"className":444},[],[446],{"type":14,"value":262},{"type":14,"value":448}," as a ",{"type":9,"tag":299,"props":450,"children":451},{},[452],{"type":14,"value":453},"fast",{"type":14,"value":455}," shutdown that drops connections and ",{"type":9,"tag":42,"props":457,"children":459},{"className":458},[],[460],{"type":14,"value":461},"SIGQUIT",{"type":14,"value":463}," as the graceful one, which is exactly backwards from what Docker sends by default.",{"type":9,"tag":10,"props":465,"children":466},{},[467,469,480],{"type":14,"value":468},"Engine 29.7 added a ",{"type":9,"tag":112,"props":470,"children":471},{},[472,478],{"type":9,"tag":42,"props":473,"children":475},{"className":474},[],[476],{"type":14,"value":477},"default-stop-timeout",{"type":14,"value":479}," daemon option",{"type":14,"value":481},", so a host can set the fleet-wide default instead of every container carrying its own flag.",{"data":483,"body":484},{},{"type":6,"children":485},[486],{"type":9,"tag":487,"props":488,"children":493},"fill-blank",{":answer":489,"hint":490,"placeholder":491,"prompt":492},"[\"docker stop -t 30 api\",\"docker stop --time 30 api\",\"docker stop --timeout 30 api\",\"docker stop -t30 api\"]","One flag for the grace period, then the container name.","docker stop ...","Stop the container named `api`, allowing 30 seconds before it is killed.",[],{"data":495,"body":496},{},{"type":6,"children":497},[498,712],{"type":9,"tag":499,"props":500,"children":502},"deep-dive",{"title":501},"Restart policies, and the crash loop that hides itself",[503,512,615,632,660,669,689],{"type":9,"tag":33,"props":504,"children":507},{"className":505,"code":506,"language":14},[368],"docker run --restart unless-stopped myapp\n",[508],{"type":9,"tag":42,"props":509,"children":510},{"__ignoreMap":39},[511],{"type":14,"value":506},{"type":9,"tag":513,"props":514,"children":515},"table",{},[516,535],{"type":9,"tag":517,"props":518,"children":519},"thead",{},[520],{"type":9,"tag":521,"props":522,"children":523},"tr",{},[524,530],{"type":9,"tag":525,"props":526,"children":527},"th",{},[528],{"type":14,"value":529},"Policy",{"type":9,"tag":525,"props":531,"children":532},{},[533],{"type":14,"value":534},"Behaviour",{"type":9,"tag":536,"props":537,"children":538},"tbody",{},[539,557,574,591],{"type":9,"tag":521,"props":540,"children":541},{},[542,552],{"type":9,"tag":543,"props":544,"children":545},"td",{},[546],{"type":9,"tag":42,"props":547,"children":549},{"className":548},[],[550],{"type":14,"value":551},"no",{"type":9,"tag":543,"props":553,"children":554},{},[555],{"type":14,"value":556},"the default — never restart",{"type":9,"tag":521,"props":558,"children":559},{},[560,569],{"type":9,"tag":543,"props":561,"children":562},{},[563],{"type":9,"tag":42,"props":564,"children":566},{"className":565},[],[567],{"type":14,"value":568},"on-failure[:N]",{"type":9,"tag":543,"props":570,"children":571},{},[572],{"type":14,"value":573},"restart on a non-zero exit, optionally at most N times",{"type":9,"tag":521,"props":575,"children":576},{},[577,586],{"type":9,"tag":543,"props":578,"children":579},{},[580],{"type":9,"tag":42,"props":581,"children":583},{"className":582},[],[584],{"type":14,"value":585},"always",{"type":9,"tag":543,"props":587,"children":588},{},[589],{"type":14,"value":590},"restart always, including after a daemon restart",{"type":9,"tag":521,"props":592,"children":593},{},[594,603],{"type":9,"tag":543,"props":595,"children":596},{},[597],{"type":9,"tag":42,"props":598,"children":600},{"className":599},[],[601],{"type":14,"value":602},"unless-stopped",{"type":9,"tag":543,"props":604,"children":605},{},[606,608,613],{"type":14,"value":607},"like ",{"type":9,"tag":42,"props":609,"children":611},{"className":610},[],[612],{"type":14,"value":585},{"type":14,"value":614},", but stays stopped if you stopped it deliberately",{"type":9,"tag":10,"props":616,"children":617},{},[618,623,625,630],{"type":9,"tag":42,"props":619,"children":621},{"className":620},[],[622],{"type":14,"value":602},{"type":14,"value":624}," is the right default for a service. ",{"type":9,"tag":42,"props":626,"children":628},{"className":627},[],[629],{"type":14,"value":585},{"type":14,"value":631}," will resurrect a container you deliberately stopped when the machine reboots, which is rarely what anyone meant.",{"type":9,"tag":10,"props":633,"children":634},{},[635,637,643,645,651,653,658],{"type":14,"value":636},"Docker backs off between restarts, doubling the delay up to a limit — so a container crashing on startup goes from restarting every second to every couple of minutes. Which is where this gets deceptive: ",{"type":9,"tag":42,"props":638,"children":640},{"className":639},[],[641],{"type":14,"value":642},"docker ps",{"type":14,"value":644}," shows ",{"type":9,"tag":42,"props":646,"children":648},{"className":647},[],[649],{"type":14,"value":650},"Up 3 seconds",{"type":14,"value":652}," and looks fine, because it ",{"type":9,"tag":299,"props":654,"children":655},{},[656],{"type":14,"value":657},"is",{"type":14,"value":659}," up, for the third time this minute.",{"type":9,"tag":33,"props":661,"children":664},{"className":662,"code":663,"language":14},[368],"docker inspect -f '{{.RestartCount}}' api\ndocker events --filter container=api --filter event=restart\n",[665],{"type":9,"tag":42,"props":666,"children":667},{"__ignoreMap":39},[668],{"type":14,"value":663},{"type":9,"tag":10,"props":670,"children":671},{},[672,674,680,682,687],{"type":14,"value":673},"A ",{"type":9,"tag":42,"props":675,"children":677},{"className":676},[],[678],{"type":14,"value":679},"RestartCount",{"type":14,"value":681}," in the hundreds on a container reporting ",{"type":9,"tag":42,"props":683,"children":685},{"className":684},[],[686],{"type":14,"value":650},{"type":14,"value":688}," is a crash loop presenting as a healthy service.",{"type":9,"tag":10,"props":690,"children":691},{},[692,697,699,704,706,710],{"type":9,"tag":112,"props":693,"children":694},{},[695],{"type":14,"value":696},"Restart policy and healthcheck do not interact.",{"type":14,"value":698}," An ",{"type":9,"tag":42,"props":700,"children":702},{"className":701},[],[703],{"type":14,"value":88},{"type":14,"value":705}," container is ",{"type":9,"tag":299,"props":707,"children":708},{},[709],{"type":14,"value":116},{"type":14,"value":711}," restarted by Docker — it is marked, and something above it is expected to act. Compose does not act. Swarm and Kubernetes do, which is why people who learned health checks on Kubernetes expect a restart that never comes. On a plain Docker host, an unhealthy container sits there being unhealthy until you or your monitoring notice.",{"type":9,"tag":10,"props":713,"children":714},{},[715],{"type":14,"value":716},"Next up: build caching across machines — making CI as fast as your laptop.",1787908868315]