CI/CD Best Practices: Panduan Lengkap untuk Production Pipeline
Membangun CI/CD pipeline yang reliable dan maintainable memerlukan lebih dari sekedar automation. Dalam artikel ini, kita akan explore best practices yang terbukti efektif untuk production environments.
๐ฏ Introductionโ
CI/CD bukan hanya tentang automation - ini tentang membangun kultur continuous improvement dan delivering value kepada users dengan faster dan safer. Mari kita pelajari practices yang akan membuat pipeline Anda production-ready.
1. Pipeline Design Principlesโ
Keep It Simpleโ
โ Bad Practice:
# Monolithic pipeline yang melakukan everything
jobs:
mega-job:
steps:
- checkout
- lint
- test-unit
- test-integration
- test-e2e
- build
- security-scan
- deploy-dev
- deploy-staging
- deploy-prod
โ Good Practice:
# Separate concerns, parallel execution
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm run lint
test:
runs-on: ubuntu-latest
strategy:
matrix:
node: [18, 20, 22]
steps:
- uses: actions/checkout@v3
- run: npm test
build:
needs: [lint, test]
runs-on: ubuntu-latest
steps:
- run: npm run build
Single Responsibilityโ
Setiap workflow atau job harus memiliki single, clear purpose:
ci.yml- Testing dan validationcd.yml- Deploymentrelease.yml- Release managementsecurity.yml- Security scanning
2. Version Everythingโ
Pin Versionsโ
โ Bad Practice:
- uses: actions/checkout@main
- uses: docker/build-push-action@latest
โ Good Practice:
- uses: actions/checkout@v3
- uses: docker/build-push-action@v4.1.1
Semantic Versioningโ
v1.2.3
โ โ โโ PATCH: Bug fixes
โ โโโโ MINOR: New features (backward compatible)
โโโโโโ MAJOR: Breaking changes
3. Security Best Practicesโ
Never Hardcode Secretsโ
โ Extremely Bad:
env:
DATABASE_PASSWORD: "mypassword123"
API_KEY: "sk-1234567890abcdef"
โ Always Use Secrets:
env:
DATABASE_PASSWORD: ${{ secrets.DB_PASSWORD }}
API_KEY: ${{ secrets.API_KEY }}
Scan Everythingโ
jobs:
security:
runs-on: ubuntu-latest
steps:
# Dependency scanning
- name: Run npm audit
run: npm audit --audit-level=moderate
# Container scanning
- name: Scan Docker image
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:latest
severity: CRITICAL,HIGH
# Code scanning
- name: SAST with SonarQube
run: sonar-scanner
Least Privilege Principleโ
permissions:
contents: read # Only read access
packages: write # Write for publishing
security-events: write # For security scanning
4. Testing Strategyโ
Test Pyramidโ
/\
/E2E\ <- Few (5%)
/------\
/ Integ \ <- Some (15%)
/----------\
/ Unit \ <- Many (80%)
/--------------\
Parallel Testingโ
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
test-suite: [unit, integration, e2e]
node-version: [18, 20, 22]
steps:
- run: npm run test:${{ matrix.test-suite }}
Fail Fastโ
strategy:
fail-fast: true # Stop on first failure
matrix:
node: [18, 20, 22]
5. Docker Best Practicesโ
Multi-Stage Buildsโ
# โ
Optimal multi-stage build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production=false
COPY . .
RUN npm run build
FROM nginx:alpine AS production
COPY --from=builder /app/build /usr/share/nginx/html
RUN adduser -D -u 1001 appuser && \
chown -R appuser:appuser /usr/share/nginx/html
USER appuser
EXPOSE 80
Optimize Layersโ
โ Bad - Many layers:
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
RUN apt-get clean
โ Good - Single layer:
RUN apt-get update && \
apt-get install -y curl git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
Use .dockerignoreโ
# .dockerignore
node_modules
.git
.gitignore
*.md
.env*
.DS_Store
coverage/
test/
6. Caching Strategiesโ
Dependency Cachingโ
- name: Cache dependencies
uses: actions/cache@v3
with:
path: |
~/.npm
~/.cache
node_modules
key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-deps-
Docker Layer Cachingโ
- name: Build with cache
uses: docker/build-push-action@v4
with:
context: .
cache-from: type=registry,ref=myapp:buildcache
cache-to: type=registry,ref=myapp:buildcache,mode=max
push: true
tags: myapp:latest
7. Deployment Strategiesโ
Blue-Green Deploymentโ
deploy:
steps:
- name: Deploy to green
run: kubectl apply -f k8s/green/
- name: Wait for ready
run: kubectl rollout status deployment/app-green
- name: Run smoke tests
run: ./scripts/smoke-test.sh green
- name: Switch traffic
run: kubectl patch service app -p '{"spec":{"selector":{"version":"green"}}}'
- name: Cleanup blue
run: kubectl delete deployment app-blue
Canary Deploymentโ
# Start with 10% traffic to new version
- name: Deploy canary
run: |
kubectl scale deployment/app-canary --replicas=1
kubectl scale deployment/app-stable --replicas=9
# Monitor metrics
- name: Monitor
run: ./scripts/monitor-canary.sh
# Gradually increase if metrics good
- name: Increase canary
if: ${{ steps.monitor.outcome == 'success' }}
run: |
kubectl scale deployment/app-canary --replicas=5
kubectl scale deployment/app-stable --replicas=5
8. Monitoring & Observabilityโ
Add Metricsโ
- name: Record deployment metrics
run: |
curl -X POST https://metrics.example.com/api/v1/deployments \
-H "Content-Type: application/json" \
-d '{
"app": "myapp",
"version": "${{ github.sha }}",
"environment": "production",
"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
}'
Structured Loggingโ
- name: Deploy with logs
run: |
echo "::group::Deployment Information"
echo "Commit: ${{ github.sha }}"
echo "Branch: ${{ github.ref_name }}"
echo "Author: ${{ github.actor }}"
echo "Timestamp: $(date -u)"
echo "::endgroup::"
Health Checksโ
- name: Verify deployment
run: |
for i in {1..30}; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://app.example.com/health)
if [ $STATUS -eq 200 ]; then
echo "โ
Health check passed"
exit 0
fi
echo "โณ Waiting for app... ($i/30)"
sleep 10
done
echo "โ Health check failed"
exit 1
9. Error Handlingโ
Retry Logicโ
- name: Deploy with retry
uses: nick-invision/retry@v2
with:
timeout_minutes: 10
max_attempts: 3
retry_wait_seconds: 30
command: kubectl apply -f k8s/
Automatic Rollbackโ
- name: Deploy
id: deploy
run: kubectl apply -f k8s/
continue-on-error: true
- name: Verify
id: verify
run: ./scripts/verify.sh
- name: Rollback on failure
if: steps.verify.outcome == 'failure'
run: |
echo "โ Deployment failed, rolling back..."
kubectl rollout undo deployment/myapp
exit 1
Notificationsโ
- name: Notify on failure
if: failure()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: |
๐จ Deployment Failed!
Commit: ${{ github.sha }}
Author: ${{ github.actor }}
Job: ${{ github.job }}
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
10. Environment Managementโ
Environment Variablesโ
# โ
Environment-specific configs
jobs:
deploy-staging:
environment: staging
env:
API_URL: https://api-staging.example.com
REPLICAS: 2
deploy-production:
environment: production
env:
API_URL: https://api.example.com
REPLICAS: 5
Approval Gatesโ
deploy-production:
environment:
name: production
# Requires manual approval
needs: deploy-staging
if: github.ref == 'refs/heads/main'
11. Code Qualityโ
Pre-commit Checksโ
jobs:
quality:
runs-on: ubuntu-latest
steps:
- name: Lint
run: npm run lint
- name: Format check
run: npm run format:check
- name: Type check
run: npm run typecheck
- name: Security audit
run: npm audit
Code Coverageโ
- name: Run tests with coverage
run: npm test -- --coverage
- name: Upload to Codecov
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
fail_ci_if_error: true
flags: unittests
12. Documentationโ
Pipeline Documentationโ
name: CI Pipeline
# Purpose: Run tests and build on every push
# Triggers: Push to main/develop, PRs
# Artifacts: Test reports, build output
# Notifications: Slack on failure
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
Change Documentationโ
- name: Generate changelog
run: |
git log --oneline --no-merges ${{ github.event.before }}..${{ github.sha }} \
> CHANGELOG.txt
- name: Update docs
run: |
echo "## Deployment $(date)" >> docs/deployments.md
echo "- Version: ${{ github.sha }}" >> docs/deployments.md
echo "- By: ${{ github.actor }}" >> docs/deployments.md
13. Performance Optimizationโ
Parallel Jobsโ
jobs:
lint:
runs-on: ubuntu-latest
test:
runs-on: ubuntu-latest
build:
runs-on: ubuntu-latest
# All run in parallel
Job Dependenciesโ
jobs:
build:
runs-on: ubuntu-latest
test:
needs: build # Waits for build
deploy:
needs: [build, test] # Waits for both
14. Cost Optimizationโ
Conditional Executionโ
jobs:
deploy:
if: github.ref == 'refs/heads/main' # Only on main
expensive-tests:
if: startsWith(github.ref, 'refs/tags/') # Only on tags
Timeout Limitsโ
jobs:
build:
timeout-minutes: 30 # Prevent runaway jobs
runs-on: ubuntu-latest
15. Compliance & Auditโ
Audit Trailโ
- name: Record deployment
run: |
curl -X POST ${{ secrets.AUDIT_API }} \
-H "Authorization: Bearer ${{ secrets.AUDIT_TOKEN }}" \
-d '{
"action": "deployment",
"user": "${{ github.actor }}",
"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'",
"commit": "${{ github.sha }}",
"environment": "production"
}'
Compliance Checksโ
- name: Compliance scan
run: |
# Check required labels
kubectl get pods -l app=myapp,environment=production
# Verify security policies
kubectl auth can-i create pods --as=system:serviceaccount:default:myapp
Key Takeawaysโ
- โ Automate Everything - Manual steps introduce errors
- โ Test Thoroughly - Catch issues before production
- โ Secure by Default - Security is not optional
- โ Monitor Constantly - Know what's happening
- โ Document Well - Future you will thank you
- โ Fail Fast - Quick feedback is crucial
- โ Version Control - Everything should be versioned
- โ Keep It Simple - Complexity is the enemy
- โ Measure Impact - Track metrics that matter
- โ Continuous Improvement - Always iterate
Resourcesโ
Conclusionโ
Building production-ready CI/CD pipelines requires discipline, planning, dan continuous refinement. Start dengan basics, iterate based on feedback, dan always prioritize reliability over speed.
Remember: A good pipeline is one that you can trust.
Happy automating! ๐
Questions or feedback? Leave a comment atau reach out di GitHub Discussions!
Published on February 10, 2024 by Ahmad Ardiansyah