feat: kompose hooks
This commit is contained in:
@@ -3,9 +3,11 @@ title: Hooks System
|
||||
description: Extend Kompose with custom hooks
|
||||
---
|
||||
|
||||
Extend Kompose functionality with custom hooks for each stack.
|
||||
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.
|
||||
|
||||
### Available Hooks
|
||||
## Hook Types
|
||||
|
||||
### Database Hooks
|
||||
|
||||
| Hook | Timing | Arguments | Use Case |
|
||||
|------|--------|-----------|----------|
|
||||
@@ -14,76 +16,431 @@ Extend Kompose functionality with custom hooks for each stack.
|
||||
| `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 |
|
||||
|
||||
### Creating Hooks
|
||||
### Docker Compose Command Hooks
|
||||
|
||||
Create `<stack>/hooks.sh`:
|
||||
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:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Export schema before database export
|
||||
hook_pre_db_export() {
|
||||
echo " Exporting application schema..."
|
||||
docker exec sexy_api npx directus schema snapshot --yes ./schema.yaml
|
||||
# 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
|
||||
}
|
||||
|
||||
# Apply schema before database import
|
||||
hook_pre_db_import() {
|
||||
local dump_file="$1"
|
||||
echo " Applying schema snapshot..."
|
||||
docker exec sexy_api npx directus schema apply --yes ./schema.yaml
|
||||
# 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
|
||||
}
|
||||
|
||||
# Restart service after import
|
||||
hook_post_db_import() {
|
||||
local dump_file="$1"
|
||||
echo " Restarting application..."
|
||||
docker restart sexy_api
|
||||
# Example: Restart dependent services
|
||||
hook_post_restart() {
|
||||
echo " Restarting dependent services..."
|
||||
# Restart a related service that depends on this stack
|
||||
return 0
|
||||
}
|
||||
```
|
||||
|
||||
### Real-World Example: Directus (sexy stack)
|
||||
## Real-World Examples
|
||||
|
||||
The `sexy` stack uses hooks for Directus schema management:
|
||||
### Example 1: Directus Schema Management (sexy stack)
|
||||
|
||||
**Export Flow:**
|
||||
1. `pre_db_export`: Export Directus schema snapshot
|
||||
2. Database export creates SQL dump
|
||||
3. Result: Both database dump + schema snapshot
|
||||
|
||||
**Import Flow:**
|
||||
1. `pre_db_import`: Apply Directus schema from snapshot
|
||||
2. Database import loads SQL dump
|
||||
3. `post_db_import`: Restart Directus container
|
||||
4. Result: Fully synchronized schema + data
|
||||
|
||||
### Testing Hooks
|
||||
The `sexy` stack uses hooks for Directus schema synchronization:
|
||||
|
||||
```bash
|
||||
# Preview hook execution
|
||||
#!/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
|
||||
|
||||
```bash
|
||||
#!/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
|
||||
|
||||
```bash
|
||||
#!/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
|
||||
|
||||
```bash
|
||||
#!/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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
# Import with hooks
|
||||
./kompose.sh sexy db:import
|
||||
# Test specific commands with hooks
|
||||
./kompose.sh news restart
|
||||
./kompose.sh blog build
|
||||
./kompose.sh "*" down
|
||||
```
|
||||
|
||||
### Hook Best Practices
|
||||
## Accessing Command Arguments
|
||||
|
||||
✅ **DO:**
|
||||
- Return 0 for success, 1 for failure
|
||||
- Use indented output: `echo " Message"`
|
||||
- Make non-critical operations return 0
|
||||
- Check container status before `docker exec`
|
||||
- Test in dry-run mode first
|
||||
Hooks receive the full command and its arguments:
|
||||
|
||||
❌ **DON'T:**
|
||||
- Assume containers are running
|
||||
- Use blocking operations without timeouts
|
||||
- Forget error handling
|
||||
- Hardcode paths or credentials
|
||||
```bash
|
||||
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:
|
||||
```bash
|
||||
$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
|
||||
```bash
|
||||
# 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
|
||||
```bash
|
||||
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
|
||||
```bash
|
||||
hook_post_up() {
|
||||
echo " Running parallel initialization tasks..."
|
||||
|
||||
# Background jobs
|
||||
(cache_warm) &
|
||||
(index_rebuild) &
|
||||
|
||||
# Wait for all background jobs
|
||||
wait
|
||||
|
||||
return 0
|
||||
}
|
||||
```
|
||||
|
||||
### Hook Chaining
|
||||
```bash
|
||||
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
|
||||
|
||||
## Related
|
||||
|
||||
- [Database Operations](/guide/database) - Learn about database import/export
|
||||
- [Stack Management](/guide/stack-management) - Managing multiple stacks
|
||||
- [Configuration](/guide/configuration) - Environment variables and settings
|
||||
|
||||
Reference in New Issue
Block a user