podman builds an OCI image by default and drops Docker's HEALTHCHECK instruction, so the container had no health at all and the play failed waiting for one. The check is now declared on the docker_container task - node is in the image and the exec form needs no shell quoting - which is both what operators see in docker ps and what an orchestrator would use. The wait still asks the app directly rather than reading State.Health, because a container that already exists is only rebuilt when the image tag changes or when -e recreate_container=true is passed, and a wait must not depend on a container attribute an existing one may not have.
135 lines
4.0 KiB
YAML
135 lines
4.0 KiB
YAML
---
|
|
- name: Pull the app image
|
|
docker_image:
|
|
name: '{{ image_name }}'
|
|
tag: '{{ image_tag }}'
|
|
source: pull
|
|
force_source: yes
|
|
|
|
- name: Ensure the app root exists
|
|
file:
|
|
path: '{{ app_root }}'
|
|
state: directory
|
|
owner: '{{ app_uid }}'
|
|
group: '{{ app_gid }}'
|
|
mode: '0750'
|
|
|
|
- name: Ensure the data directory exists
|
|
file:
|
|
path: '{{ app_root }}/data'
|
|
state: directory
|
|
owner: '{{ app_uid }}'
|
|
group: '{{ app_gid }}'
|
|
mode: '0750'
|
|
|
|
- name: Look for the untracked site.env
|
|
stat:
|
|
path: '{{ app_root }}/site.env'
|
|
register: menu_site_env
|
|
|
|
- name: Warn when the admin has no password yet
|
|
debug:
|
|
msg: >-
|
|
{{ app_root }}/site.env is missing, so the admin is disabled: the public
|
|
menu still serves. Run scripts/provision-site-env.sh {{ app_root }} on
|
|
this host, then re-run this playbook.
|
|
when: not menu_site_env.stat.exists
|
|
|
|
- name: Run the menu app
|
|
docker_container:
|
|
name: '{{ docker_container }}'
|
|
image: '{{ image_name }}:{{ image_tag }}'
|
|
state: started
|
|
restart_policy: unless-stopped
|
|
user: '{{ app_uid }}:{{ app_gid }}'
|
|
# Force a rebuild of the container with -e recreate_container=true: that is
|
|
# what applies a changed healthcheck or env to an existing container, since
|
|
# the module otherwise leaves a running one alone.
|
|
recreate: '{{ recreate_container | default(false) | bool }}'
|
|
# Declared here rather than relied on from the image: podman builds an OCI
|
|
# image by default and drops Docker's HEALTHCHECK instruction, so the
|
|
# Dockerfile's own healthcheck never reaches the container. node is in the
|
|
# image and the exec form needs no shell quoting.
|
|
healthcheck:
|
|
test:
|
|
- CMD
|
|
- node
|
|
- -e
|
|
- "fetch('http://127.0.0.1:3000/healthz').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"
|
|
interval: 30s
|
|
timeout: 5s
|
|
start_period: 30s
|
|
retries: 3
|
|
env:
|
|
MENU_ROOT: /data
|
|
MENU_SITE_CONFIG: '/app/config/{{ site }}.site.json'
|
|
SECURE_COOKIES: 'true'
|
|
LOG_LEVEL: info
|
|
PORT: '3000'
|
|
volumes:
|
|
- '{{ app_root }}:/data'
|
|
networks:
|
|
- name: '{{ docker_network }}'
|
|
|
|
- name: Keep site.env readable only by the app user
|
|
file:
|
|
path: '{{ app_root }}/site.env'
|
|
owner: '{{ app_uid }}'
|
|
group: '{{ app_gid }}'
|
|
mode: '0600'
|
|
when: menu_site_env.stat.exists
|
|
|
|
# Deliberately not the image's HEALTHCHECK: podman builds an OCI image by
|
|
# default and drops the Docker HEALTHCHECK instruction, so State.Health is absent
|
|
# and a wait on it fails on a perfectly healthy container — which stops the play
|
|
# before Caddy is ever pointed at the app.
|
|
- name: Wait for the app to answer inside its own container
|
|
ansible.builtin.command:
|
|
argv:
|
|
- docker
|
|
- exec
|
|
- '{{ docker_container }}'
|
|
- node
|
|
- -e
|
|
- "fetch('http://127.0.0.1:3000/healthz').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"
|
|
register: menu_health
|
|
changed_when: false
|
|
retries: 20
|
|
delay: 3
|
|
until: menu_health.rc == 0
|
|
failed_when: false
|
|
|
|
- name: Fail when the app never answers
|
|
fail:
|
|
msg: >-
|
|
{{ docker_container }} did not answer /healthz within a minute. Its logs
|
|
say why: docker logs {{ docker_container }}
|
|
when: menu_health.rc != 0
|
|
|
|
- name: Check the data directory for integrity problems
|
|
# Reads the same directory the container writes, as the same user, so a
|
|
# permissions mistake shows up here rather than at 8pm service.
|
|
command:
|
|
argv:
|
|
- docker
|
|
- run
|
|
- --rm
|
|
- --user
|
|
- '{{ app_uid }}:{{ app_gid }}'
|
|
- -e
|
|
- MENU_ROOT=/data
|
|
- -e
|
|
- 'MENU_SITE_CONFIG=/app/config/{{ site }}.site.json'
|
|
- --entrypoint
|
|
- node
|
|
- '{{ image_name }}:{{ image_tag }}'
|
|
- src/cli/verify.ts
|
|
register: menu_verify
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Report integrity problems
|
|
debug:
|
|
msg: '{{ menu_verify.stdout_lines | default([]) }}'
|
|
when: menu_verify.rc != 0
|