Kubernetes liveness is not a Docker healthcheck
Compose waits until php-fpm listens. Kubelet kills a pod that is still compiling OPcache. Readiness and liveness are different knobs.
The first Kubernetes deploy of this stack looked like a crash loop. Local bring-up is Docker Compose: nginx, php, queue, mysql, redis, memcached. A healthcheck that curls /up after FPM binds is enough. On Kubernetes the same check as liveness killed the pod during warmup. The first request compiles LaraBoom into OPcache. That takes longer than the default probe.
Users hit 502 through the Service. Kubelet restarted a process that was still compiling. Readiness removes the pod from the Service until it can take traffic. Liveness restarts a pod that is stuck. If you use one probe for both, a slow start looks like a dead process. The replica never becomes Ready.
Compose waited. Kubelet killed. That is the whole mismatch.
startupProbe owns the first minute
A startup probe hits /up every five seconds with a high failure threshold. Kubelet does not run liveness until that succeeds. After that, liveness may kill. Readiness stays on nginx in front of FPM, not on a route that still boots Doctrine or Eloquent.
Rolling update needs maxUnavailable that leaves at least one Ready php pod. SIGTERM from a deploy is the same story as Compose: workers must drain. A liveness that hits during drain just makes the 502 worse.
I pointed liveness at the same /up that boots the framework. The probe then failed while OPcache filled, and again while workers drained. A cheap TCP check on the FPM listen port is enough for liveness after startup. Readiness still HTTP through nginx, so a bound FPM with a dead nginx does not take traffic.
What Compose still is for
This site stays on Compose and a VPS until there are several nodes. Kubernetes is the next step, not a rename of the compose file. Probes, PodDisruptionBudget, and a real readiness path are the cost of that step. Copying healthcheck from Dockerfile into liveness is how you pay it twice.
I also learned that a queue worker pod must not share the web readiness path. Horizon can be alive while FPM is not serving HTML. Separate probes, or the web Service steals queue pods. PDB on php must count Ready web pods, not every container in the pod.
What I took from this
I watch Ready versus Restarts during warmup, not whether FPM has bound. A crash loop with a compiling OPcache is a liveness copied from Compose.
I do not trust one probe for start, live, and ready. startupProbe covers compile. Liveness may be TCP on FPM after that. Readiness is nginx in front of FPM.
The rule I keep: maxUnavailable leaves one Ready php pod. Do not run liveness during drain. Do not paste Dockerfile healthcheck into liveness. Compose stays until there are several nodes.
