Custom Initialization
Custom Initialization
Cbox provides hooks for running custom code at container startup. Use these for database migrations, waiting for services, cache warming, and dynamic configuration.
Initialization Methods
Method 1: Init Scripts Directory
Place executable scripts in /docker-entrypoint-init.d/:
FROM ghcr.io/cboxdk/php-baseimages/php-fpm-nginx:8.4-bookworm
COPY scripts/init-*.sh /docker-entrypoint-init.d/
RUN chmod +x /docker-entrypoint-init.d/*.sh
Execution order: Scripts run alphabetically by filename.
/docker-entrypoint-init.d/
01-wait-for-db.sh # Runs first
02-run-migrations.sh # Runs second
03-cache-warmup.sh # Runs third
Method 2: Environment Variables
Use built-in Laravel features:
services:
app:
image: ghcr.io/cboxdk/php-baseimages/php-fpm-nginx:8.4-bookworm
environment:
LARAVEL_MIGRATE_ENABLED: "true" # Run migrations
LARAVEL_OPTIMIZE_ENABLED: "true" # Cache config/routes
Method 3: Custom Entrypoint
For complete control:
FROM ghcr.io/cboxdk/php-baseimages/php-fpm-nginx:8.4-bookworm
COPY custom-entrypoint.sh /usr/local/bin/custom-entrypoint.sh
RUN chmod +x /usr/local/bin/custom-entrypoint.sh
ENTRYPOINT ["/usr/local/bin/custom-entrypoint.sh"]
#!/bin/bash
# custom-entrypoint.sh
# Your custom initialization
echo "Running custom initialization..."
# ... your code ...
# Call original entrypoint
exec /usr/local/bin/docker-entrypoint.sh "$@"
Common Patterns
Wait for Database
#!/bin/bash
# /docker-entrypoint-init.d/01-wait-for-db.sh
set -e
MAX_RETRIES=30
RETRY_INTERVAL=2
echo "Waiting for database..."
for i in $(seq 1 $MAX_RETRIES); do
if php artisan db:monitor --database=mysql 2>/dev/null; then
echo "Database is ready!"
exit 0
fi
echo "Attempt $i/$MAX_RETRIES: Database not ready, waiting ${RETRY_INTERVAL}s..."
sleep $RETRY_INTERVAL
done
echo "ERROR: Database not available after $MAX_RETRIES attempts"
exit 1
Wait for Redis
#!/bin/bash
# /docker-entrypoint-init.d/01-wait-for-redis.sh
set -e
REDIS_HOST="${REDIS_HOST:-redis}"
REDIS_PORT="${REDIS_PORT:-6379}"
MAX_RETRIES=30
echo "Waiting for Redis at $REDIS_HOST:$REDIS_PORT..."
for i in $(seq 1 $MAX_RETRIES); do
if nc -z "$REDIS_HOST" "$REDIS_PORT" 2>/dev/null; then
echo "Redis is ready!"
exit 0
fi
echo "Attempt $i/$MAX_RETRIES: Redis not ready..."
sleep 2
done
echo "ERROR: Redis not available"
exit 1
Run Migrations
#!/bin/bash
# /docker-entrypoint-init.d/02-run-migrations.sh
set -e
if [ -f "/var/www/html/artisan" ]; then
echo "Running database migrations..."
if [ "${APP_ENV:-production}" = "production" ]; then
php artisan migrate --force --no-interaction
else
php artisan migrate --no-interaction
fi
echo "Migrations completed!"
fi
Cache Warmup (Laravel)
#!/bin/bash
# /docker-entrypoint-init.d/03-cache-warmup.sh
set -e
if [ -f "/var/www/html/artisan" ]; then
echo "Warming up caches..."
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
echo "Cache warmup completed!"
fi
Generate App Key (If Missing)
#!/bin/bash
# /docker-entrypoint-init.d/00-generate-key.sh
set -e
if [ -f "/var/www/html/artisan" ]; then
# Check if APP_KEY is set
if [ -z "${APP_KEY}" ] || [ "${APP_KEY}" = "base64:" ]; then
echo "WARNING: APP_KEY not set, generating..."
php artisan key:generate --force
fi
fi
Fix Permissions
#!/bin/bash
# /docker-entrypoint-init.d/00-fix-permissions.sh
set -e
WORKDIR="${WORKDIR:-/var/www/html}"
echo "Fixing permissions..."
# Laravel directories
for dir in storage bootstrap/cache; do
if [ -d "$WORKDIR/$dir" ]; then
chown -R www-data:www-data "$WORKDIR/$dir"
chmod -R 775 "$WORKDIR/$dir"
fi
done
echo "Permissions fixed!"
Dynamic Configuration
#!/bin/bash
# /docker-entrypoint-init.d/01-dynamic-config.sh
set -e
# Generate config from environment
cat > /var/www/html/config/dynamic.php << EOF
<?php
return [
'api_url' => '${API_URL}',
'feature_flags' => [
'new_feature' => ${FEATURE_NEW:-false},
],
];
EOF
echo "Dynamic configuration generated!"
Download Remote Configuration
#!/bin/bash
# /docker-entrypoint-init.d/01-fetch-config.sh
set -e
CONFIG_URL="${CONFIG_URL:-}"
if [ -n "$CONFIG_URL" ]; then
echo "Fetching configuration from $CONFIG_URL..."
curl -sSL "$CONFIG_URL" -o /var/www/html/.env.remote
# Merge with existing
if [ -f /var/www/html/.env ]; then
cat /var/www/html/.env.remote >> /var/www/html/.env
fi
fi
Docker Compose Examples
Complete Laravel Setup
services:
app:
image: ghcr.io/cboxdk/php-baseimages/php-fpm-nginx:8.4-bookworm
volumes:
- .:/var/www/html
- ./docker/init:/docker-entrypoint-init.d:ro
environment:
DB_HOST: db
REDIS_HOST: redis
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
db:
image: mysql:8.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 3s
retries: 10
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 10
Init Scripts Directory
docker/
init/
01-wait-for-services.sh
02-run-migrations.sh
03-seed-database.sh
04-cache-warmup.sh
Kubernetes Patterns
Init Containers
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
initContainers:
# Wait for database
- name: wait-for-db
image: busybox:1.36
command: ['sh', '-c', 'until nc -z db 3306; do sleep 2; done']
# Run migrations
- name: migrations
image: ghcr.io/cboxdk/php-baseimages/php-cli:8.4-bookworm
command: ['php', 'artisan', 'migrate', '--force']
volumeMounts:
- name: app
mountPath: /var/www/html
containers:
- name: app
image: ghcr.io/cboxdk/php-baseimages/php-fpm-nginx:8.4-bookworm
Lifecycle Hooks
containers:
- name: app
image: ghcr.io/cboxdk/php-baseimages/php-fpm-nginx:8.4-bookworm
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "php artisan cache:clear"]
preStop:
exec:
command: ["/bin/sh", "-c", "php artisan down"]
Error Handling
Exit on Failure
#!/bin/bash
set -e # Exit on any error
# Commands that must succeed
php artisan migrate --force || exit 1
Continue on Failure
#!/bin/bash
# Don't use set -e
# Non-critical operation
php artisan cache:clear || echo "WARNING: Cache clear failed"
# Critical operation - explicit check
if ! php artisan migrate --force; then
echo "ERROR: Migration failed"
exit 1
fi
Conditional Execution
#!/bin/bash
set -e
# Only run in production
if [ "${APP_ENV}" = "production" ]; then
php artisan config:cache
php artisan route:cache
fi
# Only run if flag is set
if [ "${RUN_MIGRATIONS:-false}" = "true" ]; then
php artisan migrate --force
fi
Debugging Init Scripts
Enable Verbose Output
#!/bin/bash
set -ex # -x prints each command
echo "DEBUG: Starting initialization"
echo "DEBUG: APP_ENV=$APP_ENV"
Check Script Execution
# List init scripts
docker exec myapp ls -la /docker-entrypoint-init.d/
# Run specific script manually
docker exec myapp /docker-entrypoint-init.d/02-migrations.sh
# Check script permissions
docker exec myapp stat /docker-entrypoint-init.d/02-migrations.sh
Watch Container Logs
# Real-time logs during startup
docker logs -f myapp
# Filter for init messages
docker logs myapp 2>&1 | grep -i "init\|migration\|cache"
Best Practices
1. Use Numbered Prefixes
01-wait.sh # Dependencies first
02-migrate.sh # Database changes
03-seed.sh # Data population
04-cache.sh # Cache warming
2. Keep Scripts Idempotent
# Good: Can run multiple times safely
php artisan migrate --force
# Bad: Fails on second run
php artisan db:seed # Use --force or check first
3. Fail Fast
#!/bin/bash
set -e # Stop on first error
# Critical operations at the top
php artisan migrate --force
# Non-critical at the bottom
php artisan cache:clear || true
4. Log Everything
#!/bin/bash
echo "$(date): Starting initialization..."
echo "$(date): Environment: ${APP_ENV}"
echo "$(date): Database: ${DB_HOST}"
5. Use Health Checks
services:
app:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
start_period: 60s # Give init scripts time to complete
Next Steps
- Extending Images - Custom Dockerfiles
- Custom Extensions - Add PHP extensions
- Performance Tuning - Optimize startup time