Deployment Guide
This comprehensive guide covers deploying Glueful applications across various environments and platforms.
Quick Start Checklist
Essential checklist and tips for deploying Glueful services:
✅ Essential Configuration
- Configure environment:
.envwithAPP_ENV=production, DB/cache endpoints - Enable a real cache backend (e.g., Redis) in
config/cache.php - Harden security defaults in
config/security.php(CSP/HSTS, allowlists) - Ensure PHP OPcache is enabled; tune memory limits
- Serve via a production web server (Nginx/Apache) or PHP-FPM
- Health endpoints:
/healthz(liveness),/ready(readiness + allowlist) - Logs: configure paths/rotation; ship to your observability stack
⚡ Zero-Downtime Hints
- Warm up route cache/opcache during deploy
- Run DB migrations and queue workers with supervision
Table of Contents
- Pre-Deployment Checklist
- Environment Configuration
- Docker Deployment
- Cloud Platform Deployment
- Traditional Server Deployment
- Load Balancer Configuration
- Database Migration Strategies
- Zero-Downtime Deployment
- Monitoring and Health Checks
- Rollback Procedures
Pre-Deployment Checklist
✅ Essential Pre-Deployment Tasks
- Security hardening complete (see Security Hardening)
- Environment variables configured
- Database migrations tested
- SSL certificates obtained
- DNS records configured
- Backup strategy implemented
- Monitoring tools configured
- Load testing completed
- Rollback plan documented
Code Preparation
# Run final tests
composer test
# Security scan
php glueful security:scan
# Optimize for production
composer install --no-dev --optimize-autoloader
# Production readiness checks and cleanup
php glueful system:production --check --score
php glueful cache:clear
Environment Configuration
Production Environment Variables
# Application
APP_ENV=production
APP_DEBUG=false
API_DOCS_ENABLED=false
# Security
FORCE_HTTPS=true
CORS_ALLOWED_ORIGINS=https://yourdomain.com
DEFAULT_SECURITY_LEVEL=5
# Performance
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
# Logging
LOG_LEVEL=error
LOG_TO_DB=true
ENABLE_AUDIT=true
# Rate Limiting
IP_RATE_LIMIT_MAX=30
USER_RATE_LIMIT_MAX=500
ENDPOINT_RATE_LIMIT_MAX=15
Staging Environment
# Application
APP_ENV=staging
APP_DEBUG=false
API_DOCS_ENABLED=true
# Security (relaxed for testing)
FORCE_HTTPS=true
DEFAULT_SECURITY_LEVEL=3
# Logging (more verbose for debugging)
LOG_LEVEL=warning
LOG_TO_DB=true
ENABLE_AUDIT=true
Docker Deployment
Dockerfile
# Multi-stage build for optimized production image
FROM php:8.2-fpm-alpine AS base
# Install system dependencies
RUN apk add --no-cache \
git \
zip \
unzip \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
mysql-client \
nginx \
supervisor
# Install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
gd \
pdo \
pdo_mysql \
opcache \
bcmath
# Install Redis extension
RUN pecl install redis && docker-php-ext-enable redis
# Production build stage
FROM base AS production
# Set working directory
WORKDIR /var/www/html
# Copy composer files first for better cache utilization
COPY composer.json composer.lock ./
# Install Composer
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
# Install PHP dependencies
RUN composer install --no-dev --optimize-autoloader --no-scripts
# Copy application code
COPY . .
# Set proper permissions
RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 755 /var/www/html \
&& chmod -R 775 /var/www/html/storage
# Copy configuration files
COPY docker/nginx.conf /etc/nginx/nginx.conf
COPY docker/php.ini /usr/local/etc/php/php.ini
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# Expose port
EXPOSE 80
# Start supervisor
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
target: production
ports:
- "80:80"
- "443:443"
environment:
- APP_ENV=production
volumes:
- ./storage:/var/www/html/storage
- ./logs:/var/www/html/storage/logs
depends_on:
- database
- redis
networks:
- glueful-network
database:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: ${DB_DATABASE}
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
- ./docker/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "3306:3306"
networks:
- glueful-network
redis:
image: redis:7-alpine
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
ports:
- "6379:6379"
networks:
- glueful-network
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
- ./docker/nginx/ssl:/etc/ssl/certs
depends_on:
- app
networks:
- glueful-network
volumes:
mysql_data:
redis_data:
networks:
glueful-network:
driver: bridge
Docker Deployment Commands
# Build and deploy
docker-compose up --build -d
# Run migrations
docker-compose exec app php glueful migrate:run
# Monitor logs
docker-compose logs -f app
# Scale application
docker-compose up --scale app=3 -d
Cloud Platform Deployment
AWS Deployment
Using AWS ECS
# task-definition.json
{
"family": "glueful-app",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"executionRoleArn": "arn:aws:iam::account:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::account:role/ecsTaskRole",
"containerDefinitions": [
{
"name": "glueful-app",
"image": "your-account.dkr.ecr.region.amazonaws.com/glueful:latest",
"portMappings": [
{
"containerPort": 80,
"protocol": "tcp"
}
],
"environment": [
{
"name": "APP_ENV",
"value": "production"
}
],
"secrets": [
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:region:account:secret:glueful/db-password"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/glueful-app",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}
AWS RDS Configuration
# Create RDS instance
aws rds create-db-instance \
--db-instance-identifier glueful-production \
--db-instance-class db.t3.medium \
--engine mysql \
--engine-version 8.0.35 \
--master-username admin \
--master-user-password $(aws secretsmanager get-secret-value --secret-id prod/glueful/db --query SecretString --output text | jq -r .password) \
--allocated-storage 100 \
--storage-type gp2 \
--storage-encrypted \
--vpc-security-group-ids sg-xxxxxxxxx \
--db-subnet-group-name glueful-db-subnet-group \
--backup-retention-period 7 \
--multi-az
Google Cloud Platform (GCP)
Using Cloud Run
# cloudrun.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: glueful-app
annotations:
run.googleapis.com/ingress: all
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/maxScale: "10"
run.googleapis.com/cpu-throttling: "false"
run.googleapis.com/memory: "1Gi"
run.googleapis.com/cpu: "1"
spec:
containerConcurrency: 80
containers:
- image: gcr.io/PROJECT_ID/glueful:latest
ports:
- containerPort: 80
env:
- name: APP_ENV
value: production
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: glueful-db-password
key: password
resources:
limits:
memory: "1Gi"
cpu: "1"
Deploy to Cloud Run
# Build and push image
gcloud builds submit --tag gcr.io/PROJECT_ID/glueful
# Deploy to Cloud Run
gcloud run deploy glueful-app \
--image gcr.io/PROJECT_ID/glueful \
--platform managed \
--region us-central1 \
--allow-unauthenticated \
--memory 1Gi \
--cpu 1 \
--max-instances 10
Azure Deployment
Using Azure Container Instances
# azure-deployment.yaml
apiVersion: '2019-12-01'
location: eastus
name: glueful-app
properties:
containers:
- name: glueful-app
properties:
image: your-registry.azurecr.io/glueful:latest
resources:
requests:
cpu: 1
memoryInGb: 1
ports:
- port: 80
protocol: TCP
environmentVariables:
- name: APP_ENV
value: production
- name: DB_PASSWORD
secureValue: "$(DB_PASSWORD)"
osType: Linux
ipAddress:
type: Public
ports:
- protocol: tcp
port: 80
restartPolicy: Always
Traditional Server Deployment
Ubuntu/Debian Server Setup
# Update system
sudo apt update && sudo apt upgrade -y
# Install required packages
sudo apt install -y nginx mysql-server redis-server php8.2-fpm \
php8.2-mysql php8.2-redis php8.2-mbstring php8.2-xml \
php8.2-curl php8.2-zip php8.2-bcmath php8.2-json \
php8.2-opcache certbot python3-certbot-nginx
# Install Composer
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
# Configure PHP-FPM
sudo nano /etc/php/8.2/fpm/pool.d/glueful.conf
PHP-FPM Configuration
[glueful]
user = www-data
group = www-data
listen = /run/php/php8.2-fpm-glueful.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 1000
php_admin_value[memory_limit] = 512M
php_admin_value[upload_max_filesize] = 50M
php_admin_value[post_max_size] = 50M
php_admin_value[max_execution_time] = 300
Nginx Configuration
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
root /var/www/glueful;
index index.php;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
# Security Headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Rate Limiting
limit_req zone=api burst=50 nodelay;
limit_req_status 429;
# Glueful API routing
location / {
try_files $uri $uri/ /api/index.php?$query_string;
}
# PHP processing
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm-glueful.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
# Security
fastcgi_hide_header X-Powered-By;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
}
# Deny access to sensitive files
location ~ /\. {
deny all;
}
location ~ composer\.(json|lock)$ {
deny all;
}
location ~ \.env$ {
deny all;
}
}
Application Deployment Script
#!/bin/bash
# deploy.sh
set -e
DEPLOY_DIR="/var/www/glueful"
BACKUP_DIR="/var/backups/glueful"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
echo "Starting deployment..."
# Create backup
echo "Creating backup..."
mkdir -p $BACKUP_DIR
sudo tar -czf $BACKUP_DIR/backup_$TIMESTAMP.tar.gz -C $DEPLOY_DIR .
# Update code
echo "Updating code..."
cd $DEPLOY_DIR
git pull origin main
# Install dependencies
echo "Installing dependencies..."
composer install --no-dev --optimize-autoloader
# Run migrations
echo "Running database migrations..."
php glueful migrate:run --force
# Clear caches
echo "Clearing caches..."
php glueful cache:clear
# Set permissions
echo "Setting permissions..."
sudo chown -R www-data:www-data $DEPLOY_DIR
sudo chmod -R 755 $DEPLOY_DIR
sudo chmod -R 775 $DEPLOY_DIR/storage
# Reload services
echo "Reloading services..."
sudo systemctl reload php8.2-fpm
sudo systemctl reload nginx
# Health check
echo "Performing health check..."
if curl -f http://localhost/health > /dev/null 2>&1; then
echo "Deployment successful!"
else
echo "Health check failed! Rolling back..."
cd $DEPLOY_DIR
sudo tar -xzf $BACKUP_DIR/backup_$TIMESTAMP.tar.gz
sudo systemctl reload php8.2-fpm
exit 1
fi
Load Balancer Configuration
Nginx Load Balancer
upstream glueful_backend {
server 10.0.1.10:80 weight=3 max_fails=3 fail_timeout=30s;
server 10.0.1.11:80 weight=3 max_fails=3 fail_timeout=30s;
server 10.0.1.12:80 weight=2 max_fails=3 fail_timeout=30s backup;
}
server {
listen 80;
server_name api.yourdomain.com;
location / {
proxy_pass http://glueful_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Health check
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_connect_timeout 5s;
proxy_send_timeout 10s;
proxy_read_timeout 10s;
}
# Health check endpoint
location /health {
access_log off;
proxy_pass http://glueful_backend;
}
}
AWS Application Load Balancer
# alb-target-group.yaml
Resources:
GluefulTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: glueful-targets
Port: 80
Protocol: HTTP
VpcId: !Ref VPC
HealthCheckPath: /health
HealthCheckProtocol: HTTP
HealthCheckIntervalSeconds: 30
HealthyThresholdCount: 2
UnhealthyThresholdCount: 3
TargetType: ip
ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: glueful-alb
Type: application
Scheme: internet-facing
SecurityGroups:
- !Ref ALBSecurityGroup
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
Database Migration Strategies
Blue-Green Deployment with Database
#!/bin/bash
# blue-green-deploy.sh
BLUE_ENV="blue"
GREEN_ENV="green"
CURRENT_ENV=$(cat /var/current_env)
if [ "$CURRENT_ENV" = "$BLUE_ENV" ]; then
NEW_ENV=$GREEN_ENV
else
NEW_ENV=$BLUE_ENV
fi
echo "Deploying to $NEW_ENV environment"
# Deploy new version to standby environment
deploy_to_environment $NEW_ENV
# Run database migrations
php glueful migrate run --env=$NEW_ENV
# Run smoke tests
run_smoke_tests $NEW_ENV
# Switch traffic
switch_traffic_to $NEW_ENV
# Update current environment marker
echo $NEW_ENV > /var/current_env
echo "Deployment complete. Traffic now on $NEW_ENV"
Rolling Migrations
#!/bin/bash
# rolling-migration.sh
echo "Starting rolling migration..."
# Step 1: Deploy backward-compatible changes
php glueful migrate run --compatibility-mode
# Step 2: Deploy application updates
deploy_application_update
# Step 3: Complete migration after deployment
php glueful migrate run --complete
echo "Rolling migration complete"
Zero-Downtime Deployment
Strategy 1: Rolling Updates
#!/bin/bash
# rolling-update.sh
SERVERS=("server1" "server2" "server3")
HEALTH_CHECK_URL="http://localhost/health"
for server in "${SERVERS[@]}"; do
echo "Updating $server..."
# Remove from load balancer
remove_from_lb $server
# Wait for connections to drain
sleep 30
# Deploy update
ssh $server "cd /var/www/glueful && git pull && composer install --no-dev"
# Health check
if curl -f $HEALTH_CHECK_URL; then
# Add back to load balancer
add_to_lb $server
echo "$server updated successfully"
else
echo "Health check failed for $server"
exit 1
fi
# Wait before next server
sleep 10
done
Strategy 2: Canary Deployment
#!/bin/bash
# canary-deploy.sh
echo "Starting canary deployment..."
# Deploy to canary servers (10% of traffic)
deploy_to_canary
# Monitor metrics for 10 minutes
monitor_canary_metrics 600
# If metrics are good, proceed with full deployment
if [ $? -eq 0 ]; then
echo "Canary metrics look good, proceeding with full deployment"
deploy_to_all_servers
else
echo "Canary metrics failed, rolling back"
rollback_canary
exit 1
fi
Monitoring and Health Checks
Application Health Check
// api/Controllers/HealthController.php
public function comprehensive(): array
{
$checks = [
'database' => $this->checkDatabase(),
'redis' => $this->checkRedis(),
'storage' => $this->checkStorage(),
'external_apis' => $this->checkExternalAPIs(),
'memory' => $this->checkMemoryUsage(),
'disk_space' => $this->checkDiskSpace()
];
$allHealthy = !in_array(false, array_column($checks, 'healthy'));
return [
'status' => $allHealthy ? 'healthy' : 'unhealthy',
'timestamp' => date('c'),
'checks' => $checks,
'version' => config('app.version')
];
}
Monitoring Configuration
# docker-compose.monitoring.yml
version: '3.8'
services:
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
alertmanager:
image: prom/alertmanager
ports:
- "9093:9093"
volumes:
- ./monitoring/alertmanager.yml:/etc/alertmanager/alertmanager.yml
volumes:
grafana_data:
Rollback Procedures
Automated Rollback Script
#!/bin/bash
# rollback.sh
ROLLBACK_VERSION=${1:-previous}
DEPLOY_DIR="/var/www/glueful"
BACKUP_DIR="/var/backups/glueful"
echo "Rolling back to version: $ROLLBACK_VERSION"
# Stop application traffic
echo "Removing from load balancer..."
remove_from_load_balancer
# Restore code
echo "Restoring code..."
if [ "$ROLLBACK_VERSION" = "previous" ]; then
BACKUP_FILE=$(ls -t $BACKUP_DIR/backup_*.tar.gz | head -1)
else
BACKUP_FILE="$BACKUP_DIR/backup_$ROLLBACK_VERSION.tar.gz"
fi
cd $DEPLOY_DIR
sudo tar -xzf $BACKUP_FILE
# Restore database (if needed)
echo "Checking database rollback requirements..."
if [ -f "database_rollback_required" ]; then
echo "Rolling back database..."
php glueful db:rollback --to-version=$ROLLBACK_VERSION
fi
# Clear caches
php glueful cache:clear
# Set permissions
sudo chown -R www-data:www-data $DEPLOY_DIR
sudo chmod -R 755 $DEPLOY_DIR
# Restart services
sudo systemctl restart php8.2-fpm
sudo systemctl restart nginx
# Health check
echo "Performing health check..."
if curl -f http://localhost/health > /dev/null 2>&1; then
echo "Adding back to load balancer..."
add_to_load_balancer
echo "Rollback successful!"
else
echo "Rollback failed! Manual intervention required."
exit 1
fi
Database Rollback Strategy
# Database rollback with point-in-time recovery
restore_database_to_point_in_time() {
local target_time=$1
echo "Restoring database to $target_time"
# Stop application
systemctl stop glueful-app
# Restore from backup
mysql -u root -p glueful < /backups/glueful_backup_before_$target_time.sql
# Apply any necessary data fixes
php glueful db:fix-data --after-rollback
# Start application
systemctl start glueful-app
}
Performance Optimization
Production Optimizations
# Enable OPcache
echo "opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=512
opcache.interned_strings_buffer=64
opcache.max_accelerated_files=32531
opcache.validate_timestamps=0
opcache.save_comments=1
opcache.fast_shutdown=0" >> /etc/php/8.2/fpm/conf.d/10-opcache.ini
# Configure Redis for production
echo "maxmemory 2gb
maxmemory-policy allkeys-lru
save 900 1
save 300 10
save 60 10000" >> /etc/redis/redis.conf
CDN Integration
# Configure CloudFlare/AWS CloudFront
configure_cdn() {
echo "Configuring CDN settings..."
# Cache static assets
cache_static_assets
# Configure cache headers
set_cache_headers
# Enable compression
enable_gzip_compression
}
This deployment guide provides comprehensive coverage for deploying Glueful applications across various environments. Choose the deployment strategy that best fits your infrastructure and requirements.
Remember to test all deployment procedures in a staging environment before applying them to production.
Console Commands
Glueful’s Symfony Console-based CLI with DI-enabled commands for cache, database, extensions, queues, security, system checks, and development tools.
Glueful Extensions
Learn how to create, configure, and optimize Glueful extensions using the ServiceProvider pattern, Composer discovery, and production caching.