forgejo: restart the db service on a clean exit, not just on failure
All checks were successful
PR build (required check) / changes (pull_request) Successful in 5s
secrets-guard / encrypted (pull_request) Successful in 4s
PR build (required check) / build-backend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 6s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 1s

forgejo_db has been at 0/1 replicas since 2026-07-29 01:24 UTC. Postgres hit
an invalid data-directory lock file ("could not open file postmaster.pid ...
performing immediate shutdown because data directory lock file is invalid")
and exited 0. With restart_policy.condition=on-failure, Swarm read the zero
status as successful completion, marked the task Complete, and never
rescheduled it.

The forgejo service itself stayed Up and kept serving its homepage, so the
outage presented as every repository page, the whole API and all CI returning
500 with "dial tcp: lookup db on 127.0.0.11:53: no such host" — including the
auth path, which is why API calls reported "user does not exist [uid: 0]"
rather than a database error.

on-failure cannot distinguish "finished successfully" from "shut itself down
and should be restarted", and Postgres exits 0 on several such paths, so it is
the wrong policy for an always-on stateful service.

This is the durable fix; it does not restart the currently stopped task.
This commit is contained in:
Emi Griffith 2026-07-29 21:42:01 -07:00
parent 6bd07d33cb
commit 7878763b66

View file

@ -53,7 +53,19 @@ services:
cpus: "${FORGEJO_DB_CPUS:-1}"
memory: ${FORGEJO_DB_MEMORY:-1g}
restart_policy:
condition: on-failure
# `any`, NOT `on-failure`. This took Forgejo down for 27 hours on 2026-07-29:
# Postgres hit an invalid data-directory lock file ("could not open file
# postmaster.pid ... performing immediate shutdown") and exited **0**. A clean
# exit is not a failure, so Swarm considered the task Complete, dropped the
# service to 0/1 replicas, and never rescheduled it. Forgejo itself stayed Up
# and served its homepage while every repo page, the API and all CI returned
# 500 with `dial tcp: lookup db ... no such host`.
#
# `on-failure` is the wrong policy for any always-on stateful service: it
# cannot distinguish "finished successfully" from "shut itself down and should
# be restarted", and Postgres does the latter with status 0 on several paths.
condition: any
delay: 5s
forgejo:
image: codeberg.org/forgejo/forgejo:9-rootless