- Disabled ci-cd-fast.yml (Gitea Actions syntax) - Added ci-cd-woodpecker.yml (proper Woodpecker CI syntax) - Fixed environment variable and secret access - Should resolve deployment issues with missing variables
233 lines
8.0 KiB
YAML
233 lines
8.0 KiB
YAML
name: CI/CD Pipeline (Woodpecker)
|
|
|
|
when:
|
|
event: push
|
|
branch: production
|
|
|
|
steps:
|
|
build:
|
|
image: node:20-alpine
|
|
commands:
|
|
- echo "🚀 Starting CI/CD Pipeline"
|
|
- echo "📋 Step 1: Installing dependencies..."
|
|
- npm ci --prefer-offline --no-audit
|
|
- echo "🔍 Step 2: Running linting..."
|
|
- npm run lint
|
|
- echo "🧪 Step 3: Running tests..."
|
|
- npm run test
|
|
- echo "🏗️ Step 4: Building application..."
|
|
- npm run build
|
|
- echo "🔒 Step 5: Running security scan..."
|
|
- npm audit --audit-level=high || echo "⚠️ Some vulnerabilities found, but continuing..."
|
|
volumes:
|
|
- node_modules:/app/node_modules
|
|
|
|
docker-build:
|
|
image: docker:latest
|
|
commands:
|
|
- echo "🐳 Building Docker image..."
|
|
- docker build -t portfolio-app:latest .
|
|
- docker tag portfolio-app:latest portfolio-app:$(date +%Y%m%d-%H%M%S)
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
|
|
deploy:
|
|
image: docker:latest
|
|
commands:
|
|
- echo "🚀 Deploying application..."
|
|
|
|
# Verify secrets and variables
|
|
- echo "🔍 Verifying secrets and variables..."
|
|
- |
|
|
if [ -z "$NEXT_PUBLIC_BASE_URL" ]; then
|
|
echo "❌ NEXT_PUBLIC_BASE_URL variable is missing!"
|
|
exit 1
|
|
fi
|
|
if [ -z "$MY_EMAIL" ]; then
|
|
echo "❌ MY_EMAIL variable is missing!"
|
|
exit 1
|
|
fi
|
|
if [ -z "$MY_INFO_EMAIL" ]; then
|
|
echo "❌ MY_INFO_EMAIL variable is missing!"
|
|
exit 1
|
|
fi
|
|
if [ -z "$MY_PASSWORD" ]; then
|
|
echo "❌ MY_PASSWORD secret is missing!"
|
|
exit 1
|
|
fi
|
|
if [ -z "$MY_INFO_PASSWORD" ]; then
|
|
echo "❌ MY_INFO_PASSWORD secret is missing!"
|
|
exit 1
|
|
fi
|
|
if [ -z "$ADMIN_BASIC_AUTH" ]; then
|
|
echo "❌ ADMIN_BASIC_AUTH secret is missing!"
|
|
exit 1
|
|
fi
|
|
echo "✅ All required secrets and variables are present"
|
|
|
|
# Check if current container is running
|
|
- |
|
|
if docker ps -q -f name=portfolio-app | grep -q .; then
|
|
echo "📊 Current container is running, proceeding with zero-downtime update"
|
|
CURRENT_CONTAINER_RUNNING=true
|
|
else
|
|
echo "📊 No current container running, doing fresh deployment"
|
|
CURRENT_CONTAINER_RUNNING=false
|
|
fi
|
|
|
|
# Ensure database and redis are running
|
|
- echo "🔧 Ensuring database and redis are running..."
|
|
- docker compose up -d postgres redis
|
|
- sleep 10
|
|
|
|
# Deploy with zero downtime
|
|
- |
|
|
if [ "$CURRENT_CONTAINER_RUNNING" = "true" ]; then
|
|
echo "🔄 Performing rolling update..."
|
|
|
|
# Generate unique container name
|
|
TIMESTAMP=$(date +%s)
|
|
TEMP_CONTAINER_NAME="portfolio-app-temp-$TIMESTAMP"
|
|
echo "🔧 Using temporary container name: $TEMP_CONTAINER_NAME"
|
|
|
|
# Clean up any existing temporary containers
|
|
echo "🧹 Cleaning up any existing temporary containers..."
|
|
docker rm -f portfolio-app-new portfolio-app-temp-* portfolio-app-backup || true
|
|
|
|
# Find and remove any containers with portfolio-app in the name (except the main one)
|
|
EXISTING_CONTAINERS=$(docker ps -a --format "table {{.Names}}" | grep "portfolio-app" | grep -v "^portfolio-app$" || true)
|
|
if [ -n "$EXISTING_CONTAINERS" ]; then
|
|
echo "🗑️ Removing existing portfolio-app containers:"
|
|
echo "$EXISTING_CONTAINERS"
|
|
echo "$EXISTING_CONTAINERS" | xargs -r docker rm -f || true
|
|
fi
|
|
|
|
# Also clean up any stopped containers
|
|
docker container prune -f || true
|
|
|
|
# Start new container with unique temporary name
|
|
docker run -d \
|
|
--name $TEMP_CONTAINER_NAME \
|
|
--restart unless-stopped \
|
|
--network portfolio_net \
|
|
-e NODE_ENV=$NODE_ENV \
|
|
-e LOG_LEVEL=$LOG_LEVEL \
|
|
-e DATABASE_URL=postgresql://portfolio_user:portfolio_pass@postgres:5432/portfolio_db?schema=public \
|
|
-e REDIS_URL=redis://redis:6379 \
|
|
-e NEXT_PUBLIC_BASE_URL="$NEXT_PUBLIC_BASE_URL" \
|
|
-e NEXT_PUBLIC_UMAMI_URL="$NEXT_PUBLIC_UMAMI_URL" \
|
|
-e NEXT_PUBLIC_UMAMI_WEBSITE_ID="$NEXT_PUBLIC_UMAMI_WEBSITE_ID" \
|
|
-e MY_EMAIL="$MY_EMAIL" \
|
|
-e MY_INFO_EMAIL="$MY_INFO_EMAIL" \
|
|
-e MY_PASSWORD="$MY_PASSWORD" \
|
|
-e MY_INFO_PASSWORD="$MY_INFO_PASSWORD" \
|
|
-e ADMIN_BASIC_AUTH="$ADMIN_BASIC_AUTH" \
|
|
portfolio-app:latest
|
|
|
|
# Wait for new container to be ready
|
|
echo "⏳ Waiting for new container to be ready..."
|
|
sleep 15
|
|
|
|
# Health check new container
|
|
for i in {1..20}; do
|
|
if docker exec $TEMP_CONTAINER_NAME curl -f http://localhost:3000/api/health > /dev/null 2>&1; then
|
|
echo "✅ New container is healthy!"
|
|
break
|
|
fi
|
|
echo "⏳ Health check attempt $i/20..."
|
|
sleep 3
|
|
done
|
|
|
|
# Stop old container
|
|
echo "🛑 Stopping old container..."
|
|
docker stop portfolio-app || true
|
|
docker rm portfolio-app || true
|
|
|
|
# Rename new container
|
|
docker rename $TEMP_CONTAINER_NAME portfolio-app
|
|
|
|
# Update port mapping
|
|
docker stop portfolio-app
|
|
docker rm portfolio-app
|
|
|
|
# Start with correct port
|
|
docker run -d \
|
|
--name portfolio-app \
|
|
--restart unless-stopped \
|
|
--network portfolio_net \
|
|
-p 3000:3000 \
|
|
-e NODE_ENV=$NODE_ENV \
|
|
-e LOG_LEVEL=$LOG_LEVEL \
|
|
-e DATABASE_URL=postgresql://portfolio_user:portfolio_pass@postgres:5432/portfolio_db?schema=public \
|
|
-e REDIS_URL=redis://redis:6379 \
|
|
-e NEXT_PUBLIC_BASE_URL="$NEXT_PUBLIC_BASE_URL" \
|
|
-e NEXT_PUBLIC_UMAMI_URL="$NEXT_PUBLIC_UMAMI_URL" \
|
|
-e NEXT_PUBLIC_UMAMI_WEBSITE_ID="$NEXT_PUBLIC_UMAMI_WEBSITE_ID" \
|
|
-e MY_EMAIL="$MY_EMAIL" \
|
|
-e MY_INFO_EMAIL="$MY_INFO_EMAIL" \
|
|
-e MY_PASSWORD="$MY_PASSWORD" \
|
|
-e MY_INFO_PASSWORD="$MY_INFO_PASSWORD" \
|
|
-e ADMIN_BASIC_AUTH="$ADMIN_BASIC_AUTH" \
|
|
portfolio-app:latest
|
|
|
|
echo "✅ Rolling update completed!"
|
|
else
|
|
echo "🆕 Fresh deployment..."
|
|
docker compose up -d
|
|
fi
|
|
|
|
# Wait for container to be ready
|
|
- echo "⏳ Waiting for container to be ready..."
|
|
- sleep 15
|
|
|
|
# Health check
|
|
- |
|
|
echo "🏥 Performing health check..."
|
|
for i in {1..40}; do
|
|
if curl -f http://localhost:3000/api/health > /dev/null 2>&1; then
|
|
echo "✅ Application is healthy!"
|
|
break
|
|
fi
|
|
echo "⏳ Health check attempt $i/40..."
|
|
sleep 3
|
|
done
|
|
|
|
# Final verification
|
|
- echo "🔍 Final health verification..."
|
|
- docker ps --filter "name=portfolio-app" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
|
- |
|
|
if curl -f http://localhost:3000/api/health; then
|
|
echo "✅ Health endpoint accessible"
|
|
else
|
|
echo "❌ Health endpoint not accessible"
|
|
exit 1
|
|
fi
|
|
- |
|
|
if curl -f http://localhost:3000/ > /dev/null; then
|
|
echo "✅ Main page is accessible"
|
|
else
|
|
echo "❌ Main page is not accessible"
|
|
exit 1
|
|
fi
|
|
- echo "✅ Deployment successful!"
|
|
|
|
# Cleanup
|
|
- docker image prune -f
|
|
- docker system prune -f
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
environment:
|
|
- NODE_ENV
|
|
- LOG_LEVEL
|
|
- NEXT_PUBLIC_BASE_URL
|
|
- NEXT_PUBLIC_UMAMI_URL
|
|
- NEXT_PUBLIC_UMAMI_WEBSITE_ID
|
|
- MY_EMAIL
|
|
- MY_INFO_EMAIL
|
|
- MY_PASSWORD
|
|
- MY_INFO_PASSWORD
|
|
- ADMIN_BASIC_AUTH
|
|
|
|
volumes:
|
|
node_modules:
|