Skip to main content

CI/CD Best Practices: Panduan Lengkap untuk Production Pipeline

ยท 7 min read
Ahmad Ardiansyah
DevOps Engineer

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 validation
  • cd.yml - Deployment
  • release.yml - Release management
  • security.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โ€‹

  1. โœ… Automate Everything - Manual steps introduce errors
  2. โœ… Test Thoroughly - Catch issues before production
  3. โœ… Secure by Default - Security is not optional
  4. โœ… Monitor Constantly - Know what's happening
  5. โœ… Document Well - Future you will thank you
  6. โœ… Fail Fast - Quick feedback is crucial
  7. โœ… Version Control - Everything should be versioned
  8. โœ… Keep It Simple - Complexity is the enemy
  9. โœ… Measure Impact - Track metrics that matter
  10. โœ… 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