#!/bin/bash # LittleShop Production Deployment Script # This script handles production deployment with zero-downtime set -e # Exit on any error # Configuration APP_NAME="littleshop" DOCKER_COMPOSE_FILE="docker-compose.prod.yml" BACKUP_DIR="/opt/littleshop/backups" DATA_DIR="/opt/littleshop/data" UPLOADS_DIR="/opt/littleshop/uploads" LOGS_DIR="/opt/littleshop/logs" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color log() { echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}" } warn() { echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}" } error() { echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}" exit 1 } # Check if running as root if [[ $EUID -eq 0 ]]; then error "This script should not be run as root" fi # Check if Docker and Docker Compose are installed command -v docker >/dev/null 2>&1 || error "Docker is not installed" command -v docker-compose >/dev/null 2>&1 || error "Docker Compose is not installed" # Check if .env file exists if [[ ! -f .env ]]; then error ".env file not found. Copy .env.production.template to .env and configure it." fi log "Starting production deployment for $APP_NAME" # Create required directories log "Creating required directories..." sudo mkdir -p "$DATA_DIR" "$UPLOADS_DIR" "$LOGS_DIR" "$BACKUP_DIR" sudo chown -R $USER:$USER /opt/littleshop # Backup existing data (if exists) if [[ -f "$DATA_DIR/littleshop.db" ]]; then log "Backing up existing database..." BACKUP_FILE="$BACKUP_DIR/littleshop-$(date +%Y%m%d-%H%M%S).db" cp "$DATA_DIR/littleshop.db" "$BACKUP_FILE" log "Database backed up to $BACKUP_FILE" fi # Pull latest images (for base images) log "Pulling latest base images..." docker pull mcr.microsoft.com/dotnet/aspnet:9.0-jammy-chiseled docker pull mcr.microsoft.com/dotnet/sdk:9.0-jammy # Build production image log "Building production image..." docker-compose -f "$DOCKER_COMPOSE_FILE" build --no-cache # Perform health check on existing container (if running) if docker ps --format "table {{.Names}}" | grep -q "${APP_NAME}_prod"; then log "Performing health check on existing container..." if ! docker exec "${APP_NAME}_prod" curl -f http://localhost:8080/health >/dev/null 2>&1; then warn "Existing container health check failed" fi fi # Deploy with rolling update strategy log "Deploying new version..." docker-compose -f "$DOCKER_COMPOSE_FILE" up -d --remove-orphans # Wait for health check log "Waiting for application to become healthy..." MAX_ATTEMPTS=30 ATTEMPT=0 while [[ $ATTEMPT -lt $MAX_ATTEMPTS ]]; do if docker exec "${APP_NAME}_prod" curl -f http://localhost:8080/health >/dev/null 2>&1; then log "Application is healthy!" break fi ATTEMPT=$((ATTEMPT + 1)) log "Health check attempt $ATTEMPT/$MAX_ATTEMPTS failed, waiting..." sleep 10 done if [[ $ATTEMPT -eq $MAX_ATTEMPTS ]]; then error "Application failed to become healthy after $MAX_ATTEMPTS attempts" fi # Clean up old images log "Cleaning up old Docker images..." docker image prune -f # Display deployment status log "Deployment completed successfully!" log "Application URL: https://littleshop.silverlabs.uk" log "Health check: https://littleshop.silverlabs.uk/health" # Show running containers log "Running containers:" docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep littleshop # Show logs (last 20 lines) log "Recent application logs:" docker-compose -f "$DOCKER_COMPOSE_FILE" logs --tail=20 littleshop log "Production deployment completed successfully!"