Files
home/Projects/kompose/docs/content/3.guide/hooks.md
2025-10-11 10:24:25 +02:00

12 KiB
Executable File

title, description
title description
Hooks System Extend Kompose with custom hooks

Extend Kompose functionality with custom hooks for each stack. Hooks allow you to run custom logic before and after Docker Compose commands and database operations.

Hook Types

Database Hooks

Hook Timing Arguments Use Case
hook_pre_db_export Before DB export None Prepare data, export schemas
hook_post_db_export After DB export $1 = dump file path Cleanup, notifications
hook_pre_db_import Before DB import $1 = dump file path Prepare environment, schema setup
hook_post_db_import After DB import $1 = dump file path Restart services, clear caches

Docker Compose Command Hooks

All Docker Compose commands support pre and post hooks. The hooks receive the full command arguments.

Hook Timing Arguments Use Case
hook_pre_up Before docker compose up $@ = command args Pre-flight checks, setup
hook_post_up After docker compose up $@ = command args Health checks, initialization
hook_pre_down Before docker compose down $@ = command args Graceful shutdown, backups
hook_post_down After docker compose down $@ = command args Cleanup, notifications
hook_pre_start Before docker compose start $@ = command args Validate state
hook_post_start After docker compose start $@ = command args Verify services
hook_pre_stop Before docker compose stop $@ = command args Save state
hook_post_stop After docker compose stop $@ = command args Cleanup
hook_pre_restart Before docker compose restart $@ = command args Prepare for restart
hook_post_restart After docker compose restart $@ = command args Verify restart
hook_pre_build Before docker compose build $@ = command args Code generation
hook_post_build After docker compose build $@ = command args Tag images, push to registry
hook_pre_pull Before docker compose pull $@ = command args Check registry availability
hook_post_pull After docker compose pull $@ = command args Verify images
hook_pre_logs Before docker compose logs $@ = command args Setup log filters
hook_post_logs After docker compose logs $@ = command args Process logs

Also supported: ps, exec, run, create, kill, pause, unpause, port, top

::alert{type="info"} Note: Post-command hooks only execute if the Docker Compose command succeeds (exit code 0). ::

Creating Hooks

Create <stack>/hooks.sh in your stack directory:

#!/usr/bin/env bash

# Hook naming convention:
# - hook_pre_<command>  - runs before the command
# - hook_post_<command> - runs after the command (only on success)

# Example: Setup before starting containers
hook_pre_up() {
    local -a args=("$@")
    echo "  Running pre-flight checks..."
    
    # Check if required directories exist
    if [[ ! -d "./uploads" ]]; then
        echo "  Creating uploads directory..."
        mkdir -p ./uploads
    fi
    
    return 0  # 0 = success, 1 = failure
}

# Example: Verify services after startup
hook_post_up() {
    local -a args=("$@")
    echo "  Waiting for services to be healthy..."
    
    # Wait for health check
    sleep 5
    
    if docker compose ps | grep -q "healthy"; then
        echo "  ✓ Services are healthy"
        return 0
    else
        echo "  ⚠ Warning: Some services may not be healthy"
        return 0  # Non-critical, don't fail
    fi
}

# Example: Backup before shutdown
hook_pre_down() {
    echo "  Creating backup before shutdown..."
    ./backup.sh
    return 0
}

# Example: Restart dependent services
hook_post_restart() {
    echo "  Restarting dependent services..."
    # Restart a related service that depends on this stack
    return 0
}

Real-World Examples

Example 1: Directus Schema Management (sexy stack)

The sexy stack uses hooks for Directus schema synchronization:

#!/usr/bin/env bash

