Skip to content
Lifecycle Hooks

Lifecycle Hooks

Watchtower’s lifecycle hooks are a feature that allows monitored containers to execute custom commands at specific points during the container update process. These hooks leverage Docker’s exec API to run commands inside containers.

Overview

Lifecycle hooks enable containers to perform custom actions such as:

  • Graceful shutdown procedures
  • Database backups or migrations
  • Configuration validation
  • Notification systems
  • Cleanup operations

Hook Types

Watchtower supports four distinct lifecycle hook types that execute at different stages of the update process:

Hook TypeDescriptionExecution Timing
Pre-checkExecuted for each filtered container before the update cycle beginsPer container, before scanning containers
Pre-updateExecuted before stopping the old containerPer container, immediately before stopping
Post-updateExecuted after starting the new containerPer container, immediately after starting
Post-checkExecuted for each filtered container after the update cycle completesPer container, after all updates

Configuration

Enabling Lifecycle Hooks

Lifecycle hooks are disabled by default. Enable them by using the following on the Watchtower container:

--enable-lifecycle-hooks

Defining Hook Commands

Hook commands are defined using Docker labels on the containers being monitored by Watchtower:

These commands require all necessary tooling (i.e. sh, jq, etc.) to be installed in the monitored container.
LABEL com.centurylinklabs.watchtower.lifecycle.pre-check="echo 'Starting update cycle'"
LABEL com.centurylinklabs.watchtower.lifecycle.pre-update="echo 'Preparing container for update'"
LABEL com.centurylinklabs.watchtower.lifecycle.post-update="echo 'Container updated successfully'"
LABEL com.centurylinklabs.watchtower.lifecycle.post-check="echo 'Update cycle completed'"
If the container is not running, lifecycle hooks (including pre-update hooks) cannot run, as the stop phase is skipped, and the update proceeds directly to removal (if applicable) or completion.

Advanced Configuration

Custom Timeouts

By default, hook commands timeout after 1 minute. Override this with timeout labels:

LABEL com.centurylinklabs.watchtower.lifecycle.pre-check-timeout="5"
LABEL com.centurylinklabs.watchtower.lifecycle.pre-check-timeout="0"

Custom User Execution

By default, hooks run as the monitored container’s configured user and group (uid:gid).

Both global and individual, container-specific uid/gid configurations are supported.

Container labels take precedence over global flags/variables.
--lifecycle-uid 1000
--lifecycle-gid 1000

Execution Details

Docker API Integration

Lifecycle hooks utilize Docker’s exec API through the following sequence:

  1. Exec Creation: ContainerExecCreate creates an exec instance with the specified command
  2. Exec Start: ContainerExecStart begins execution of the command
  3. Output Capture: ContainerExecAttach captures stdout/stderr output
  4. Status Monitoring: ContainerExecInspect monitors execution status and exit codes

Environment Variables

Lifecycle hook commands receive container metadata via the WT_CONTAINER environment variable containing a JSON object with the following fields:

FieldTypeDescriptionExample
namestringContainer name (may include leading /)"/my-app" or "my-app"
idstringFull container ID"abc123def456..."
image_namestringContainer image name with tag"nginx:latest"
stop_signalstringContainer’s configured stop signal"SIGTERM"
labelsobjectWatchtower-specific labels only{"com.centurylinklabs.watchtower.lifecycle.pre-update": "backup.sh"}
The labels object contains only Watchtower-specific labels (those starting with com.centurylinklabs.watchtower.) to keep the JSON payload small and focused on Watchtower configuration.

Usage Examples

#!/bin/bash
CONTAINER_NAME=$(echo $WT_CONTAINER | jq -r '.name')
echo "Processing container: $CONTAINER_NAME"

Execution Flow

