Lifecycle hooks provide runtime security checkpoints that execute at critical moments during service deployment and operation. This chapter covers how to declare and use hooks effectively.
Lifecycle hooks are actions that execute automatically at specific events:
┌─────────────────────────────────────────────────────────────┐
│ SERVICE LIFECYCLE │
│ │
│ pre_deploy → Deploy → post_deploy → Running → pre_shutdown │
│ │ │ │ │ │
│ ↓ ↓ ↓ ↓ │
│ validate health_check monitoring drain │
│ warm_cache cleanup │
└─────────────────────────────────────────────────────────────┘
Use declare_lifecycle_hook/3 to register hooks:
:- declare_lifecycle_hook(ServiceName, Event, Action).
Example:
% Register hooks for api_service
:- declare_lifecycle_hook(api_service, pre_deploy, health_check).
:- declare_lifecycle_hook(api_service, post_deploy, warm_cache).
:- declare_lifecycle_hook(api_service, pre_shutdown, drain_connections).
:- declare_lifecycle_hook(api_service, on_health_failure, custom('alert.sh')).
Executes before deployment begins:
:- declare_lifecycle_hook(my_service, pre_deploy, custom('validate.sh')).
Use cases:
Executes after deployment completes:
:- declare_lifecycle_hook(my_service, post_deploy, health_check).
:- declare_lifecycle_hook(my_service, post_deploy, warm_cache).
Use cases:
Executes before stopping the service:
:- declare_lifecycle_hook(my_service, pre_shutdown, drain_connections).
:- declare_lifecycle_hook(my_service, pre_shutdown, save_state).
Use cases:
Executes after service stops:
:- declare_lifecycle_hook(my_service, post_shutdown, custom('cleanup.sh')).
Use cases:
Executes when health check fails:
:- declare_lifecycle_hook(my_service, on_health_failure, custom('alert.sh')).
Use cases:
Wait for active connections to complete:
:- declare_lifecycle_hook(api_service, pre_shutdown, drain_connections).
Generated code:
echo "Draining connections..."
ssh "deploy@host" "sleep 5" # Allow connections to drain
Verify service is responding:
:- declare_lifecycle_hook(api_service, post_deploy, health_check).
Generated code:
echo "Running health check..."
for i in {1..30}; do
if curl -sf "http://host:${PORT}/health" >/dev/null; then
echo "Health check passed"
break
fi
sleep 1
done
Pre-populate caches:
:- declare_lifecycle_hook(api_service, post_deploy, warm_cache).
Generated code:
echo "Warming cache..."
ssh "deploy@host" "curl -s http://localhost:${PORT}/warmup || true"
Persist service state before shutdown:
:- declare_lifecycle_hook(api_service, pre_shutdown, save_state).
Generated code:
echo "Saving state..."
ssh "deploy@host" "cd ${REMOTE_DIR} && ./save_state.sh || true"
Run arbitrary command:
:- declare_lifecycle_hook(api_service, post_deploy, custom('notify.sh deployed')).
:- declare_lifecycle_hook(api_service, on_health_failure, custom('pagerduty-alert.sh')).
Generated code:
echo "Running custom hook..."
ssh "deploy@host" "notify.sh deployed"
The main deployment predicate executes hooks automatically:
deploy_with_hooks(Service, Result) :-
% 1. Validate security
validate_security(Service, SecurityErrors),
(SecurityErrors \== []
-> Result = error(security_validation_failed(SecurityErrors))
% 2. Execute pre-deploy hooks
; execute_hooks(Service, pre_deploy, PreResult),
(PreResult \== ok
-> Result = error(pre_deploy_failed(PreResult))
% 3. Perform deployment
; do_deployment(Service, DeployResult),
(DeployResult \== deployed
-> Result = error(deployment_failed(DeployResult))
% 4. Execute post-deploy hooks
; execute_hooks(Service, post_deploy, PostResult),
(PostResult == ok
-> Result = deployed
; Result = deployed_with_warnings(PostResult)
)
)
)
).
Graceful shutdown with hooks:
graceful_stop(Service, Options, Result) :-
option_or_default(drain_timeout, Options, 30, DrainTimeout),
% 1. Execute pre-shutdown hooks
execute_hooks(Service, pre_shutdown, PreResult),
(PreResult == ok
-> % 2. Drain connections
drain_connections(Service, [timeout(DrainTimeout)], DrainResult),
(DrainResult == drained
-> % 3. Stop service
stop_service(Service, StopResult),
Result = StopResult
; Result = error(drain_failed(DrainResult))
)
; Result = error(pre_shutdown_hook_failed(PreResult))
).
You can declare multiple hooks for the same event:
% Multiple post-deploy hooks - executed in order
:- declare_lifecycle_hook(api_service, post_deploy, health_check).
:- declare_lifecycle_hook(api_service, post_deploy, warm_cache).
:- declare_lifecycle_hook(api_service, post_deploy, custom('notify.sh')).
Hooks execute sequentially. If any hook fails, subsequent hooks are skipped.
If a hook fails:
execute_hook_actions(Service, [Action|Rest], Result) :-
execute_single_hook(Service, Action, ActionResult),
(ActionResult == ok
-> execute_hook_actions(Service, Rest, Result)
; Result = error(hook_failed(Action, ActionResult))
).
For non-critical hooks, wrap in custom with error handling:
% Continue even if notification fails
:- declare_lifecycle_hook(api_service, post_deploy,
custom('notify.sh || true')).
?- lifecycle_hooks(api_service, Hooks).
Hooks = [
hook(pre_deploy, health_check),
hook(post_deploy, warm_cache),
hook(pre_shutdown, drain_connections)
].
?- lifecycle_hook_db(api_service, post_deploy, _).
true.
Service with comprehensive lifecycle management:
% service_setup.pl
% 1. Declare service
:- declare_service(analytics_api, [
host('analytics.example.com'),
port(8080),
target(go),
transport(https)
]).
% 2. Configure deployment
:- declare_deploy_method(analytics_api, ssh, [
user('deploy'),
remote_dir('/opt/analytics')
]).
% 3. Declare lifecycle hooks
% Pre-deploy: Validate configuration
:- declare_lifecycle_hook(analytics_api, pre_deploy,
custom('./scripts/validate-config.sh')).
% Post-deploy: Health check then warm caches
:- declare_lifecycle_hook(analytics_api, post_deploy, health_check).
:- declare_lifecycle_hook(analytics_api, post_deploy, warm_cache).
:- declare_lifecycle_hook(analytics_api, post_deploy,
custom('./scripts/notify-slack.sh deployed')).
% Pre-shutdown: Graceful drain
:- declare_lifecycle_hook(analytics_api, pre_shutdown, drain_connections).
:- declare_lifecycle_hook(analytics_api, pre_shutdown, save_state).
% Post-shutdown: Cleanup
:- declare_lifecycle_hook(analytics_api, post_shutdown,
custom('./scripts/archive-logs.sh')).
% Health failure: Alert
:- declare_lifecycle_hook(analytics_api, on_health_failure,
custom('./scripts/pagerduty-alert.sh')).
% 4. Configure error resilience
:- declare_retry_policy(analytics_api, [
max_retries(3),
retry_delay(exponential(1000, 2, 10000))
]).
:- declare_circuit_breaker(analytics_api, [
failure_threshold(5),
reset_timeout(60)
]).
% 5. Configure health checks
:- declare_health_check(analytics_api, [
endpoint('/health'),
interval(30),
timeout(5),
healthy_threshold(2),
unhealthy_threshold(3)
]).
% Deploy with full lifecycle
deploy_analytics :-
format('Deploying analytics_api...~n'),
deploy_with_hooks(analytics_api, Result),
format('Result: ~w~n', [Result]).
% Graceful shutdown
shutdown_analytics :-
format('Shutting down analytics_api...~n'),
graceful_stop(analytics_api, [drain_timeout(30)], Result),
format('Result: ~w~n', [Result]).
The hooks are compiled into the deployment script:
#!/bin/bash
set -euo pipefail
SERVICE=analytics_api
HOST=analytics.example.com
USER=deploy
REMOTE_DIR=/opt/analytics
PORT=8080
# Pre-shutdown hooks
echo "Running pre-shutdown hooks..."
echo "Draining connections..."
ssh "${USER}@${HOST}" "sleep 5"
echo "Saving state..."
ssh "${USER}@${HOST}" "cd ${REMOTE_DIR} && ./save_state.sh || true"
# Stop existing service
ssh "${USER}@${HOST}" "pkill -f 'analytics' || true"
# Deploy new version
rsync -avz --delete ./dist/ "${USER}@${HOST}:${REMOTE_DIR}/"
# Start service
ssh "${USER}@${HOST}" "cd ${REMOTE_DIR} && ./analytics -port=${PORT} &"
# Post-deploy hooks
echo "Running post-deploy hooks..."
echo "Running health check..."
for i in {1..30}; do
if curl -sf "http://${HOST}:${PORT}/health" >/dev/null; then
echo "Health check passed"
break
fi
sleep 1
done
echo "Warming cache..."
ssh "${USER}@${HOST}" "curl -s http://localhost:${PORT}/warmup || true"
echo "Running custom hook..."
ssh "${USER}@${HOST}" "./scripts/notify-slack.sh deployed"
echo "Deployment complete: ${SERVICE}"
Custom hooks execute arbitrary commands. Validate inputs:
% DANGEROUS - User input in command
:- declare_lifecycle_hook(svc, post_deploy, custom(UserInput)).
% SAFE - Predefined commands only
:- declare_lifecycle_hook(svc, post_deploy, custom('predefined-script.sh')).
Ensure hook scripts have appropriate permissions:
# Restrict hook script access
chmod 700 scripts/*.sh
chown deploy:deploy scripts/*.sh
Set timeouts to prevent hanging hooks:
:- declare_lifecycle_hook(svc, post_deploy,
custom('timeout 30 slow-script.sh')).
Lifecycle hooks provide:
Key points:
declare_lifecycle_hook/3drain_connections, health_check, warm_cache, save_statecustom(Command) for arbitrary scriptsThe next chapter covers target-specific security considerations.
| ← Previous: Chapter 2: Firewall Policies | 📖 Book 8: Security & Firewall | Next: Chapter 4: Target Security → |