# Export Directus schema before database export
hook_pre_db_export() {
    local timestamp=$(date +%Y%m%d_%H%M%S)
    local snapshot_file="directus_schema_${timestamp}.yaml"
    
    echo "  Exporting Directus schema snapshot..."
    
    if docker exec sexy_api npx directus schema snapshot "$snapshot_file" > /dev/null 2>&1; then
        echo "  Schema snapshot saved: $snapshot_file"
        return 0
    else
        echo "  Warning: Could not export schema snapshot"
        return 0  # Don't fail the entire export
    fi
}

# Import Directus schema before database import
hook_pre_db_import() {
    local dump_file="$1"
    
    # Find most recent schema snapshot
    local snapshot_file=$(ls -t directus_schema_*.yaml 2>/dev/null | head -1)
    
    if [[ -z "$snapshot_file" ]]; then
        echo "  No schema snapshot found, skipping"
        return 0
    fi
    
    echo "  Applying Directus schema from: $snapshot_file"
    
    if docker exec sexy_api npx directus schema apply "$snapshot_file" > /dev/null 2>&1; then
        echo "  Schema applied successfully"
        return 0
    else
        echo "  Warning: Could not apply schema snapshot"
        return 0
    fi
}

# Reload Directus after import
hook_post_db_import() {
    echo "  Restarting Directus to apply changes..."
    docker restart sexy_api > /dev/null 2>&1
    return 0
}

Example 2: Health Monitoring

#!/usr/bin/env bash

# Send notification when services start
hook_post_up() {
    local -a args=("$@")
    
    echo "  Notifying monitoring system..."
    
    # Example: Send webhook
    curl -X POST https://monitoring.example.com/webhook \
        -H "Content-Type: application/json" \
        -d "{\"stack\": \"${stack}\", \"status\": \"up\"}" \
        2>/dev/null
    
    return 0
}

# Alert on shutdown
hook_post_down() {
    echo "  Sending shutdown notification..."
    
    curl -X POST https://monitoring.example.com/webhook \
        -H "Content-Type: application/json" \
        -d "{\"stack\": \"${stack}\", \"status\": \"down\"}" \
        2>/dev/null
    
    return 0
}

Example 3: Automated Backups

#!/usr/bin/env bash

# Backup volumes before rebuild
hook_pre_build() {
    echo "  Backing up volumes before rebuild..."
    
    local backup_dir="./backups/$(date +%Y%m%d_%H%M%S)"
    mkdir -p "$backup_dir"
    
    # Backup volumes
    docker run --rm \
        -v app_data:/data \
        -v "$backup_dir:/backup" \
        alpine tar czf /backup/data.tar.gz -C /data .
    
    echo "  Backup saved to: $backup_dir"
    return 0
}

# Tag and push after successful build
hook_post_build() {
    echo "  Tagging and pushing image..."
    
    local image="${COMPOSE_PROJECT_NAME}_app"
    local tag="$(date +%Y%m%d-%H%M%S)"
    
    docker tag "$image:latest" "$image:$tag"
    docker push "$image:$tag"
    
    return 0
}

Example 4: Cache Warming

#!/usr/bin/env bash

# Warm caches after starting
hook_post_up() {
    echo "  Warming application caches..."
    
    # Wait for app to be ready
    sleep 10
    
    # Hit cache endpoints
    curl -s http://localhost:3000/api/cache/warm > /dev/null
    
    return 0
}

# Clear caches before restart
hook_pre_restart() {
    echo "  Clearing application caches..."
    
    docker exec app_container redis-cli FLUSHALL
    
    return 0
}

Hook Execution Flow

Database Export Flow

1. hook_pre_db_export
2. pg_dump (database export)
3. hook_post_db_export (receives dump file path)

Database Import Flow

1. hook_pre_db_import (receives dump file path)
2. Database drop & recreate
3. psql (database import)
4. hook_post_db_import (receives dump file path)

Docker Compose Command Flow

1. hook_pre_<command> (receives command args)
2. docker compose <command>
3. hook_post_<command> (only if command succeeds, receives command args)

Testing Hooks

# Preview hook execution with dry-run
./kompose.sh sexy db:export --dry-run
./kompose.sh sexy up -d --dry-run

