{"record":{"id":"b4fccbd6f18b6342","repo":"basecamp/kamal","slug":"container-not-ready-after-kamal-config-deploy-ti","errorCode":null,"errorMessage":"container not ready after #{KAMAL.config.deploy_timeout} seconds (#{status})","messagePattern":"container not ready after #(.+?) seconds \\(#(.+?)\\)","errorType":"exception","errorClass":"Kamal::Cli::Healthcheck::Error","httpStatus":null,"severity":"critical","filePath":"lib/kamal/cli/healthcheck/poller.rb","lineNumber":22,"sourceCode":"  def wait_for_healthy(&block)\n    attempt = 1\n    timeout_at = Time.now + KAMAL.config.deploy_timeout\n    readiness_delay = KAMAL.config.readiness_delay\n\n    begin\n      status = block.call\n\n      if status == \"running\"\n        # Wait for the readiness delay and confirm it is still running\n        if readiness_delay > 0\n          info \"Container is running, waiting for readiness delay of #{readiness_delay} seconds\"\n          sleep readiness_delay\n          status = block.call\n        end\n      end\n\n      unless %w[ running healthy ].include?(status)\n        raise Kamal::Cli::Healthcheck::Error, \"container not ready after #{KAMAL.config.deploy_timeout} seconds (#{status})\"\n      end\n    rescue Kamal::Cli::Healthcheck::Error => e\n      time_left = timeout_at - Time.now\n      if time_left > 0\n        sleep [ attempt, time_left ].min\n        attempt += 1\n        retry\n      else\n        raise\n      end\n    end\n\n    info \"Container is healthy!\"\n  end\n\n  private\n    def info(message)\n      SSHKit.config.output.info(message)","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/basecamp/kamal/blob/eee0083b38661c3707c6b6052cc89e85038a096c/lib/kamal/cli/healthcheck/poller.rb#L4-L40","documentation":"During `kamal deploy` (and healthcheck-driven operations), Kamal::Cli::Healthcheck::Poller polls the app container's status on each host via the provided block (e.g. `docker inspect` health/running state). If the status is neither 'running' nor 'healthy' (e.g. 'unhealthy', 'exited', 'restarting') when the overall deadline (KAMAL.config.deploy_timeout seconds) expires, it raises Kamal::Cli::Healthcheck::Error with the last observed status. The poller retries with a growing sleep until time runs out, then surfaces this error, failing the deploy before traffic is switched.","triggerScenarios":"`kamal deploy` where the new container starts but its Docker HEALTHCHECK never reports healthy within deploy_timeout (missing curl in image, wrong healthcheck path/port), or the container exits/restarts (crash on boot). Also triggered with an unrealistic deploy_timeout for slow-booting apps; status is interpolated into the message so 'unhealthy' vs 'exited' tells you which case it is.","commonSituations":"Dockerfile healthcheck curling /up (Rails 7.1) while the image lacks curl or the app binds another port; app boots slowly (migrations, warmup) exceeding the default timeout; DB unreachable from the host so the readiness endpoint returns 500; healthcheck path returns 404 after a route change.","solutions":["Read the status in the message: 'unhealthy' → fix the Docker HEALTHCHECK (path, port, curl/wget present in image); 'exited'/'restarting' → check `kamal app logs -h <host>` for the crash.","Test the healthcheck manually on the host: `docker exec <container> curl -fsS localhost:3000/up` (or your configured path) to see the real response.","Raise the deadline in config/deploy.yml: `deploy_timeout: 300` for slow-booting apps.","If the healthcheck is wrong (route moved, port changed), fix the Dockerfile/composer healthcheck and rebuild, then redeploy."],"exampleFix":"# before (Dockerfile)\nHEALTHCHECK --interval=1s --timeout=1s CMD curl -fsS http://localhost:3000/up || exit 1\n# image has no curl -> always 'unhealthy'\n# after\nHEALTHCHECK --interval=1s --timeout=1s CMD wget -qO- http://localhost:3000/up || exit 1\n# or, if the app is just slow to boot (config/deploy.yml):\n# deploy_timeout: 300","handlingStrategy":"retry","validationCode":"# Before deploying, verify the healthcheck passes inside the image:\n# docker build -t app:test . && docker run -d --name t app:test && sleep 5\n# docker inspect --format '{{.State.Health.Status}}' t  # expect healthy\n# docker rm -f t","typeGuard":"def container_healthy?(image, wait: 10)\n  `docker run -d --name hc-check #{image} >/dev/null` or return false\n  sleep wait\n  status = `docker inspect --format '{{.State.Health.Status}}' hc-check`.strip\n  `docker rm -f hc-check >/dev/null`\n  status == \"healthy\"\nend","tryCatchPattern":"begin\n  Kamal::CLI::Deploy.new.perform\nrescue Kamal::Cli::Healthcheck::Error => e\n  status = e.message[/\\((.*)\\)\\z/, 1]\n  warn \"container status at timeout: #{status}\"\n  `kamal app logs` if %w[exited restarting].include?(status) # crash vs bad healthcheck\n  raise\nend","preventionTips":["Add a Docker HEALTHCHECK with tools guaranteed present in the image (wget/curl) and test it in CI.","Set deploy_timeout above your app's worst-case boot (migrations, cache warmup).","Verify the health endpoint path/port from inside the container before rolling deploys."],"tags":["kamal","healthcheck","deploy","container","timeout","docker"],"backgroundTag":"container-healthcheck-failed","analyzedSha":"eee0083b38661c3707c6b6052cc89e85038a096c","analyzedAt":"2026-08-21T15:17:22.045Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}