Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Lifecycle hooks

Some apps need a hand around an update: flush a write buffer before going down, or clear a cache once the new version is up (Dolibarr, GLPI, Nextcloud). Lifecycle hooks let a container declare those commands as labels; freshdock runs them inside the container with docker exec at the right moments of the update lifecycle.

services:
  glpi:
    image: glpi/glpi:latest
    labels:
      freshdock.enable: "true"
      freshdock.mode: "nightly"
      freshdock.lifecycle.pre-update: "/opt/glpi/bin/maintenance --enable"
      freshdock.lifecycle.post-update: "php bin/console cache:clear"

Labels

LabelValueDefaultMeaning
freshdock.lifecycle.pre-updateshell command(none)Runs in the old container, after the new image is pulled and before the container is stopped. If it does not succeed, the update is skipped.
freshdock.lifecycle.pre-update-timeoutseconds60Time budget for the pre-update hook. 0 disables the timeout.
freshdock.lifecycle.post-updateshell command(none)Runs in the new container once it has passed the health gate. Best-effort: a failure is logged, the update stands.
freshdock.lifecycle.post-update-timeoutseconds60Time budget for the post-update hook. 0 disables the timeout.

Commands are executed as sh -c "<command>" inside the container, so the image must ship a sh (distroless images cannot use hooks, the same constraint as Watchtower). Hook output is logged at debug level.

Where the hooks sit in the update

inspect -> pull -> PRE-UPDATE HOOK -> stop -> rename -> create -> start
        -> health gate -> remove old container -> POST-UPDATE HOOK -> image cleanup

The pre-update hook runs after the pull so the image is already local and the downtime window stays short, and before the stop so the app is still up to answer the exec.

Pre-update: the veto

The pre-update hook is a gate. The update proceeds only when the hook exits 0. Anything else leaves the container untouched, running its old image:

Hook outcomeWhat happens
Exit code 75 (EX_TEMPFAIL, Watchtower-compatible)The polite “not now”: the app is mid-backup, mid-migration, or has active sessions. freshdock logs the deferral at info and tries again the next time the container is due.
Any other non-zero exitLogged as a warning, update skipped.
TimeoutLogged as a warning, update skipped. Docker has no way to kill an exec, so the command itself keeps running inside the container; only the verdict is decided.
Exec failure (no sh in the image, daemon error)Logged as a warning, update skipped.

The skip is not an error: freshdock recreate reports it and exits 0, and the scheduler retries on the container’s normal cadence (--interval for live, the cron for calendar modes).

A pre-update script that only allows updates outside business hours:

#!/bin/sh
hour=$(date +%H)
if [ "$hour" -ge 8 ] && [ "$hour" -lt 18 ]; then
  exit 75   # EX_TEMPFAIL: defer, try again next cycle
fi
exit 0

Post-update: best-effort maintenance

The post-update hook runs in the new container, after the health gate has passed and the archived old container has been removed. The update has already succeeded at that point, so a failing or timing-out post-update hook is logged as a warning and never fails (or rolls back) the update.

If the update rolls back, the post-update hook does not run: there was no update to follow up on.

Interaction with rollback

Hooks never weaken the safety contract:

  • A pre-update veto happens before anything is stopped, so the container is left alone.
  • Rollback is still driven purely by the health gate; hooks cannot cause or prevent a rollback.

Watchtower equivalents

The Watchtower hook labels are read directly, so a migrated container keeps working without relabelling, and its timeouts are interpreted in Watchtower’s unit (minutes). A freshdock.* label always wins when both are present.

Watchtowerfreshdock
com.centurylinklabs.watchtower.lifecycle.pre-updatefreshdock.lifecycle.pre-update
com.centurylinklabs.watchtower.lifecycle.post-updatefreshdock.lifecycle.post-update
...lifecycle.pre-update-timeout (minutes)freshdock.lifecycle.pre-update-timeout (seconds)
...lifecycle.post-update-timeout (minutes)freshdock.lifecycle.post-update-timeout (seconds)
--enable-lifecycle-hooks / WATCHTOWER_LIFECYCLE_HOOKS(not needed): setting a hook label is the opt-in
...lifecycle.pre-check / ...lifecycle.post-check(no equivalent): freshdock has no per-cycle check hooks, so these are logged and ignored

Note the timeout unit difference when translating to the native labels: Watchtower counts minutes, freshdock counts seconds. A Watchtower pre-update-timeout: "5" becomes freshdock.lifecycle.pre-update-timeout: "300".