From 7366596a878e611fc45b21331acae964a413b56d Mon Sep 17 00:00:00 2001 From: Amritanshu Date: Fri, 16 Oct 2026 10:46:01 +0530 Subject: [PATCH] Fix the Quadlet healthcheck so the deploy gate can pass podman builds an OCI image by default and drops the Dockerfile's HEALTHCHECK, so the first deploy failed at "Wait for the container healthcheck to pass" with "container ... has no defined healthcheck" even though the app was up. - Declare the same check on the hops.container unit (HealthCmd + interval, timeout, retries, start period), the convention menu-admin already uses. - Flush handlers right after starting, before the gate: the unit template's restart handler otherwise runs at the end of the play, so a run that changes only the unit would test the stale container. --- ansible/roles/hops/tasks/main.yaml | 9 +++++++++ ansible/roles/hops/templates/hops.container.j2 | 11 ++++++++--- ansible/vars/default.yml | 5 +++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/ansible/roles/hops/tasks/main.yaml b/ansible/roles/hops/tasks/main.yaml index 814d625..f39f522 100644 --- a/ansible/roles/hops/tasks/main.yaml +++ b/ansible/roles/hops/tasks/main.yaml @@ -86,6 +86,15 @@ ansible.builtin.fail: msg: "Hops n Grains did not start — see the diagnostics above." +# The unit template notifies "Restart Hops n Grains", but handlers otherwise run +# at the very end of the play — after the healthcheck gate below. Flush them +# here so the gate tests the container that has the current HealthCmd; without +# this, a run that only changes the unit would check the stale container and +# fail. On a run where nothing changed, no handler is pending and this is a +# no-op. +- name: Apply pending unit changes before verifying + ansible.builtin.meta: flush_handlers + # Quadlet honours [Install] WantedBy= when it generates the unit, so # `systemctl --user enable` is neither needed nor possible on a generated unit. - name: Verify the Hops n Grains unit is active diff --git a/ansible/roles/hops/templates/hops.container.j2 b/ansible/roles/hops/templates/hops.container.j2 index 4689b50..3020e67 100644 --- a/ansible/roles/hops/templates/hops.container.j2 +++ b/ansible/roles/hops/templates/hops.container.j2 @@ -32,9 +32,14 @@ Volume={{ host_directory }}/pb_data:/app/pb_data # No PublishPort on purpose: the Docker-era deployment had none either, and # Caddy reaches both ports over {{ network_name }}_net. -# The image's own HEALTHCHECK (PocketBase /api/health on {{ pb_port }} and the -# site on {{ site_port }}) is used as is; the playbook gates on -# `podman healthcheck run`. +# podman builds an OCI image by default and drops the Dockerfile's HEALTHCHECK +# instruction, so the same check is declared here and the play gates on it with +# `podman healthcheck run`. curl is in the runner image. +HealthCmd={{ health_cmd }} +HealthInterval=15s +HealthTimeout=3s +HealthRetries=3 +HealthStartPeriod=5s [Service] Restart=always diff --git a/ansible/vars/default.yml b/ansible/vars/default.yml index 2b06d14..31562d5 100644 --- a/ansible/vars/default.yml +++ b/ansible/vars/default.yml @@ -41,6 +41,11 @@ host_directory: "{{ podman_data_dir }}/{{ container_name }}" site_port: 3000 pb_port: 8090 +# The Dockerfile's HEALTHCHECK is dropped when podman builds an OCI image (its +# default), so the same check is declared on the Quadlet unit and the play gates +# on `podman healthcheck run`. curl is in the runner image. +health_cmd: "curl -fsS http://127.0.0.1:{{ pb_port }}/api/health > /dev/null && curl -fsS http://127.0.0.1:{{ site_port }} > /dev/null" + # Public hosts for frontend & PocketBase routing. host: www.hopsngrains.com apex_host: hopsngrains.com