From 82e5b7d0f6d9aa128c87c577c7420c5b37651a23 Mon Sep 17 00:00:00 2001 From: Amritanshu Date: Fri, 16 Oct 2026 10:54:12 +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 instruction, 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 and healthy. - Declare the same check on the greatbear.container unit (HealthCmd plus interval, timeout, retries, start period), the convention hops and menu-admin already use. - 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. Mirrors hops 7366596, so the two deployments keep the same shape. --- .containerignore | 18 +++++++++++++++++- ansible/roles/greatbear/tasks/main.yaml | 13 +++++++++++-- .../greatbear/templates/greatbear.container.j2 | 11 ++++++++--- ansible/vars/default.yml | 5 +++++ 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/.containerignore b/.containerignore index 22be4fd..8bd17b5 100644 --- a/.containerignore +++ b/.containerignore @@ -1,24 +1,40 @@ # Keep the build context small and secret-free. # Read by Podman/Buildah (`.containerignore`) and, as a fallback, Docker. +# +# The build is `git archive HEAD`, so untracked files never reach the context +# anyway; this is belt-and-suspenders for a working-tree build. # VCS / editor / local state .git .gitignore .containerignore .env +.env.* .npm-cache .tmp-pb .podman .vscode +.idea design *.zip -# Node artifacts +# Node artifacts (installed/rebuilt inside the image) +node_modules **/node_modules app/.output +app/.tanstack app/.nitro app/dist +# PocketBase local data & scratch +pb_data +*.db +*.db-shm +*.db-wal + +# Logs +*.log + # Ops assets not needed inside the image ansible Makefile diff --git a/ansible/roles/greatbear/tasks/main.yaml b/ansible/roles/greatbear/tasks/main.yaml index b9bd457..83e30d3 100644 --- a/ansible/roles/greatbear/tasks/main.yaml +++ b/ansible/roles/greatbear/tasks/main.yaml @@ -86,6 +86,15 @@ ansible.builtin.fail: msg: "The Great Bear did not start — see the diagnostics above." +# The unit template notifies "Restart The Great Bear", 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 Great Bear unit is active @@ -97,8 +106,8 @@ # `is-active` says the unit is running, not that both processes inside the # container are healthy: the entrypoint starts PocketBase, waits for # /api/health, upserts the superuser and seeds content before the site server -# answers. The image ships a HEALTHCHECK covering both; `healthcheck run` forces -# one now and reports its result instead of waiting out the interval. +# answers. The unit's HealthCmd covers both; `healthcheck run` forces one now +# and reports its result instead of waiting out the interval. - name: Wait for the container healthcheck to pass ansible.builtin.command: "podman healthcheck run {{ container_name }}" register: greatbear_health diff --git a/ansible/roles/greatbear/templates/greatbear.container.j2 b/ansible/roles/greatbear/templates/greatbear.container.j2 index 580d9c0..b1bf50d 100644 --- a/ansible/roles/greatbear/templates/greatbear.container.j2 +++ b/ansible/roles/greatbear/templates/greatbear.container.j2 @@ -33,9 +33,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 ccb396b..5a1fdf5 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.greatbear.in apex_host: greatbear.in