# 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 # # Environment Variables (configured below): # - REGISTRY: Docker registry URL # - IMAGE_NAME: Docker image name # # Docker Registry Configuration: # - Current registry (repository.workspace:5000) is INSECURE - no authentication required # - Registry login steps are SKIPPED to avoid 7+ minute timeout delays # - Docker push/pull operations work without credentials # - If switching to authenticated registry: uncomment login steps and configure secrets 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-22 # env: # ACTIONS_RUNTIME_URL: http://192.168.1.53:3000 # SeteazΔƒ la nivel de job steps: - name: πŸ”Ž Checkout code uses: actions/checkout@v4 # with: # github-server-url: ${{ env.ACTIONS_RUNTIME_URL }} # - name: πŸ“¦ Setup Node.js # uses: actions/setup-node@v4 # with: # node-version: "22" # cache: "npm" - name: πŸ“₯ Install dependencies run: npm ci - name: πŸ” Run ESLint run: npm run lint continue-on-error: true - name: πŸ’… Check code formatting (Prettier) run: npm run format:check continue-on-error: true - 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: πŸ“ Create .env file from Gitea secrets run: | echo "Creating .env file for Docker build..." cat > .env << EOF # Build-time environment variables NEXT_PUBLIC_SITE_URL=${{ secrets.NEXT_PUBLIC_SITE_URL }} NODE_ENV=production NEXT_TELEMETRY_DISABLED=1 # Add other build-time variables here as needed # NEXT_PUBLIC_GA_ID=${{ secrets.NEXT_PUBLIC_GA_ID }} EOF echo "βœ… .env file created successfully" echo "Preview (secrets masked):" cat .env | sed 's/=.*/=***MASKED***/g' # Insecure registry configuration - no authentication required # The registry at repository.workspace:5000 does not require login # Docker push/pull operations work without credentials - name: ℹ️ Registry configuration (insecure - no login required) run: | echo "=== Docker Registry Configuration ===" echo "Registry: ${{ env.REGISTRY }}" echo "Type: Insecure (no authentication required)" echo "" echo "ℹ️ Skipping registry login - insecure registry allows push/pull without credentials" echo "" echo "If your registry requires authentication in the future:" echo " 1. Set REGISTRY_USERNAME and REGISTRY_PASSWORD secrets in Gitea" echo " 2. Uncomment the login step below this message" echo " 3. Change registry URL to authenticated registry" # Uncomment this step if registry requires authentication in the future # - name: πŸ” Log in to Docker Registry # timeout-minutes: 1 # run: | # if [ -n "${{ secrets.REGISTRY_USERNAME }}" ] && [ -n "${{ secrets.REGISTRY_PASSWORD }}" ]; then # echo "Attempting login to ${{ env.REGISTRY }}..." # timeout 30s bash -c 'echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin' || { # echo "⚠️ Login failed - continuing anyway" # } # 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" # Clean up sensitive files rm -f .env echo "βœ… Cleaned up .env file" # 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 # Verify Docker is accessible on production server # Registry authentication is not required for insecure registry - name: ℹ️ Verify production server Docker access 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 "=== Verifying Docker is accessible ===" docker info > /dev/null 2>&1 || { echo "❌ Docker is not running or user has no access" echo "Please ensure Docker is installed and user is in docker group" exit 1 } echo "βœ… Docker is accessible" echo "" echo "=== Registry Configuration ===" echo "Registry: ${{ env.REGISTRY }}" echo "Type: Insecure (no authentication)" echo "ℹ️ Skipping registry login - push/pull will work without credentials" - 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: # Optional: only needed if registry requires authentication 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 # Registry configuration - insecure registry does not require authentication echo "=== Registry Configuration ===" echo "Registry: $REGISTRY_URL" echo "Type: Insecure (no authentication required)" echo "ℹ️ Skipping registry login" echo "" # 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