name: Testing Deployment (Zero Downtime) on: push: branches: [ testing ] env: NODE_VERSION: '20' DOCKER_IMAGE: portfolio-app IMAGE_TAG: testing jobs: deploy-testing: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Run linting run: npm run lint continue-on-error: true # Don't block dev deployments on lint errors - name: Run tests run: npm run test continue-on-error: true # Don't block dev deployments on test failures - name: Build application run: npm run build - name: Build Docker image run: | echo "๐Ÿ—๏ธ Building testing Docker image with BuildKit cache..." DOCKER_BUILDKIT=1 docker build \ --cache-from ${{ env.DOCKER_IMAGE }}:${{ env.IMAGE_TAG }} \ --cache-from ${{ env.DOCKER_IMAGE }}:latest \ -t ${{ env.DOCKER_IMAGE }}:${{ env.IMAGE_TAG }} \ . echo "โœ… Docker image built successfully" - name: Zero-Downtime Testing Deployment run: | echo "๐Ÿš€ Starting zero-downtime testing deployment..." COMPOSE_FILE="docker-compose.testing.yml" CONTAINER_NAME="portfolio-app-testing" HEALTH_PORT="3002" # Backup current container ID if running OLD_CONTAINER=$(docker ps -q -f name=$CONTAINER_NAME || echo "") # Start new container with updated image echo "๐Ÿ†• Starting new dev container..." docker compose -f $COMPOSE_FILE up -d --no-deps --build portfolio-testing # Wait for new container to be healthy echo "โณ Waiting for new container to be healthy..." for i in {1..60}; do NEW_CONTAINER=$(docker ps -q -f name=$CONTAINER_NAME) if [ ! -z "$NEW_CONTAINER" ]; then # Check health status HEALTH=$(docker inspect $NEW_CONTAINER --format='{{.State.Health.Status}}' 2>/dev/null || echo "starting") if [ "$HEALTH" == "healthy" ]; then echo "โœ… New container is healthy!" break fi # Also check HTTP health endpoint if curl -f http://localhost:$HEALTH_PORT/api/health > /dev/null 2>&1; then echo "โœ… New container is responding!" break fi fi echo "โณ Waiting... ($i/60)" sleep 2 done # Verify new container is working if ! curl -f http://localhost:$HEALTH_PORT/api/health > /dev/null 2>&1; then echo "โš ๏ธ New testing container health check failed, but continuing (non-blocking)..." docker compose -f $COMPOSE_FILE logs --tail=50 portfolio-testing fi # Remove old container if it exists and is different if [ ! -z "$OLD_CONTAINER" ]; then NEW_CONTAINER=$(docker ps -q -f name=$CONTAINER_NAME) if [ "$OLD_CONTAINER" != "$NEW_CONTAINER" ]; then echo "๐Ÿงน Removing old container..." docker stop $OLD_CONTAINER 2>/dev/null || true docker rm $OLD_CONTAINER 2>/dev/null || true fi fi echo "โœ… Testing deployment completed!" env: NODE_ENV: production LOG_LEVEL: ${{ vars.LOG_LEVEL || 'debug' }} NEXT_PUBLIC_BASE_URL: ${{ vars.NEXT_PUBLIC_BASE_URL_TESTING || 'https://testing.dk0.dev' }} MY_EMAIL: ${{ vars.MY_EMAIL }} MY_INFO_EMAIL: ${{ vars.MY_INFO_EMAIL }} MY_PASSWORD: ${{ secrets.MY_PASSWORD }} MY_INFO_PASSWORD: ${{ secrets.MY_INFO_PASSWORD }} ADMIN_BASIC_AUTH: ${{ secrets.ADMIN_BASIC_AUTH }} ADMIN_SESSION_SECRET: ${{ secrets.ADMIN_SESSION_SECRET }} N8N_WEBHOOK_URL: ${{ vars.N8N_WEBHOOK_URL || '' }} N8N_SECRET_TOKEN: ${{ secrets.N8N_SECRET_TOKEN || '' }} - name: Testing Health Check run: | echo "๐Ÿ” Running testing health checks..." for i in {1..20}; do if curl -f http://localhost:3002/api/health && curl -f http://localhost:3002/ > /dev/null; then echo "โœ… Testing is fully operational!" exit 0 fi echo "โณ Waiting for testing... ($i/20)" sleep 3 done echo "โš ๏ธ Testing health check failed, but continuing (non-blocking)..." docker compose -f docker-compose.testing.yml logs --tail=50 - name: Cleanup run: | echo "๐Ÿงน Cleaning up old images..." docker image prune -f echo "โœ… Cleanup completed"