# Execute with hooks
./kompose.sh sexy db:export
./kompose.sh sexy up -d

# Test specific commands with hooks
./kompose.sh news restart
./kompose.sh blog build
./kompose.sh "*" down

Accessing Command Arguments

Hooks receive the full command and its arguments:

hook_pre_up() {
    local -a args=("$@")
    
    # Access specific arguments
    echo "  Command: ${args[0]}"          # "up"
    echo "  All args: ${args[*]}"         # "up -d --build"
    
    # Check for specific flags
    if [[ " ${args[*]} " =~ " --build " ]]; then
        echo "  Build flag detected, running pre-build tasks..."
    fi
    
    return 0
}

Hook Best Practices

DO:

  • Return proper exit codes: 0 for success, 1 for failure
  • Use indented output: echo " Message" for better readability
  • Make non-critical operations non-blocking: Return 0 even on minor failures
  • Check container status before using docker exec
  • Test in dry-run mode first: ./kompose.sh stack command --dry-run
  • Use environment variables available from .env files
  • Add timeouts for long-running operations
  • Document your hooks with comments

DON'T:

  • Assume containers are running before executing commands
  • Use blocking operations without timeouts
  • Forget error handling for external commands
  • Hardcode paths, credentials, or hostnames
  • Perform destructive operations in pre_* hooks without confirmation
  • Make hooks too complex - consider separate scripts if needed
  • Ignore exit codes from important operations

Available Environment Variables

Hooks have access to all environment variables from:

  • Root .env file
  • Stack-specific .env file
  • CLI overrides (-e KEY=VALUE)

Common variables:

$SCRIPT_DIR          # Root kompose directory
$stack              # Current stack name
$COMPOSE_PROJECT_NAME
$DB_NAME
$DB_HOST
$DB_PORT
$DB_USER
$DB_PASSWORD
# ... all variables from .env files

Troubleshooting

Hook not executing

  1. Verify hooks.sh has execute permissions: chmod +x <stack>/hooks.sh
  2. Check function naming: hook_pre_<command> or hook_post_<command>
  3. Test with dry-run mode to see if hooks are detected
  4. Check for bash syntax errors: bash -n <stack>/hooks.sh

Hook failing

  1. Add set -x at the top of your hook for debugging
  2. Check return codes: return 0 for success
  3. Verify container names and states
  4. Check environment variables are loaded
  5. Look at Kompose output for error messages

Best debugging approach

# Enable dry-run to see what would execute
./kompose.sh stack command --dry-run

# Add debug output in your hook
hook_pre_up() {
    echo "  [DEBUG] Stack: $stack"
    echo "  [DEBUG] Args: $*"
    echo "  [DEBUG] Container: $(docker ps --filter name=$stack)"
    # ... your hook logic
    return 0
}

Advanced Patterns

Conditional Execution

hook_pre_up() {
    # Only run in production
    if [[ "${ENVIRONMENT}" == "production" ]]; then
        echo "  Running production pre-flight checks..."
        ./production-checks.sh
    fi
    return 0
}

Parallel Hook Execution

hook_post_up() {
    echo "  Running parallel initialization tasks..."
    
    # Background jobs
    (cache_warm) &
    (index_rebuild) &
    
    # Wait for all background jobs
    wait
    
    return 0
}

Hook Chaining

hook_post_restart() {
    # Call another stack's command
    echo "  Restarting dependent services..."
    "${SCRIPT_DIR}/kompose.sh" dependent-stack restart
    return 0
}

Security Considerations

  • Never log sensitive information (passwords, tokens)
  • Validate user input in hooks that accept parameters
  • Use quotes around variables to prevent injection
  • Restrict file permissions on hooks.sh: chmod 750 hooks.sh
  • Avoid executing user-supplied data without validation
  • Use absolute paths or $SCRIPT_DIR for file references