#!/bin/bash # Portfolio Deployment Script # Usage: ./scripts/deploy.sh [environment] set -e # Configuration ENVIRONMENT=${1:-production} REGISTRY="ghcr.io" IMAGE_NAME="dennis-konkol/my_portfolio" CONTAINER_NAME="portfolio-app" COMPOSE_FILE="docker-compose.zero-downtime.yml" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Logging function log() { echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" } error() { echo -e "${RED}[ERROR]${NC} $1" >&2 } success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } # Check if running as root (skip in CI environments) if [[ $EUID -eq 0 ]] && [[ -z "$CI" ]]; then error "This script should not be run as root (use CI=true to override)" exit 1 fi # Check if Docker is running if ! docker info > /dev/null 2>&1; then error "Docker is not running. Please start Docker and try again." exit 1 fi # Check if docker compose is available if ! docker compose version &> /dev/null; then error "docker compose is not available. Please ensure Docker is installed and try again." exit 1 fi # Check if .env file exists if [ ! -f .env ]; then error ".env file not found. Please create it with the required environment variables." exit 1 fi log "Starting deployment for environment: $ENVIRONMENT" # Set image tag based on environment if [ "$ENVIRONMENT" = "production" ]; then IMAGE_TAG="production" else IMAGE_TAG="main" fi FULL_IMAGE_NAME="$REGISTRY/$IMAGE_NAME:$IMAGE_TAG" log "Using image: $FULL_IMAGE_NAME" # Login to registry (if needed) log "Logging in to container registry..." echo "$GITHUB_TOKEN" | docker login $REGISTRY -u $GITHUB_ACTOR --password-stdin || { warning "Failed to login to registry. Make sure GITHUB_TOKEN and GITHUB_ACTOR are set." } # Build latest image locally log "Building latest image locally..." docker build -t portfolio-app:latest . || { error "Failed to build image locally" exit 1 } # Stop and remove old containers log "Stopping old containers..." docker compose -f $COMPOSE_FILE down || { warning "No old containers to stop" } # Remove old images (keep last 3 versions) log "Cleaning up old images..." docker images $REGISTRY/$IMAGE_NAME --format "table {{.Tag}}\t{{.ID}}" | tail -n +2 | head -n -3 | awk '{print $2}' | xargs -r docker rmi || { warning "No old images to remove" } # Start new containers log "Starting new containers..." docker compose -f $COMPOSE_FILE up -d || { error "Failed to start containers" exit 1 } # Wait for health check log "Waiting for application to be healthy..." HEALTH_CHECK_TIMEOUT=60 HEALTH_CHECK_INTERVAL=2 ELAPSED=0 while [ $ELAPSED -lt $HEALTH_CHECK_TIMEOUT ]; do if curl -f http://localhost/api/health > /dev/null 2>&1; then success "Application is healthy!" break fi sleep $HEALTH_CHECK_INTERVAL ELAPSED=$((ELAPSED + HEALTH_CHECK_INTERVAL)) echo -n "." done if [ $ELAPSED -ge $HEALTH_CHECK_TIMEOUT ]; then error "Health check timeout. Application may not be running properly." log "Container logs:" docker compose -f $COMPOSE_FILE logs --tail=50 exit 1 fi # Verify deployment log "Verifying deployment..." if curl -f http://localhost/api/health > /dev/null 2>&1; then success "Deployment successful!" # Show container status log "Container status:" docker compose -f $COMPOSE_FILE ps # Show resource usage log "Resource usage:" docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}" else error "Deployment verification failed!" log "Container logs:" docker compose -f $COMPOSE_FILE logs --tail=50 exit 1 fi # Cleanup log "Cleaning up unused Docker resources..." docker system prune -f --volumes || { warning "Failed to clean up Docker resources" } success "Deployment completed successfully!" log "Application is available at: http://localhost/" log "Health check endpoint: http://localhost/api/health"