1- name : Deploy Vite React App
1+ # .github/workflows/cicd-pipeline.yml
2+ name : Complete CI/CD Pipeline
23
34on :
45 push :
5- branches :
6- - main
6+ branches : [ main, develop ]
7+ pull_request :
8+ branches : [ main, develop ]
9+ workflow_dispatch :
10+
11+ env :
12+ IMAGE_NAME : vite-react-app
13+ NAMESPACE : production
714
815jobs :
9- deploy :
16+ test :
17+ name : Run Tests
1018 runs-on : self-hosted
1119
1220 steps :
@@ -16,57 +24,165 @@ jobs:
1624 - name : Setup Node.js
1725 uses : actions/setup-node@v4
1826 with :
19- node-version : ' 18 '
27+ node-version : ' 20 '
2028 cache : ' npm'
2129
2230 - name : Install dependencies
2331 run : npm ci
2432
25- - name : Build project
26- run : npm run build
27- env :
28- NODE_ENV : production
33+ - name : Run linter
34+ run : npm run lint || echo "Linting step skipped"
35+ continue-on-error : true
36+
37+ - name : Run tests
38+ run : npm test
39+
40+ - name : Generate coverage report
41+ run : npm test -- --coverage
42+ continue-on-error : true
43+
44+ - name : Upload coverage
45+ uses : actions/upload-artifact@v4
46+ if : always()
47+ with :
48+ name : coverage-report
49+ path : coverage/
50+ retention-days : 30
51+
52+ build-and-deploy :
53+ name : Build & Deploy to MicroK8s
54+ runs-on : self-hosted
55+ needs : test
56+ if : github.event_name == 'push'
57+
58+ steps :
59+ - name : Checkout code
60+ uses : actions/checkout@v4
61+
62+ - name : Set image tag
63+ id : image-tag
64+ run : |
65+ TAG="${{ env.IMAGE_NAME }}:${{ github.sha }}"
66+ echo "tag=$TAG" >> $GITHUB_OUTPUT
67+ echo "Image tag: $TAG"
68+
69+ - name : Build Docker image in MicroK8s
70+ run : |
71+ # Build image with Docker and import to MicroK8s
72+ docker build -t ${{ steps.image-tag.outputs.tag }} .
73+ docker save ${{ steps.image-tag.outputs.tag }} > /tmp/app-image.tar
74+ microk8s ctr image import /tmp/app-image.tar
75+ rm /tmp/app-image.tar
76+
77+ # Tag as latest
78+ docker tag ${{ steps.image-tag.outputs.tag }} ${{ env.IMAGE_NAME }}:latest
79+ docker save ${{ env.IMAGE_NAME }}:latest > /tmp/app-image-latest.tar
80+ microk8s ctr image import /tmp/app-image-latest.tar
81+ rm /tmp/app-image-latest.tar
82+
83+ - name : Verify image in MicroK8s
84+ run : |
85+ echo "Images available in MicroK8s:"
86+ microk8s ctr images ls | grep ${{ env.IMAGE_NAME }}
87+
88+ - name : Setup kubectl
89+ run : |
90+ mkdir -p ~/.kube
91+ microk8s config > ~/.kube/config
92+
93+ - name : Create namespace if not exists
94+ run : |
95+ microk8s kubectl create namespace ${{ env.NAMESPACE }} --dry-run=client -o yaml | microk8s kubectl apply -f -
96+
97+ - name : Update deployment manifest with new image
98+ run : |
99+ sed -i "s|image:.*${{ env.IMAGE_NAME }}.*|image: ${{ steps.image-tag.outputs.tag }}|g" k8s/deployment.yaml
29100
30- - name : Backup existing deployment
101+ - name : Apply Kubernetes manifests
31102 run : |
32- DEPLOY_DIR="/var/www/your-app"
33- if [ -d "$DEPLOY_DIR" ]; then
34- BACKUP_DIR="${DEPLOY_DIR}.backup.$(date +%Y%m%d_%H%M%S)"
35- echo "Creating backup at $BACKUP_DIR"
36- sudo cp -r "$DEPLOY_DIR" "$BACKUP_DIR"
37- ls -dt ${DEPLOY_DIR}.backup.* | tail -n +6 | xargs -r sudo rm -rf
38- fi
103+ microk8s kubectl apply -f k8s/ --namespace=${{ env.NAMESPACE }}
39104
40- - name : Deploy to server
105+ - name : Wait for rollout
41106 run : |
42- DEPLOY_DIR="/var/www/your-app"
43- echo "Deploying to $DEPLOY_DIR"
44- sudo mkdir -p "$DEPLOY_DIR"
45- sudo rm -rf "$DEPLOY_DIR"/*
46- sudo cp -r ./dist/* "$DEPLOY_DIR"/
47- sudo chown -R www-data:www-data "$DEPLOY_DIR"
48- sudo chmod -R 755 "$DEPLOY_DIR"
107+ microk8s kubectl rollout status deployment/vite-react-app \
108+ --namespace=${{ env.NAMESPACE }} \
109+ --timeout=5m
49110
50111 - name : Verify deployment
51112 run : |
52- DEPLOY_DIR="/var/www/your-app"
53- if [ -f "$DEPLOY_DIR/index.html" ]; then
54- echo "✅ Deployment successful!"
55- echo "Files deployed:"
56- ls -lah "$DEPLOY_DIR"
57- else
58- echo "❌ Deployment failed - index.html not found"
59- exit 1
60- fi
61-
62- - name : Deployment summary
63- if : always()
113+ echo "Pods:"
114+ microk8s kubectl get pods --namespace=${{ env.NAMESPACE }}
115+ echo ""
116+ echo "Services:"
117+ microk8s kubectl get services --namespace=${{ env.NAMESPACE }}
118+ echo ""
119+ echo "Deployment:"
120+ microk8s kubectl get deployment --namespace=${{ env.NAMESPACE }}
121+
122+ - name : Get service URL
123+ id : service-url
124+ run : |
125+ NODE_PORT=$(microk8s kubectl get service vite-react-app-service -n ${{ env.NAMESPACE }} -o jsonpath='{.spec.ports[0].nodePort}')
126+ NODE_IP=$(microk8s kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
127+ echo "url=http://$NODE_IP:$NODE_PORT" >> $GITHUB_OUTPUT
128+ echo "Application URL: http://$NODE_IP:$NODE_PORT"
129+
130+ - name : Deployment Summary
131+ run : |
132+ NODE_PORT=$(microk8s kubectl get service vite-react-app-service -n ${{ env.NAMESPACE }} -o jsonpath='{.spec.ports[0].nodePort}')
133+ NODE_IP=$(microk8s kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
134+
135+ echo "## Deployment Status :rocket:" >> $GITHUB_STEP_SUMMARY
136+ echo "" >> $GITHUB_STEP_SUMMARY
137+ echo "**Namespace:** ${{ env.NAMESPACE }}" >> $GITHUB_STEP_SUMMARY
138+ echo "**Image:** ${{ steps.image-tag.outputs.tag }}" >> $GITHUB_STEP_SUMMARY
139+ echo "**Commit:** ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
140+ echo "**URL:** http://$NODE_IP:$NODE_PORT" >> $GITHUB_STEP_SUMMARY
141+ echo "" >> $GITHUB_STEP_SUMMARY
142+ echo "### Pods Status" >> $GITHUB_STEP_SUMMARY
143+ echo '```' >> $GITHUB_STEP_SUMMARY
144+ microk8s kubectl get pods --namespace=${{ env.NAMESPACE }} >> $GITHUB_STEP_SUMMARY
145+ echo '```' >> $GITHUB_STEP_SUMMARY
146+
147+ - name : Cleanup old images
148+ run : |
149+ # Keep only the last 5 images
150+ microk8s ctr images ls | grep ${{ env.IMAGE_NAME }} | grep -v latest | awk '{print $1}' | sort -r | tail -n +6 | xargs -r microk8s ctr images rm || true
151+
152+ rollback :
153+ name : Rollback on Failure
154+ runs-on : self-hosted
155+ needs : build-and-deploy
156+ if : failure()
157+
158+ steps :
159+ - name : Setup kubectl
160+ run : |
161+ mkdir -p ~/.kube
162+ microk8s config > ~/.kube/config
163+
164+ - name : Rollback deployment
165+ run : |
166+ microk8s kubectl rollout undo deployment/vite-react-app \
167+ --namespace=${{ env.NAMESPACE }}
168+
169+ - name : Wait for rollback
170+ run : |
171+ microk8s kubectl rollout status deployment/vite-react-app \
172+ --namespace=${{ env.NAMESPACE }} \
173+ --timeout=3m
174+
175+ - name : Verify rollback
176+ run : |
177+ microk8s kubectl get pods --namespace=${{ env.NAMESPACE }}
178+
179+ - name : Rollback Summary
64180 run : |
65- echo "=========================================="
66- echo "Deployment Summary"
67- echo "=========================================="
68- echo "Branch: ${{ github.ref_name }}"
69- echo "Commit: ${{ github.sha }}"
70- echo "Triggered by: ${{ github.actor }}"
71- echo "Status: ${{ job.status }}"
72- echo "=========================================="
181+ echo "## Rollback Completed :warning:" >> $GITHUB_STEP_SUMMARY
182+ echo "" >> $GITHUB_STEP_SUMMARY
183+ echo "Deployment has been rolled back to previous version" >> $GITHUB_STEP_SUMMARY
184+ echo "" >> $GITHUB_STEP_SUMMARY
185+ echo "### Current Pods" >> $GITHUB_STEP_SUMMARY
186+ echo '```' >> $GITHUB_STEP_SUMMARY
187+ microk8s kubectl get pods --namespace= ${{ env.NAMESPACE }} >> $GITHUB_STEP_SUMMARY
188+ echo '```' >> $GITHUB_STEP_SUMMARY
0 commit comments