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 Type Description Execution Timing
Pre-check Executed for each filtered container before the update cycle begins Per container, before scanning containers
Pre-update Executed before stopping the old container Per container, immediately before stopping
Post-update Executed after starting the new container Per container, immediately after starting
Post-check Executed for each filtered container after the update cycle completes Per 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:

Field Type Description Example
name string Container name (may include leading /) "/my-app" or "my-app"
id string Full container ID "abc123def456..."
image_name string Container image name with tag "nginx:latest"
stop_signal string Container’s configured stop signal "SIGTERM"
labels object Watchtower-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 Type Scope Timing Conditions Container State
Pre-check Per filtered container Beginning of update cycle Command defined + hooks enabled Ignored
Pre-update Individual containers being updated Immediately before stopping Command defined + hooks enabled + container running + not restarting Must be running and not restarting
Post-update Individual containers successfully updated Immediately after starting new container Command defined + hooks enabled + update successful New container running
Post-check Per filtered container End of update cycle Command defined + hooks enabled Ignored

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