Skip to main content

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

ComponentTechnologyVersionPurpose
SCMGitea1.21+Source control
CI/CD RunnerGitea Runner0.2+Job execution
Build Toolnpm10.xPackage management
RuntimeNode.js20.xJavaScript runtime
FrameworkDocusaurus3.xDocumentation site
ContainerDocker24.xContainerization
OrchestrationKubernetes1.28+Container orchestration
Web ServerNginx1.25Static file serving
IngressNginx Ingress1.9+Load balancing
TLSCert-Manager1.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

MetricBeforeAfterImprovement
Build Time10 min3 min70% faster
Deployment Time5 min2 min60% faster
Image Size500 MB50 MB90% smaller
Pipeline Success Rate85%98%13% increase
MTTR (Mean Time to Recovery)30 min2 min93% 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

  1. Incremental Approach: Implemented features gradually
  2. Documentation: Comprehensive docs helped team adoption
  3. Automation: Reduced manual errors significantly
  4. Monitoring: Early detection of issues
  5. Rollback Strategy: Confidence to deploy frequently

What Could Be Improved

  1. Testing Coverage: Need more integration tests
  2. Cost Monitoring: Better visibility into runner costs
  3. Alert Tuning: Reduce false positives
  4. Documentation: Keep docs in sync with changes
  5. Security Scanning: Earlier in the pipeline

Best Practices Adopted

  1. Infrastructure as Code: All configs in Git
  2. Immutable Infrastructure: Never modify running containers
  3. Security First: Scan early and often
  4. Observability: Comprehensive monitoring and logging
  5. 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

  1. Start Simple: Begin with basic pipeline, iterate
  2. Automate Everything: Manual processes are error-prone
  3. Monitor Closely: Can't improve what you don't measure
  4. Fail Fast: Quick feedback loops are crucial
  5. 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

Community