# Gitea Actions Workflow for Next.js Blog Application # This workflow builds a Docker image and deploys it to production # # Workflow triggers: # - Push to master branch (automatic deployment) # - Manual trigger via workflow_dispatch # # Required Secrets (configure in Gitea repository settings): # - PRODUCTION_HOST: IP address or hostname of production server # - PRODUCTION_USER: SSH username (e.g., 'deployer') # - SSH_PRIVATE_KEY: Private SSH key for authentication # - REGISTRY_USERNAME: Docker registry username (optional, if registry requires auth) # - REGISTRY_PASSWORD: Docker registry password (optional, if registry requires auth) # # Environment Variables (configured below): # - REGISTRY: Docker registry URL # - IMAGE_NAME: Docker image name name: Build and Deploy Next.js Blog to Production on: push: branches: - master # Trigger on push to master branch workflow_dispatch: # Allow manual trigger from Gitea UI env: # Docker registry configuration # Update this to match your private registry URL REGISTRY: repository.workspace:5000 IMAGE_NAME: mypage jobs: # ============================================ # Job 1: Code Quality Checks (Linting) # ============================================ lint: name: 🔍 Code Quality Checks runs-on: node-latest steps: - name: 🔎 Checkout code uses: actions/checkout@v4 - name: 📥 Install dependencies run: npm ci - name: 🔍 Run ESLint run: npm run lint - name: 💅 Check code formatting (Prettier) run: npm run format:check - name: 🔤 TypeScript type checking run: npx tsc --noEmit - name: ✅ All quality checks passed run: | echo "✅ All code quality checks passed successfully!" echo " - ESLint: No linting errors" echo " - Prettier: Code is properly formatted" echo " - TypeScript: No type errors" # ============================================ # Job 2: Build and Push Docker Image # ============================================ build-and-push: name: 🏗️ Build and Push Docker Image runs-on: ubuntu-latest needs: [lint] # Wait for lint job to complete successfully steps: - name: 🔎 Checkout code uses: actions/checkout@v4 - name: 🔐 Log in to Docker Registry (if credentials provided) run: | if [ -n "${{ secrets.REGISTRY_USERNAME }}" ] && [ -n "${{ secrets.REGISTRY_PASSWORD }}" ]; then echo "Logging into ${{ env.REGISTRY }} with credentials..." echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin echo "✅ Login successful" else echo "⚠️ No registry credentials provided - using insecure/public registry" fi - name: 🏗️ Build Docker image timeout-minutes: 30 env: DOCKER_BUILDKIT: 1 # Enable BuildKit for faster builds and better caching run: | echo "Building Next.js Docker image with BuildKit..." echo "Build context size:" du -sh . 2>/dev/null || echo "Cannot measure context size" # Build the Docker image # - Uses Dockerfile.nextjs from project root # - Tags image with both 'latest' and commit SHA # - Enables inline cache for faster subsequent builds # -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} ❗ do this if deploying on PR creation docker build \ --progress=plain \ --build-arg BUILDKIT_INLINE_CACHE=1 \ -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \ -f Dockerfile.nextjs \ . echo "✅ Build successful" echo "Image size:" docker images ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - name: 🚀 Push Docker image to registry run: | echo "Pushing image to registry..." # Push both tags (latest and commit SHA) docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest # docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} echo "✅ Image pushed successfully" echo " - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" # echo " - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}" # ============================================ # Job 2: Deploy to Production Server # ============================================ deploy-production: name: 🚀 Deploy to Production runs-on: ubuntu-latest needs: [build-and-push] # Wait for build job to complete environment: name: production url: http://192.168.1.54:3030 # Update with your actual production URL steps: - name: 🔎 Checkout code (for docker-compose file) uses: actions/checkout@v4 - name: 🔐 Validate Registry Access on Production Server uses: appleboy/ssh-action@v1.0.3 env: REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} REGISTRY_URL: ${{ env.REGISTRY }} with: host: ${{ vars.PRODUCTION_HOST }} username: ${{ vars.PRODUCTION_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} port: 22 envs: REGISTRY_PASSWORD,REGISTRY_USERNAME,REGISTRY_URL script: | echo "=== Validating Docker Registry access ===" if [ -n "$REGISTRY_USERNAME" ] && [ -n "$REGISTRY_PASSWORD" ]; then echo "Logging into $REGISTRY_URL with credentials..." echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_URL" -u "$REGISTRY_USERNAME" --password-stdin echo "✅ Registry authentication successful" else echo "⚠️ No registry credentials - using insecure/public registry" echo "Testing registry connectivity..." curl -f "http://$REGISTRY_URL/v2/" || { echo "❌ Registry not accessible"; exit 1; } echo "✅ Registry is accessible" fi - name: 📁 Ensure application directory structure uses: appleboy/ssh-action@v1.0.3 with: host: ${{ vars.PRODUCTION_HOST }} username: ${{ vars.PRODUCTION_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} port: 22 script: | echo "=== Ensuring application directory structure ===" # Verify base directory exists and is writable # Update /opt/mypage to match your deployment directory if [ ! -d /opt/mypage ]; then echo "❌ /opt/mypage does not exist!" echo "Please run manually on production server:" echo " sudo mkdir -p /opt/mypage" echo " sudo chown -R deployer:docker /opt/mypage" echo " sudo chmod -R 775 /opt/mypage" exit 1 fi if [ ! -w /opt/mypage ]; then echo "❌ /opt/mypage is not writable by $USER user" echo "Please run manually on production server:" echo " sudo chown -R deployer:docker /opt/mypage" echo " sudo chmod -R 775 /opt/mypage" exit 1 fi # Create data directories for logs mkdir -p /opt/mypage/data/logs || { echo "❌ Failed to create logs directory"; exit 1; } echo "✅ Directory structure ready" ls -la /opt/mypage - name: 📦 Copy docker-compose.prod.yml to server uses: appleboy/scp-action@v0.1.7 with: host: ${{ vars.PRODUCTION_HOST }} username: ${{ vars.PRODUCTION_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} port: 22 source: "docker-compose.prod.yml" target: "/opt/mypage/" overwrite: true - name: 🐳 Deploy application via Docker Compose uses: appleboy/ssh-action@v1.0.3 env: REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD || '' }} REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME || '' }} REGISTRY_URL: ${{ env.REGISTRY }} IMAGE_FULL: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest with: host: ${{ vars.PRODUCTION_HOST }} username: ${{ vars.PRODUCTION_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} port: 22 envs: REGISTRY_PASSWORD,REGISTRY_USERNAME,REGISTRY_URL,IMAGE_FULL script_stop: true # Stop execution on any error script: | echo "=== Starting deployment to production server ===" cd /opt/mypage # Log in to Docker registry (if credentials are configured) if [ -n "$REGISTRY_USERNAME" ] && [ -n "$REGISTRY_PASSWORD" ]; then echo "=== Logging in to Docker registry ===" echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_URL" -u "$REGISTRY_USERNAME" --password-stdin echo "✅ Registry login successful" else echo "⚠️ No registry credentials - using insecure/public registry (no login required)" fi # Pull latest image from registry echo "=== Pulling latest Docker image ===" docker pull "$IMAGE_FULL" if [ $? -ne 0 ]; then echo "❌ Failed to pull image, aborting deployment" exit 1 fi # Deploy new container # - Stops old container # - Removes old container # - Creates and starts new container with fresh image echo "=== Deploying new container ===" docker compose -f docker-compose.prod.yml up -d --force-recreate if [ $? -ne 0 ]; then echo "❌ Failed to deploy new container" echo "Check logs above for errors" exit 1 fi # Check container status echo "=== Container Status ===" docker compose -f docker-compose.prod.yml ps # Show recent logs for debugging echo "=== Recent application logs ===" docker compose -f docker-compose.prod.yml logs --tail=50 # Clean up old/unused images to save disk space echo "=== Cleaning up old Docker images ===" docker image prune -f echo "✅ Deployment completed successfully ===" - name: ❤️ Health check uses: appleboy/ssh-action@v1.0.3 with: host: ${{ vars.PRODUCTION_HOST }} username: ${{ vars.PRODUCTION_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} port: 22 script: | echo "=== Performing health check ===" cd /opt/mypage max_attempts=15 attempt=1 # Wait for container to be healthy (respect start_period from health check) echo "Waiting for application to start (40s start period)..." sleep 40 # Retry health check up to 15 times while [ $attempt -le $max_attempts ]; do # Check if application responds at port 3030 if curl -f http://localhost:3030/ > /dev/null 2>&1; then echo "✅ Health check passed!" echo "Application is healthy and responding to requests" exit 0 fi echo "Attempt $attempt/$max_attempts: Health check failed, retrying in 5s..." sleep 5 attempt=$((attempt + 1)) done # Health check failed - gather diagnostic information echo "❌ Health check failed after $max_attempts attempts" echo "" echo "=== Container Status ===" docker compose -f docker-compose.prod.yml ps echo "" echo "=== Container Health ===" docker inspect mypage-prod --format='{{.State.Health.Status}}' 2>/dev/null || echo "No health status" echo "" echo "=== Recent Application Logs ===" docker compose -f docker-compose.prod.yml logs --tail=100 exit 1 - name: 📊 Deployment summary if: always() # Run even if previous steps fail run: | echo "### 🚀 Deployment Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "- **Environment**: Production" >> $GITHUB_STEP_SUMMARY echo "- **Image**: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" >> $GITHUB_STEP_SUMMARY echo "- **Commit**: \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY echo "- **Workflow Run**: #${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY echo "- **Triggered By**: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY echo "- **Status**: ${{ job.status }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Next Steps:**" >> $GITHUB_STEP_SUMMARY echo "1. Verify application is accessible at production URL" >> $GITHUB_STEP_SUMMARY echo "2. Check application logs for any errors" >> $GITHUB_STEP_SUMMARY echo "3. Monitor resource usage and performance" >> $GITHUB_STEP_SUMMARY