Skip to content

feat: gitignore env.local 허용 #12

feat: gitignore env.local 허용

feat: gitignore env.local 허용 #12

Workflow file for this run

name: CI/CD for Production Server
on:
push:
branches: [ main ] # Only main branch
permissions:
contents: read
jobs:
ci-cd-prod:
name: Test, Build, and Deploy to Production Server
runs-on: ubuntu-latest
steps:
# ========================================
# CI Stage: Test & Lint
# ========================================
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test -- --ci --coverage --maxWorkers=2
continue-on-error: true
# ========================================
# CD Stage: Build & Push Docker Image
# ========================================
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: |
${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:latest
${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:prod
${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:prod-${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
NODE_ENV=production
NEXT_TELEMETRY_DISABLED=1
# ========================================
# Deploy Stage: SSH & Deploy
# ========================================
- name: Setup SSH key and config
run: |
set -e
mkdir -p ~/.ssh
# Write SSH private key (handle \n in secret)
echo "${{ secrets.PROD_SSH_PRIVATE_KEY }}" | tr -d '\r' > ~/.ssh/my-key.pem
chmod 600 ~/.ssh/my-key.pem
# Verify key file was created
if [ ! -s ~/.ssh/my-key.pem ]; then
echo "Error: SSH key file is empty"
exit 1
fi
echo "SSH key written successfully ($(wc -l < ~/.ssh/my-key.pem) lines)"
# Add server to known_hosts
echo "Adding ${{ secrets.PROD_SERVER_HOST }}:${{ secrets.PROD_SSH_PORT }} to known_hosts..."
ssh-keyscan -p ${{ secrets.PROD_SSH_PORT }} -H ${{ secrets.PROD_SERVER_HOST }} >> ~/.ssh/known_hosts 2>&1 || {
echo "Warning: ssh-keyscan failed, but continuing..."
echo "You may need to manually verify the host key on first connection"
}
# Configure SSH keep-alive and disable strict host checking for automation
cat >> ~/.ssh/config << 'EOF'
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
StrictHostKeyChecking no
UserKnownHostsFile ~/.ssh/known_hosts
EOF
echo "✓ SSH configuration complete"
- name: Create app directory on server
run: |
ssh -i ~/.ssh/my-key.pem -p ${{ secrets.PROD_SSH_PORT }} ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }} "mkdir -p ~/devnogi-react"
- name: Copy deployment files to server
run: |
scp -i ~/.ssh/my-key.pem -P ${{ secrets.PROD_SSH_PORT }} docker-compose-prod.yaml ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }}:~/devnogi-react/
- name: Deploy to Production Server
run: |
ssh -i ~/.ssh/my-key.pem -p ${{ secrets.PROD_SSH_PORT }} ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }} << 'EOF'
cd ~/devnogi-react
# Write .env content from GitHub Secret
echo "${{ secrets.ENV_FILE_PROD }}" > .env
# Pull latest production image
docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:prod
# Stop and remove existing containers
docker compose -f docker-compose-prod.yaml down
# Start new containers
docker compose -f docker-compose-prod.yaml up -d
echo "✅ Production deployment complete"
EOF
# ========================================
# Health Check Stage (Stricter for Production)
# ========================================
- name: Comprehensive Health Check
run: |
ssh -i ~/.ssh/my-key.pem -p ${{ secrets.PROD_SSH_PORT }} ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }} << 'EOF'
echo "=== Starting Production Health Check ==="
# 1. Check if Next.js container is running
CONTAINER_ID=$(docker ps -q --filter "name=nextjs-app-prod")
if [ -z "$CONTAINER_ID" ]; then
echo "❌ Next.js container not running"
docker ps -a
docker logs nextjs-app-prod --tail 50
exit 1
fi
echo "✅ Next.js container is running (ID: $CONTAINER_ID)"
# 2. Wait for Docker health check (Next.js)
echo "Waiting for Next.js container to become healthy..."
for i in {1..36}; do
HEALTH_STATUS=$(docker inspect --format='{{.State.Health.Status}}' nextjs-app-prod 2>/dev/null || echo "no-healthcheck")
if [ "$HEALTH_STATUS" == "healthy" ]; then
echo "✅ Next.js container is healthy"
break
elif [ "$HEALTH_STATUS" == "no-healthcheck" ]; then
echo "⚠️ No healthcheck configured, checking endpoint directly"
break
fi
echo "Current health status: $HEALTH_STATUS ($i/36)"
sleep 10
if [ $i -eq 36 ]; then
echo "❌ Container failed to become healthy after 6 minutes"
docker logs nextjs-app-prod --tail 100
exit 1
fi
done
# 3. Check Next.js health endpoint directly
echo "Checking health endpoint (port 3010)..."
for i in {1..30}; do
HEALTH_RESPONSE=$(curl -s http://localhost:3010/api/health || echo "")
if echo "$HEALTH_RESPONSE" | grep -q '"status":"ok"'; then
echo "✅ Application health check passed"
echo "Health response: $HEALTH_RESPONSE"
break
fi
echo "Waiting for application to start... ($i/30)"
sleep 10
if [ $i -eq 30 ]; then
echo "❌ Application health check failed after 5 minutes"
echo "Last response: $HEALTH_RESPONSE"
docker logs nextjs-app-prod --tail 100
exit 1
fi
done
# 4. Smoke test: Check if Next.js responds
echo "Running smoke test..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3010/api/health)
if [ "$HTTP_CODE" == "200" ]; then
echo "✅ Smoke test passed (HTTP $HTTP_CODE)"
else
echo "❌ Smoke test failed (HTTP $HTTP_CODE)"
exit 1
fi
echo "=== Health Check Complete ==="
docker ps --filter "name=nextjs-app-prod"
EOF
- name: Display deployment info
if: success()
run: |
echo "✅ Production deployment successful!"
echo "🐳 Image: ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:prod"
echo "📦 Commit: ${{ github.sha }}"
echo "🔗 Next.js App: http://${{ secrets.PROD_SERVER_HOST }}:3010"
echo "⚠️ Configure your host Nginx to proxy to localhost:3010"
echo "⚠️ Please verify the production deployment manually"
# ========================================
# Rollback on Failure (Optional)
# ========================================
- name: Rollback on failure
if: failure()
run: |
echo "❌ Deployment failed! Consider manual rollback if needed."
echo "To rollback, SSH to server and run:"
echo " cd ~/devnogi-react"
echo " docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:prod-<previous-sha>"
echo " docker compose -f docker-compose-prod.yaml down"
echo " docker compose -f docker-compose-prod.yaml up -d"