Skip to content

Instantly share code, notes, and snippets.

@seia-soto
Last active March 9, 2026 08:23
Show Gist options
  • Select an option

  • Save seia-soto/f38965b3977e32110c5a7b147a16d30e to your computer and use it in GitHub Desktop.

Select an option

Save seia-soto/f38965b3977e32110c5a7b147a16d30e to your computer and use it in GitHub Desktop.
Podman compose init rc-script
#!/bin/sh
set -u
PATH=/usr/sbin:/usr/bin:/sbin:/bin
export LC_ALL=C
umask 077
SCRIPT_NAME=${0##*/}
INTERVAL=${INTERVAL:-60}
PARALLEL_JOBS=${PARALLEL_JOBS:-auto}
STATEFILE=${STATEFILE:-${TMPDIR:-/tmp}/healthcheck.state}
is_positive_integer() {
case ${1:-} in
''|*[!0-9]*|0)
return 1
;;
*)
return 0
;;
esac
}
is_executable() {
command -v "$1" >/dev/null 2>&1
}
is_xargs_parallel() {
printf '%s\n' probe | xargs -n 1 -P 1 printf '%s\n' >/dev/null 2>&1
}
is_list_contains() {
local needle=$1 list=$2 item=
for item in $list
do
if [ "$item" = "$needle" ]; then
return 0
fi
done
return 1
}
is_container_running() {
[ "$(podman inspect --format '{{.State.Running}}' "$1" 2>/dev/null)" = "true" ]
}
get_parallel_jobs() {
local jobs=$PARALLEL_JOBS
case $jobs in
''|auto)
jobs=$(nproc 2>/dev/null || printf '%s\n' 1)
;;
esac
if ! is_positive_integer "$jobs"; then
jobs=1
fi
printf '%s\n' "$jobs"
}
get_running_container_ids() {
podman ps -q
}
get_line_count() {
wc -l | tr -d '[:space:]'
}
get_state_dir() {
dirname "$STATEFILE"
}
get_last_checked_at() {
local cid=$1 id= ts=
if [ ! -r "$STATEFILE" ]; then
printf '%s\n' 0
return 0
fi
while IFS=' ' read -r id ts
do
[ -n "$id" ] || continue
[ "$id" = "$cid" ] || continue
if is_positive_integer "${ts:-0}"; then
printf '%s\n' "$ts"
else
printf '%s\n' 0
fi
return 0
done < "$STATEFILE"
printf '%s\n' 0
}
get_container_interval() {
local cid=$1 interval_ns= interval=
interval_ns=$(podman inspect --format '{{if .Config.Healthcheck}}{{printf "%d" .Config.Healthcheck.Interval}}{{end}}' "$cid" 2>/dev/null) || return 1
if [ -z "$interval_ns" ]; then
return 2
fi
case $interval_ns in
*[!0-9]*)
return 1
;;
esac
if [ "$interval_ns" -le 0 ]; then
interval=$INTERVAL
else
interval=$(( (interval_ns + 999999999) / 1000000000 ))
fi
if ! is_positive_integer "$interval"; then
interval=$INTERVAL
fi
printf '%s\n' "$interval"
}
get_due_container_ids() {
local now=$1 container_ids=$2 cid= interval= last_checked= remaining= rc=
for cid in $container_ids
do
interval=$(get_container_interval "$cid")
rc=$?
case $rc in
0)
;;
2)
use_log INFO "container $cid skipped: no healthcheck defined"
continue
;;
*)
use_log WARN "unable to inspect container $cid healthcheck interval"
continue
;;
esac
last_checked=$(get_last_checked_at "$cid")
if ! is_positive_integer "$last_checked"; then
last_checked=0
fi
if [ "$last_checked" -gt "$now" ]; then
last_checked=0
fi
if [ $((now - last_checked)) -ge "$interval" ]; then
printf '%s\n' "$cid"
else
remaining=$((interval - (now - last_checked)))
use_log INFO "container $cid skipped: interval not due (${remaining}s remaining)"
fi
done
}
get_health_state() {
local cid=$1 state=
state=$(podman inspect --format '{{if .State.Health}}{{.State.Health.Status}} {{.State.Health.FailingStreak}}{{else if .State.Healthcheck}}{{.State.Healthcheck.Status}} {{.State.Healthcheck.FailingStreak}}{{end}}' "$cid" 2>/dev/null) || return 1
printf '%s\n' "$state"
}
set_statefile_ready() {
local dir=$(get_state_dir)
if [ ! -d "$dir" ] && ! install -d -m 0700 "$dir"; then
use_log ERROR "failed to create statefile directory: $dir"
return 1
fi
if [ ! -e "$STATEFILE" ] && ! install -m 0600 /dev/null "$STATEFILE"; then
use_log ERROR "failed to create statefile: $STATEFILE"
return 1
fi
return 0
}
set_statefile_entries() {
local checked_at=$1 running_ids=$2 checked_ids=$3 dir= tmp_state= id= ts=
dir=$(get_state_dir)
if [ ! -d "$dir" ] && ! install -d -m 0700 "$dir"; then
use_log ERROR "failed to create statefile directory: $dir"
return 1
fi
tmp_state="${STATEFILE}.$$"
if ! : > "$tmp_state"; then
use_log ERROR "failed to write temporary statefile: $tmp_state"
return 1
fi
if [ -r "$STATEFILE" ]; then
while IFS=' ' read -r id ts
do
[ -n "$id" ] || continue
is_list_contains "$id" "$running_ids" || continue
is_list_contains "$id" "$checked_ids" && continue
if ! is_positive_integer "${ts:-0}"; then
ts=0
fi
if ! printf '%s %s\n' "$id" "$ts" >> "$tmp_state"; then
rm -f "$tmp_state"
use_log ERROR "failed to update temporary statefile: $tmp_state"
return 1
fi
done < "$STATEFILE"
fi
for id in $checked_ids
do
is_list_contains "$id" "$running_ids" || continue
if ! printf '%s %s\n' "$id" "$checked_at" >> "$tmp_state"; then
rm -f "$tmp_state"
use_log ERROR "failed to update temporary statefile: $tmp_state"
return 1
fi
done
if ! mv "$tmp_state" "$STATEFILE"; then
rm -f "$tmp_state"
use_log ERROR "failed to replace statefile: $STATEFILE"
return 1
fi
return 0
}
use_log() {
local level=$1
shift
local ts=$(date '+%Y-%m-%dT%H:%M:%S%z' 2>/dev/null || date)
printf '%s [%s] %s: %s\n' "$ts" "$SCRIPT_NAME" "$level" "$*" >&2
}
use_usage() {
cat <<EOF
Usage:
${SCRIPT_NAME} help
${SCRIPT_NAME} check
${SCRIPT_NAME} daemon
Notes:
- Execute this script directly, not as "sh ${SCRIPT_NAME}".
- For Alpine, install:
apk add findutils coreutils
- Configure it with environment variables.
Example:
INTERVAL=10 PARALLEL_JOBS=4 STATEFILE=/run/healthcheck.state ./${SCRIPT_NAME} daemon
- The script polls every INTERVAL seconds, tracks last checks in STATEFILE,
and only runs a container when its configured health interval is due.
- Containers that stop before execution are skipped.
Config variables:
INTERVAL=60
PARALLEL_JOBS=auto
STATEFILE=/tmp/healthcheck.state
EOF
}
use_config_check() {
local jobs=
if ! is_executable podman; then
use_log ERROR "podman executable not found in PATH"
return 1
fi
if ! is_executable xargs; then
use_log ERROR "xargs executable not found in PATH"
return 1
fi
if ! is_executable nproc; then
use_log ERROR "nproc executable not found in PATH"
return 1
fi
if ! is_xargs_parallel; then
use_log ERROR "xargs does not support -P; install Alpine findutils"
return 1
fi
if ! is_positive_integer "$INTERVAL"; then
use_log ERROR "INTERVAL must be a positive integer"
return 1
fi
jobs=$(get_parallel_jobs)
if ! is_positive_integer "$jobs"; then
use_log ERROR "PARALLEL_JOBS resolved to an invalid value"
return 1
fi
if ! podman ps -q >/dev/null 2>&1; then
use_log ERROR "podman is not accessible for this user/context"
return 1
fi
if ! set_statefile_ready; then
return 1
fi
use_log INFO "configuration looks good (jobs=$jobs interval=${INTERVAL}s statefile=$STATEFILE)"
return 0
}
use_worker() {
local container_id=$1 healthcheck_output= healthcheck_rc= health_state= health_status= failing_streak=
if ! is_container_running "$container_id"; then
use_log INFO "container $container_id skipped: no longer running"
return 0
fi
healthcheck_output=$(podman healthcheck run "$container_id" 2>&1)
healthcheck_rc=$?
case $healthcheck_rc in
0|1)
health_state=$(get_health_state "$container_id")
if [ $? -ne 0 ]; then
if [ -n "$healthcheck_output" ]; then
use_log ERROR "container $container_id health state unavailable: $healthcheck_output"
else
use_log ERROR "container $container_id health state unavailable"
fi
return 1
fi
health_status=${health_state%% *}
failing_streak=${health_state#* }
if [ "$failing_streak" = "$health_state" ]; then
failing_streak=0
fi
case $health_status in
healthy)
use_log INFO "container $container_id healthy"
return 0
;;
starting)
use_log INFO "container $container_id starting (failing_streak=$failing_streak)"
return 0
;;
unhealthy)
if [ -n "$healthcheck_output" ]; then
use_log WARN "container $container_id unhealthy (failing_streak=$failing_streak): $healthcheck_output"
else
use_log WARN "container $container_id unhealthy (failing_streak=$failing_streak)"
fi
return 1
;;
'')
if [ -n "$healthcheck_output" ]; then
use_log INFO "container $container_id healthcheck executed: $healthcheck_output"
else
use_log INFO "container $container_id healthcheck executed"
fi
return 0
;;
*)
use_log WARN "container $container_id health state is $health_status (failing_streak=$failing_streak)"
return 1
;;
esac
;;
125)
case $healthcheck_output in
*"no defined healthcheck"*|*"no healthcheck"*)
use_log INFO "container $container_id skipped: no healthcheck defined"
return 0
;;
*"not running"*|*"unable to find"*|*"no such container"*)
use_log INFO "container $container_id skipped: no longer running"
return 0
;;
*)
if [ -n "$healthcheck_output" ]; then
use_log ERROR "container $container_id error: $healthcheck_output"
else
use_log ERROR "container $container_id error (podman returned 125)"
fi
return 1
;;
esac
;;
*)
if [ -n "$healthcheck_output" ]; then
use_log ERROR "container $container_id unexpected exit $healthcheck_rc: $healthcheck_output"
else
use_log ERROR "container $container_id unexpected exit $healthcheck_rc"
fi
return 1
;;
esac
}
use_check_once() {
local jobs=$(get_parallel_jobs) now= container_ids= rc= total_count= due_ids= due_count= xargs_rc= checked_at= running_ids=
now=$(date +%s 2>/dev/null || printf '%s\n' 0)
if ! is_positive_integer "$now"; then
now=0
fi
container_ids=$(get_running_container_ids)
rc=$?
if [ "$rc" -ne 0 ]; then
use_log ERROR "failed to list running containers"
return 1
fi
if [ -z "$container_ids" ]; then
set_statefile_entries "$now" "" "" || return 1
use_log INFO "no running containers found"
return 0
fi
total_count=$(printf '%s\n' "$container_ids" | get_line_count)
due_ids=$(get_due_container_ids "$now" "$container_ids")
if [ -z "$due_ids" ]; then
set_statefile_entries "$now" "$container_ids" "" || return 1
use_log INFO "no container healthchecks due (${total_count} running)"
return 0
fi
due_count=$(printf '%s\n' "$due_ids" | get_line_count)
use_log INFO "running healthchecks for ${due_count} due container(s) out of ${total_count} running with ${jobs} parallel worker(s)"
printf '%s\n' "$due_ids" | xargs -n 1 -P "$jobs" "$0" use_worker
xargs_rc=$?
checked_at=$(date +%s 2>/dev/null || printf '%s\n' "$now")
if ! is_positive_integer "$checked_at"; then
checked_at=$now
fi
running_ids=$(get_running_container_ids)
rc=$?
if [ "$rc" -ne 0 ]; then
use_log ERROR "failed to refresh running containers"
return 1
fi
set_statefile_entries "$checked_at" "$running_ids" "$due_ids" || return 1
if [ "$xargs_rc" -ne 0 ]; then
case $xargs_rc in
123)
use_log ERROR "one or more container healthchecks failed"
;;
*)
use_log ERROR "healthcheck sweep failed (xargs=$xargs_rc)"
;;
esac
return 1
fi
use_log INFO "healthcheck sweep complete for ${due_count} container(s)"
return 0
}
use_daemon_loop() {
use_log INFO "starting podman healthcheck loop (interval=${INTERVAL}s statefile=$STATEFILE)"
while :
do
if ! use_check_once; then
use_log WARN "healthcheck sweep reported failures"
fi
sleep "$INTERVAL" || return 1
done
}
entrypoint() {
local command_name=${1:-check}
case $command_name in
check)
use_config_check || exit 1
use_check_once
;;
daemon)
use_config_check || exit 1
use_daemon_loop
;;
use_worker)
shift
if [ $# -ne 1 ]; then
use_log ERROR "use_worker requires exactly one container ID"
exit 2
fi
use_worker "$1"
;;
help|-h|--help)
use_usage
;;
*)
use_usage >&2
exit 1
;;
esac
}
entrypoint "$@"
#!/sbin/openrc-run
command_user="<CHANGE_ME_TO_USER>"
name="zz<CHANGE_ME_TO_PROJECT_NAME>"
output_log="/var/log/${name}.log"
error_log="/var/log/${name}.err"
# PID file required by start-stop-daemon for tracking, must be in a root-owned directory
PIDFILE="/run/${name}.pid"
WORKING_DIR="<CHANGE_ME_TO_WORKING_DIR>"
COMMAND="/usr/bin/podman-compose"
COMMAND_ARGS="up --remove-orphans"
STOP_COMMAND_ARGS="down"
depend() {
need localmount net cgroups
}
start_pre() {
if [ ! -d "${WORKING_DIR}" ]; then
eerror "WORKING_DIR=${WORKING_DIR} not found for user ${command_user}. Is directory available?"
return 1
fi
cd "${WORKING_DIR}"
if [ -f "${PIDFILE}" ]; then
elog "Found an ungratefully exited PID file! Trying to clean-up running process."
su -c "${COMMAND} ${STOP_COMMAND_ARGS}" - "${command_user}"
fi
# Ensure log files exist and have correct permissions
checkpath -f --mode 0640 --owner "${command_user}:${command_user}" "${output_log}" "${error_log}"
}
stop_pre() {
cd "${WORKING_DIR}"
}
start() {
ebegin "Starting ${name} in ${WORKING_DIR}"
start-stop-daemon --start --quiet --background --make-pidfile --pidfile "${PIDFILE}" \
--chuid "${command_user}" --chdir "${WORKING_DIR}" \
--stdout "${output_log}" --stderr "${error_log}" \
--exec "${COMMAND}" -- ${COMMAND_ARGS}
eend $?
}
stop() {
ebegin "Stopping ${name}"
start-stop-daemon --stop --quiet --pidfile "${PIDFILE}" \
--chuid "${command_user}" --chdir "${WORKING_DIR}" \
--signal INT
if [ -f "${PIDFILE}" ]; then
# Clean up the PID file after stop
rm "${PIDFILE}"
fi
eend $?
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment