Some checks failed
Build and Deploy Next.js Blog to Production / 🔍 Code Quality Checks (push) Successful in 10m17s
Build and Deploy Next.js Blog to Production / 🚀 Deploy to Production (push) Has been cancelled
Build and Deploy Next.js Blog to Production / 🏗️ Build and Push Docker Image (push) Has been cancelled
366 lines
15 KiB
YAML
366 lines
15 KiB
YAML
# 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
|
||
#
|
||
# Docker Registry Authentication Strategy:
|
||
# - Registry login is OPTIONAL and conditional
|
||
# - Login only attempted if REGISTRY_USERNAME and REGISTRY_PASSWORD are configured
|
||
# - Login failures are logged but do NOT fail the workflow
|
||
# - Insecure/private registries (e.g., repository.workspace:5000) work without authentication
|
||
# - If push/pull fails due to auth, the workflow will fail at that point (not at login)
|
||
# - This approach supports both authenticated and insecure registries without workflow changes
|
||
|
||
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: ubuntu-latest
|
||
# 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
|
||
|
||
- 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
|
||
|
||
# Optional: Only needed if registry requires authentication
|
||
# For insecure/private registries (e.g., repository.workspace:5000), login is not required
|
||
# Credentials are checked before attempting login to avoid unnecessary failures
|
||
- 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..."
|
||
if echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin 2>/dev/null; then
|
||
echo "✅ Login successful"
|
||
else
|
||
echo "⚠️ Login failed, continuing anyway (registry might not require auth)"
|
||
fi
|
||
else
|
||
echo "⚠️ No registry credentials provided - using insecure/public registry (no login required)"
|
||
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
|
||
|
||
# Optional: Validate registry access if authentication is configured
|
||
# For insecure registries, this step only logs status without failing workflow
|
||
# Actual registry access is tested during image pull in deployment step
|
||
- 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..."
|
||
if echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_URL" -u "$REGISTRY_USERNAME" --password-stdin 2>/dev/null; then
|
||
echo "✅ Registry authentication successful"
|
||
else
|
||
echo "⚠️ Login failed - registry might not require authentication"
|
||
fi
|
||
else
|
||
echo "⚠️ No registry credentials configured - using insecure/public registry"
|
||
echo "ℹ️ Registry connectivity will be validated during image pull"
|
||
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:
|
||
# 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
|
||
|
||
# Log in to Docker registry (skip if credentials not configured)
|
||
# For insecure/private registries (repository.workspace:5000), login is optional
|
||
if [ -n "$REGISTRY_USERNAME" ] && [ -n "$REGISTRY_PASSWORD" ]; then
|
||
echo "=== Logging in to Docker registry ==="
|
||
if echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_URL" -u "$REGISTRY_USERNAME" --password-stdin 2>/dev/null; then
|
||
echo "✅ Registry login successful"
|
||
else
|
||
echo "⚠️ Login failed - continuing anyway (registry might not require auth)"
|
||
fi
|
||
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
|