name: CI/CD Pipeline on: push: branches: [ main, production ] pull_request: branches: [ main, production ] env: NODE_VERSION: '20' DOCKER_IMAGE: portfolio-app CONTAINER_NAME: portfolio-app jobs: test: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Run linting run: npm run lint - name: Run tests run: npm run test - name: Build application run: npm run build security: runs-on: ubuntu-latest needs: test steps: - name: Checkout code uses: actions/checkout@v4 - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@0.30.0 with: scan-type: 'fs' scan-ref: '.' format: 'table' output: 'trivy-results.txt' timeout: '10m' ignore-unfixed: true severity: 'CRITICAL,HIGH' continue-on-error: true - name: Run npm audit as fallback if: failure() run: | echo "Trivy failed, running npm audit as fallback..." npm audit --audit-level=high || true echo "Security scan completed with fallback method" - name: Upload Trivy scan results uses: actions/upload-artifact@v4 if: always() with: name: trivy-results path: trivy-results.txt retention-days: 7 build: runs-on: ubuntu-latest needs: test if: github.ref == 'refs/heads/production' steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build Docker image run: | docker build -t ${{ env.DOCKER_IMAGE }}:latest . docker tag ${{ env.DOCKER_IMAGE }}:latest ${{ env.DOCKER_IMAGE }}:$(date +%Y%m%d-%H%M%S) - name: Save Docker image run: | docker save ${{ env.DOCKER_IMAGE }}:latest | gzip > ${{ env.DOCKER_IMAGE }}.tar.gz - name: Upload Docker image artifact uses: actions/upload-artifact@v4 with: name: docker-image path: ${{ env.DOCKER_IMAGE }}.tar.gz retention-days: 7 deploy: runs-on: ubuntu-latest needs: build if: github.ref == 'refs/heads/production' steps: - name: Checkout code uses: actions/checkout@v4 - name: Download Docker image artifact uses: actions/download-artifact@v4 with: name: docker-image path: ./ - name: Load Docker image run: | gunzip -c ${{ env.DOCKER_IMAGE }}.tar.gz | docker load - name: Stop existing container run: | docker stop ${{ env.CONTAINER_NAME }} || true docker rm ${{ env.CONTAINER_NAME }} || true - name: Start new container run: | docker run -d \ --name ${{ env.CONTAINER_NAME }} \ --restart unless-stopped \ -p 3000:3000 \ -e NODE_ENV=production \ ${{ env.DOCKER_IMAGE }}:latest - name: Wait for container to be ready run: | sleep 10 timeout 60 bash -c 'until curl -f http://localhost:3000/api/health; do sleep 2; done' - name: Health check run: | curl -f http://localhost:3000/api/health echo "✅ Deployment successful!" - name: Cleanup old images run: | docker image prune -f docker system prune -f