Studi Kasus: Implementasi CI/CD Docusaurus
Pengenalan
Dokumen ini menyajikan studi kasus implementasi lengkap CI/CD untuk project Docusaurus menggunakan Gitea, Gitea Runner, dan Kubernetes. Studi kasus ini mencakup real-world scenario, challenges yang dihadapi, dan solutions yang diimplementasikan.
Project Overview
Deskripsi Project
Project Name: Gitea Documentation Site
Technology Stack: Docusaurus, React, TypeScript
Infrastructure: Kubernetes Cluster
CI/CD Platform: Gitea + Gitea Runner
Container Registry: Docker Hub
Requirements
Functional Requirements
- Automated build on every commit
- Automated testing (unit, integration)
- Docker image creation and publishing
- Multi-environment deployment (dev, staging, production)
- Zero-downtime deployment
- Automatic rollback on failure
Non-Functional Requirements
- Build time < 5 minutes
- Deployment time < 3 minutes
- 99.9% uptime
- SSL/TLS support
- Auto-scaling capability
- Security scanning
Arsitektur Solution
High-Level Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Developer │
└────────────────────────┬────────────────────────────────────────┘
│ git push
▼
┌─────────────────────────────────────────────────────────────────┐
│ Gitea Server │
│ - Repository hosting │
│ - Webhook triggers │
│ - Actions orchestration │
└────────────────────────┬────────────────────────────────────────┘
│ Trigger workflow
▼
┌─────────────────────────────────────────────────────────────────┐
│ Gitea Runner (3 instances) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Runner 1 │ │ Runner 2 │ │ Runner 3 │ │
│ │ (Build/Test)│ │ (Docker) │ │ (Deploy) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└────────────────────────┬────────────────────────────────────────┘
│ Build & Push
▼
┌─────────────────────────────────────────────────────────────────┐
│ Docker Hub Registry │
│ - Image storage │
│ - Version tagging │
│ - Image scanning │
└────────────────────────┬────────────────────────────────────────┘
│ Pull image
▼
┌─────────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ Development │ │ Staging │ │ Production │ │
│ │ Namespace │ │ Namespace │ │ Namespace │ │
│ │ │ │ │ │ │ │
│ │ ┌───┐ ┌───┐ │ │ ┌───┐ ┌───┐ │ │ ┌───┐ ┌───┐ │ │
│ │ │Pod│ │Pod│ │ │ │Pod│ │Pod│ │ │ │Pod│ │Pod│ │ │
│ │ └───┘ └───┘ │ │ └───┘ └───┘ │ │ └───┘ └───┘ │ │
│ └───────────────┘ └───────────────┘ └───────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ End Users │
│ https://docs.example.com (Production) │
│ https://staging.docs.example.com (Staging) │
└─────────────────────────────────────────────────────────────────┘
Technology Stack Details
| Component | Technology | Version | Purpose |
|---|---|---|---|
| SCM | Gitea | 1.21+ | Source control |
| CI/CD Runner | Gitea Runner | 0.2+ | Job execution |
| Build Tool | npm | 10.x | Package management |
| Runtime | Node.js | 20.x | JavaScript runtime |
| Framework | Docusaurus | 3.x | Documentation site |
| Container | Docker | 24.x | Containerization |
| Orchestration | Kubernetes | 1.28+ | Container orchestration |
| Web Server | Nginx | 1.25 | Static file serving |
| Ingress | Nginx Ingress | 1.9+ | Load balancing |
| TLS | Cert-Manager | 1.13+ | SSL certificates |
Implementation Steps
Phase 1: Repository Setup
1.1 Inisialisasi Repository
# Clone repository
git clone https://gitea.example.com/username/gitea-docs.git
cd gitea-docs
# Inisialisasi Node.js project
npm init -y
npx create-docusaurus@latest . classic --typescript
# Install dependencies
npm install
# Test local development
npm start
1.2 Struktur Directory
# Buat directory structure
mkdir -p .gitea/workflows
mkdir -p k8s/{base,overlays/{dev,staging,production}}
mkdir -p scripts
# Buat Dockerfile
touch Dockerfile
touch .dockerignore
1.3 Git Configuration
# .gitignore
cat > .gitignore << 'EOF'
# Dependencies
node_modules/
.pnp
.pnp.js
# Production
build/
.docusaurus/
.cache/
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# IDE
.idea/
.vscode/
*.swp
*.swo
EOF
# Commit initial structure
git add .
git commit -m "Initial project structure"
git push origin main
Phase 2: CI/CD Pipeline Setup
2.1 Build Workflow
File: .gitea/workflows/ci.yml
name: Continuous Integration
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: '20'
jobs:
lint:
name: Lint Code
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint --if-present
continue-on-error: true
typecheck:
name: TypeScript Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- name: Type check
run: npm run typecheck
build:
name: Build Application
runs-on: ubuntu-latest
needs: [lint, typecheck]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build site
run: npm run build
env:
NODE_ENV: production
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-${{ github.sha }}
path: build/
retention-days: 7
- name: Generate build report
run: |
echo "## Build Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Commit**: \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
echo "- **Branch**: \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY
echo "- **Build Size**: \`$(du -sh build/ | cut -f1)\`" >> $GITHUB_STEP_SUMMARY
echo "- **Node Version**: \`${{ env.NODE_VERSION }}\`" >> $GITHUB_STEP_SUMMARY
2.2 Docker Build Workflow
File: .gitea/workflows/docker.yml
name: Docker Build & Push
on:
push:
branches: [main]
tags: ['v*']
workflow_dispatch:
env:
REGISTRY: docker.io
IMAGE_NAME: myusername/gitea-docs
jobs:
docker:
name: Build and Push Docker Image
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=sha,prefix={{branch}}-
type=semver,pattern={{version}}
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
- name: Scan image
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
format: 'table'
exit-code: '0'
severity: 'CRITICAL,HIGH'
2.3 Deployment Workflow
File: .gitea/workflows/deploy.yml
name: Deploy to Kubernetes
on:
workflow_run:
workflows: ["Docker Build & Push"]
types: [completed]
branches: [main]
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
default: 'staging'
type: choice
options:
- staging
- production
jobs:
deploy:
name: Deploy to ${{ inputs.environment || 'staging' }}
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
environment:
name: ${{ inputs.environment || 'staging' }}
url: https://${{ inputs.environment || 'staging' }}.docs.example.com
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup kubectl
uses: azure/setup-kubectl@v3
with:
version: 'v1.28.0'
- name: Configure kubectl
run: |
mkdir -p $HOME/.kube
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > $HOME/.kube/config
chmod 600 $HOME/.kube/config
- name: Verify cluster
run: |
kubectl cluster-info
kubectl get nodes
- name: Deploy application
run: |
ENV="${{ inputs.environment || 'staging' }}"
IMAGE_TAG="${{ github.sha }}"
# Update deployment image
kubectl set image deployment/gitea-docs \
gitea-docs=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main-${IMAGE_TAG} \
-n gitea-docs-${ENV}
# Wait for rollout
kubectl rollout status deployment/gitea-docs \
-n gitea-docs-${ENV} \
--timeout=5m
- name: Verify deployment
run: |
ENV="${{ inputs.environment || 'staging' }}"
kubectl get pods -n gitea-docs-${ENV}
kubectl get services -n gitea-docs-${ENV}
- name: Run smoke tests
run: |
ENV="${{ inputs.environment || 'staging' }}"
URL="https://${ENV}.docs.example.com"
# Health check
curl -f ${URL}/health || exit 1
# Homepage check
STATUS=$(curl -o /dev/null -s -w "%{http_code}" ${URL})
if [ $STATUS -ne 200 ]; then
echo "Smoke test failed: HTTP $STATUS"
exit 1
fi
echo "✅ Deployment successful!"
- name: Rollback on failure
if: failure()
run: |
ENV="${{ inputs.environment || 'staging' }}"
echo "❌ Deployment failed, rolling back..."
kubectl rollout undo deployment/gitea-docs -n gitea-docs-${ENV}
Phase 3: Kubernetes Configuration
3.1 Namespace
File: k8s/base/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: gitea-docs-staging
labels:
environment: staging
---
apiVersion: v1
kind: Namespace
metadata:
name: gitea-docs-production
labels:
environment: production
3.2 Deployment
File: k8s/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitea-docs
labels:
app: gitea-docs
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: gitea-docs
template:
metadata:
labels:
app: gitea-docs
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001
containers:
- name: gitea-docs
image: docker.io/myusername/gitea-docs:latest
imagePullPolicy: Always
ports:
- name: http
containerPort: 80
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 5
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /var/cache/nginx
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}
3.3 Service & Ingress
File: k8s/base/service.yaml
apiVersion: v1
kind: Service
metadata:
name: gitea-docs
labels:
app: gitea-docs
spec:
type: ClusterIP
ports:
- port: 80
targetPort: http
protocol: TCP
selector:
app: gitea-docs
File: k8s/base/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gitea-docs
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- staging.docs.example.com
secretName: gitea-docs-tls
rules:
- host: staging.docs.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: gitea-docs
port:
number: 80
Phase 4: Monitoring & Observability
4.1 Prometheus ServiceMonitor
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: gitea-docs
namespace: gitea-docs-production
spec:
selector:
matchLabels:
app: gitea-docs
endpoints:
- port: http
path: /metrics
interval: 30s
4.2 Grafana Dashboard
{
"dashboard": {
"title": "Gitea Docs - CI/CD Metrics",
"panels": [
{
"title": "Build Success Rate",
"targets": [{
"expr": "sum(rate(workflow_runs_total{status='success'}[5m])) / sum(rate(workflow_runs_total[5m])) * 100"
}]
},
{
"title": "Deployment Frequency",
"targets": [{
"expr": "sum(rate(deployments_total[1h]))"
}]
},
{
"title": "Pod Availability",
"targets": [{
"expr": "kube_deployment_status_replicas_available{deployment='gitea-docs'}"
}]
}
]
}
}
Challenges & Solutions
Challenge 1: Build Time Optimization
Problem: Initial build time ~10 minutes
Root Cause:
- No caching strategy
- Sequential job execution
- Large node_modules
Solution:
# Implement caching
- uses: actions/cache@v3
with:
path: |
~/.npm
node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
# Parallel jobs
jobs:
lint:
runs-on: ubuntu-latest
typecheck:
runs-on: ubuntu-latest
# Jobs run in parallel
Result: Build time reduced to ~3 minutes (70% improvement)
Challenge 2: Zero-Downtime Deployment
Problem: Brief downtime during deployments
Root Cause:
- All pods updated simultaneously
- No readiness checks
Solution:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0 # Never take down running pods
readinessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 10
Result: True zero-downtime deployments achieved
Challenge 3: Docker Image Size
Problem: Image size 500MB+
Root Cause:
- Including build dependencies
- No multi-stage build
- Large base images
Solution:
# Multi-stage build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
Result: Image size reduced to 50MB (90% reduction)
Challenge 4: Secret Management
Problem: Hardcoded credentials
Solution:
- Use Gitea repository secrets
- Kubernetes secrets for sensitive data
- Separate secrets per environment
# Add secrets via Gitea UI
Repository → Settings → Secrets → Add Secret
# Or via kubectl
kubectl create secret generic app-secrets \
--from-literal=api-key=xxx \
-n gitea-docs-production
Challenge 5: Failed Deployment Recovery
Problem: Manual intervention needed for rollback
Solution:
- name: Automatic rollback
if: failure()
run: |
kubectl rollout undo deployment/gitea-docs
kubectl rollout status deployment/gitea-docs
Result: Automatic recovery without manual intervention
Metrics & Results
Performance Metrics
| Metric | Before | After | Improvement |
|---|---|---|---|
| Build Time | 10 min | 3 min | 70% faster |
| Deployment Time | 5 min | 2 min | 60% faster |
| Image Size | 500 MB | 50 MB | 90% smaller |
| Pipeline Success Rate | 85% | 98% | 13% increase |
| MTTR (Mean Time to Recovery) | 30 min | 2 min | 93% faster |
Business Impact
- Deployment Frequency: 2x/week → 10x/day
- Lead Time: 2 days → 30 minutes
- Change Failure Rate: 15% → 2%
- Team Productivity: +40%
- Cost Savings: 30% reduction in infrastructure costs
Lessons Learned
What Went Well
- Incremental Approach: Implemented features gradually
- Documentation: Comprehensive docs helped team adoption
- Automation: Reduced manual errors significantly
- Monitoring: Early detection of issues
- Rollback Strategy: Confidence to deploy frequently
What Could Be Improved
- Testing Coverage: Need more integration tests
- Cost Monitoring: Better visibility into runner costs
- Alert Tuning: Reduce false positives
- Documentation: Keep docs in sync with changes
- Security Scanning: Earlier in the pipeline
Best Practices Adopted
- ✅ Infrastructure as Code: All configs in Git
- ✅ Immutable Infrastructure: Never modify running containers
- ✅ Security First: Scan early and often
- ✅ Observability: Comprehensive monitoring and logging
- ✅ GitOps: Git as single source of truth
Future Improvements
Short Term (1-3 months)
- Implement canary deployments
- Add performance testing to pipeline
- Setup automated dependency updates
- Implement blue-green deployment
- Add chaos engineering tests
Medium Term (3-6 months)
- Multi-region deployment
- Advanced monitoring with AI/ML
- Cost optimization automation
- Self-healing infrastructure
- Progressive delivery
Long Term (6-12 months)
- GitOps with ArgoCD/Flux
- Service mesh (Istio/Linkerd)
- Advanced observability (OpenTelemetry)
- Policy as Code (OPA)
- Full disaster recovery automation
Conclusion
Implementasi CI/CD dengan Gitea, Gitea Runner, dan Kubernetes untuk project Docusaurus ini berhasil mencapai:
Technical Achievements
- ✅ Fully automated CI/CD pipeline
- ✅ Zero-downtime deployments
- ✅ Automatic rollback capability
- ✅ Multi-environment support
- ✅ Comprehensive monitoring
Business Benefits
- 💰 30% cost reduction
- ⚡ 70% faster delivery
- 🎯 98% pipeline success rate
- 🔒 Enhanced security posture
- 📈 Improved team productivity
Key Takeaways
- Start Simple: Begin with basic pipeline, iterate
- Automate Everything: Manual processes are error-prone
- Monitor Closely: Can't improve what you don't measure
- Fail Fast: Quick feedback loops are crucial
- Document Well: Good docs enable team scaling
Pipeline ini menyediakan foundation yang solid untuk continuous delivery dengan reliability, security, dan efficiency yang tinggi. The combination of Gitea's lightweight nature, Kubernetes' orchestration power, and proper CI/CD practices creates a robust system capable of handling production workloads at scale.
Resources
Documentation
Tools Used
- Gitea Runner: https://gitea.com/gitea/act_runner
- kubectl: https://kubernetes.io/docs/reference/kubectl/
- Docker: https://docs.docker.com/
Community
- Gitea Discord: https://discord.gg/gitea
- Kubernetes Slack: https://slack.k8s.io/