Complete Update Cycle

    flowchart TD
    START([START])
    START --> PRECHECK
    PRECHECK[Pre-check hook]
    PRECHECK --> SCAN
    SCAN[Scan for updates<br/>Check images<br/>Find stale containers]
    SCAN --> DECISION{Stale containers<br/>found?}
    DECISION -->|No| POSTCHECK
    DECISION -->|Yes| LOOPSTART
    LOOPSTART[For each stale container]
    LOOPSTART --> PREUPDATE
    PREUPDATE[Pre-update hook]
    PREUPDATE --> STOP[Stop old container]
    STOP --> STARTNEW[Start new container]
    STARTNEW --> POSTUPDATE[Post-update hook]
    POSTUPDATE --> LOOPEND{All containers<br/>processed?}
    LOOPEND -->|No| LOOPSTART
    LOOPEND -->|Yes| POSTCHECK
    POSTCHECK[Post-check hook]
    POSTCHECK --> END([END])

    classDef hook fill:#406170,stroke:#000,stroke-width:2px;
    classDef action fill:#003343,stroke:#000,stroke-width:2px;
    classDef decision fill:#003343,stroke:#000,stroke-width:2px;

    class PRECHECK,PREUPDATE,POSTUPDATE,POSTCHECK hook
    class SCAN,STOP,STARTNEW,LOOPSTART action
    class DECISION,LOOPEND decision
  

Hook Execution Conditions

Hook TypeScopeTimingConditionsContainer State
Pre-checkPer filtered containerBeginning of update cycleCommand defined + hooks enabledIgnored
Pre-updateIndividual containers being updatedImmediately before stoppingCommand defined + hooks enabled + container running + not restartingMust be running and not restarting
Post-updateIndividual containers successfully updatedImmediately after starting new containerCommand defined + hooks enabled + update successfulNew container running
Post-checkPer filtered containerEnd of update cycleCommand defined + hooks enabledIgnored

Exit Code Handling

Hook execution results are evaluated based on exit codes, with different behaviors per hook type:

Failures are logged but ignored; update process continues

Practical Examples

FROM postgres:13

# Pre-update: Create backup before stopping
LABEL com.centurylinklabs.watchtower.lifecycle.pre-update="/usr/local/bin/backup.sh"
LABEL com.centurylinklabs.watchtower.lifecycle.pre-update-timeout="10"

# Post-update: Run migrations after starting new version
LABEL com.centurylinklabs.watchtower.lifecycle.post-update="/usr/local/bin/migrate.sh"
LABEL com.centurylinklabs.watchtower.lifecycle.post-update-timeout="15"

COPY backup.sh migrate.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/backup.sh /usr/local/bin/migrate.sh

Synology DSM Graceful Shutdown

This is an example implementation that requires additional testing and validation.

There is a well-known issue with Synology devices sending warning notifications when containers are stopped by anything other than the Synology’s Docker service. This can be problematic when using tools like Watchtower that stop and restart containers.

The examples/lifecycle-hooks/synology-stop directory provides examples for implementing graceful shutdowns using Synology’s DSM Web API. This includes both shell script and Go implementations that authenticate with DSM, stop containers gracefully, and handle session management.

See the synology-stop README for detailed setup instructions, environment variables, and deployment examples using docker-compose.

Troubleshooting

Common Issues

Hook Commands Not Executing

  • Verify --enable-lifecycle-hooks / WATCHTOWER_LIFECYCLE_HOOKS=true is set
  • Check that labels are correctly formatted
  • Ensure container is running (for pre-update hooks)

Timeout Errors

  • Increase timeout values using timeout labels
  • Set timeout to “0” to disable timeouts
  • Check command execution time

Permission Issues

  • Use appropriate UID/GID labels
  • Ensure user has permissions to execute commands
  • Check container’s user configuration

Exit Code Confusion

  • Exit code 0: Success, continue
  • Exit code 75: Skip this container update
  • Other codes: Fail the entire update process

Debugging

Enable debug logging to see hook execution details:

watchtower --debug --enable-lifecycle-hooks

Look for log messages containing:

  • “Executing pre-check command”
  • “Executing pre-update command”
  • “Command output captured”
  • “Command execution failed”
Last updated on