- Add docker volume prune to deploy.sh to prevent stale code issues - Add automatic migrations and cache warmup to staging entrypoint - Fix nginx race condition by waiting for PHP-FPM before starting - Improve PHP healthcheck to use php-fpm-healthcheck - Add curl to production nginx Dockerfile for healthchecks - Add ensureSeedsTable() to SeedRepository for automatic table creation - Update SeedCommand to ensure seeds table exists before operations This prevents 502 Bad Gateway errors during deployment and ensures fresh code is deployed without volume cache issues.
196 lines
5.9 KiB
Bash
Executable File
196 lines
5.9 KiB
Bash
Executable File
#!/bin/bash
|
||
# ==============================================================================
|
||
# Application Deployment Script
|
||
# ==============================================================================
|
||
# Deploys application to staging or production environment
|
||
# Usage: ./deploy.sh <environment> [options]
|
||
# ==============================================================================
|
||
|
||
set -e
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||
cd "$PROJECT_ROOT"
|
||
|
||
# Colors for output
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m' # No Color
|
||
|
||
# Function to print colored output
|
||
print_info() {
|
||
echo -e "${BLUE}ℹ${NC} $1"
|
||
}
|
||
|
||
print_success() {
|
||
echo -e "${GREEN}✅${NC} $1"
|
||
}
|
||
|
||
print_warning() {
|
||
echo -e "${YELLOW}⚠️${NC} $1"
|
||
}
|
||
|
||
print_error() {
|
||
echo -e "${RED}❌${NC} $1"
|
||
}
|
||
|
||
# Parse arguments
|
||
ENVIRONMENT=$1
|
||
BUILD_IMAGES=${2:-false}
|
||
|
||
if [ -z "$ENVIRONMENT" ]; then
|
||
print_error "Usage: $0 <environment> [build]"
|
||
print_info "Environments: staging, production"
|
||
print_info "Options: build - Build Docker images before deployment"
|
||
exit 1
|
||
fi
|
||
|
||
if [ "$ENVIRONMENT" != "staging" ] && [ "$ENVIRONMENT" != "production" ]; then
|
||
print_error "Invalid environment: $ENVIRONMENT"
|
||
print_info "Valid environments: staging, production"
|
||
exit 1
|
||
fi
|
||
|
||
# Set compose files
|
||
COMPOSE_BASE="docker-compose.base.yml"
|
||
if [ "$ENVIRONMENT" = "staging" ]; then
|
||
COMPOSE_ENV="docker-compose.staging.yml"
|
||
elif [ "$ENVIRONMENT" = "production" ]; then
|
||
COMPOSE_ENV="docker-compose.prod.yml"
|
||
fi
|
||
|
||
COMPOSE_FILES="-f $COMPOSE_BASE -f $COMPOSE_ENV"
|
||
|
||
print_info "Deploying to $ENVIRONMENT environment..."
|
||
|
||
# Check if secrets exist
|
||
SECRETS_DIR="deployment/secrets/$ENVIRONMENT"
|
||
if [ ! -d "$SECRETS_DIR" ]; then
|
||
print_warning "Secrets directory not found: $SECRETS_DIR"
|
||
print_info "Creating secrets directory..."
|
||
mkdir -p "$SECRETS_DIR"
|
||
fi
|
||
|
||
MISSING_SECRETS=()
|
||
REQUIRED_SECRETS=("db_password.txt" "redis_password.txt" "app_key.txt")
|
||
|
||
for secret_file in "${REQUIRED_SECRETS[@]}"; do
|
||
if [ ! -f "$SECRETS_DIR/$secret_file" ]; then
|
||
MISSING_SECRETS+=("$secret_file")
|
||
fi
|
||
done
|
||
|
||
if [ ${#MISSING_SECRETS[@]} -gt 0 ]; then
|
||
print_error "Missing required secrets:"
|
||
for secret in "${MISSING_SECRETS[@]}"; do
|
||
print_error " - $SECRETS_DIR/$secret"
|
||
done
|
||
print_info "See deployment/infrastructure/SECRETS.md for instructions"
|
||
exit 1
|
||
fi
|
||
|
||
# Check if infrastructure networks exist
|
||
print_info "Checking infrastructure networks..."
|
||
if ! docker network ls | grep -q "traefik-public"; then
|
||
print_error "traefik-public network not found"
|
||
print_info "Please deploy infrastructure stacks first:"
|
||
print_info " cd deployment/infrastructure && ./deploy.sh traefik"
|
||
exit 1
|
||
fi
|
||
|
||
if ! docker network ls | grep -q "app-internal"; then
|
||
print_error "app-internal network not found"
|
||
print_info "Please deploy infrastructure stacks first:"
|
||
print_info " cd deployment/infrastructure && ./deploy.sh postgresql"
|
||
exit 1
|
||
fi
|
||
|
||
# Build images if requested
|
||
if [ "$BUILD_IMAGES" = "build" ]; then
|
||
print_info "Building Docker images..."
|
||
docker compose $COMPOSE_FILES build
|
||
fi
|
||
|
||
# Pull latest images
|
||
print_info "Pulling latest images..."
|
||
docker compose $COMPOSE_FILES pull || print_warning "Failed to pull some images, continuing..."
|
||
|
||
# Stop and remove existing containers to prevent name conflicts
|
||
print_info "Stopping existing containers..."
|
||
docker compose $COMPOSE_FILES down --remove-orphans || print_warning "No existing containers to stop"
|
||
|
||
# Staging: Remove named volume to ensure fresh code from image
|
||
# This prevents stale code persisting between deployments
|
||
if [ "$ENVIRONMENT" = "staging" ]; then
|
||
print_info "Removing staging code volume to ensure fresh deployment..."
|
||
docker volume rm staging-code 2>/dev/null || print_info "No stale staging volume to remove"
|
||
fi
|
||
|
||
# Remove any orphaned containers with conflicting names
|
||
for container in nginx php redis scheduler queue-worker; do
|
||
if docker ps -a --format '{{.Names}}' | grep -q "^${container}$"; then
|
||
print_warning "Removing orphaned container: $container"
|
||
docker rm -f "$container" 2>/dev/null || true
|
||
fi
|
||
done
|
||
|
||
# Deploy stack
|
||
print_info "Deploying application stack..."
|
||
docker compose $COMPOSE_FILES up -d --force-recreate --remove-orphans
|
||
|
||
# Wait for services to be healthy
|
||
print_info "Waiting for services to be healthy..."
|
||
sleep 10
|
||
|
||
# Check service status
|
||
print_info "Checking service status..."
|
||
docker compose $COMPOSE_FILES ps
|
||
|
||
# Health checks
|
||
print_info "Running health checks..."
|
||
HEALTH_CHECK_FAILED=0
|
||
|
||
# Check PHP service
|
||
if docker compose $COMPOSE_FILES exec -T php php -v > /dev/null 2>&1; then
|
||
print_success "PHP service is healthy"
|
||
else
|
||
print_error "PHP service health check failed"
|
||
HEALTH_CHECK_FAILED=1
|
||
fi
|
||
|
||
# Check Redis service
|
||
if docker compose $COMPOSE_FILES exec -T redis redis-cli ping > /dev/null 2>&1; then
|
||
print_success "Redis service is healthy"
|
||
else
|
||
print_error "Redis service health check failed"
|
||
HEALTH_CHECK_FAILED=1
|
||
fi
|
||
|
||
# Check Nginx service
|
||
if docker compose $COMPOSE_FILES exec -T nginx wget --quiet --tries=1 --spider http://localhost/health > /dev/null 2>&1; then
|
||
print_success "Nginx service is healthy"
|
||
else
|
||
print_warning "Nginx health check endpoint not available (this may be normal)"
|
||
fi
|
||
|
||
if [ $HEALTH_CHECK_FAILED -eq 1 ]; then
|
||
print_error "Some health checks failed. Check logs:"
|
||
print_info " docker compose $COMPOSE_FILES logs"
|
||
exit 1
|
||
fi
|
||
|
||
print_success "Deployment to $ENVIRONMENT completed successfully!"
|
||
|
||
# Show service URLs
|
||
if [ "$ENVIRONMENT" = "production" ]; then
|
||
print_info "Application URL: https://michaelschiemer.de"
|
||
elif [ "$ENVIRONMENT" = "staging" ]; then
|
||
print_info "Application URL: https://staging.michaelschiemer.de"
|
||
fi
|
||
|
||
print_info "View logs: docker compose $COMPOSE_FILES logs -f"
|
||
print_info "View status: docker compose $COMPOSE_FILES ps"
|
||
|