All posts
DevOps & Containers

DevOps CI/CD Pipelines: Automated Testing, Building, and Deployment with GitHub Actions

14 min
Share:
DevOpsCI/CDGitHub ActionsAutomationDockerTesting

Build production-grade CI/CD pipelines with GitHub Actions. Automate testing, security scanning, Docker builds, and deployments to AWS, Azure, and Kubernetes with practical examples.

Modern DevOps requires automated pipelines that test, build, and deploy code reliably. This guide covers building CI/CD pipelines with GitHub Actions, from basic workflows to complex multi-environment deployments.

Basic CI Pipeline: Test and Build

1# .github/workflows/ci.yml
2name: CI Pipeline
3
4on:
5  push:
6    branches: [main, develop]
7  pull_request:
8    branches: [main]
9
10env:
11  NODE_VERSION: 18
12
13jobs:
14  test:
15    runs-on: ubuntu-latest
16    
17    steps:
18      - name: Checkout code
19        uses: actions/checkout@v3
20      
21      - name: Setup Node.js
22        uses: actions/setup-node@v3
23        with:
24          node-version: ${{ env.NODE_VERSION }}
25          cache: 'npm'
26      
27      - name: Install dependencies
28        run: npm ci  # Faster than npm install
29      
30      - name: Run linter
31        run: npm run lint
32      
33      - name: Run tests
34        run: npm test -- --coverage
35      
36      - name: Upload coverage
37        uses: codecov/codecov-action@v3
38        with:
39          file: ./coverage/coverage-final.json
40          fail_ci_if_error: true
41  
42  build:
43    runs-on: ubuntu-latest
44    needs: test  # Only build if tests pass
45    
46    steps:
47      - uses: actions/checkout@v3
48      - uses: actions/setup-node@v3
49        with:
50          node-version: ${{ env.NODE_VERSION }}
51      
52      - run: npm ci
53      - run: npm run build
54      
55      - name: Upload build artifacts
56        uses: actions/upload-artifact@v3
57        with:
58          name: build
59          path: dist/
60          retention-days: 7

Docker Build and Push

1# .github/workflows/docker.yml
2name: Build and Push Docker Image
3
4on:
5  push:
6    branches: [main]
7    tags:
8      - 'v*'
9
10env:
11  REGISTRY: ghcr.io
12  IMAGE_NAME: ${{ github.repository }}
13
14jobs:
15  build-and-push:
16    runs-on: ubuntu-latest
17    permissions:
18      contents: read
19      packages: write
20    
21    steps:
22      - name: Checkout
23        uses: actions/checkout@v3
24      
25      - name: Set up Docker Buildx
26        uses: docker/setup-buildx-action@v2
27      
28      - name: Log in to Container Registry
29        uses: docker/login-action@v2
30        with:
31          registry: ${{ env.REGISTRY }}
32          username: ${{ github.actor }}
33          password: ${{ secrets.GITHUB_TOKEN }}
34      
35      - name: Extract metadata
36        id: meta
37        uses: docker/metadata-action@v4
38        with:
39          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
40          tags: |
41            type=ref,event=branch
42            type=semver,pattern={{version}}
43            type=semver,pattern={{major}}.{{minor}}
44            type=sha,prefix={{branch}}-
45      
46      - name: Build and push
47        uses: docker/build-push-action@v4
48        with:
49          context: .
50          push: true
51          tags: ${{ steps.meta.outputs.tags }}
52          labels: ${{ steps.meta.outputs.labels }}
53          cache-from: type=gha
54          cache-to: type=gha,mode=max
55          platforms: linux/amd64,linux/arm64
56      
57      - name: Run Trivy vulnerability scanner
58        uses: aquasecurity/trivy-action@master
59        with:
60          image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
61          format: 'sarif'
62          output: 'trivy-results.sarif'
63      
64      - name: Upload Trivy results
65        uses: github/codeql-action/upload-sarif@v2
66        with:
67          sarif_file: 'trivy-results.sarif'

Multi-Environment Deployment

1# .github/workflows/deploy.yml
2name: Deploy to Environments
3
4on:
5  push:
6    branches: [main, staging]
7  workflow_dispatch:  # Manual trigger
8    inputs:
9      environment:
10        description: 'Environment to deploy'
11        required: true
12        type: choice
13        options:
14          - staging
15          - production
16
17jobs:
18  deploy-staging:
19    if: github.ref == 'refs/heads/staging' || github.event.inputs.environment == 'staging'
20    runs-on: ubuntu-latest
21    environment:
22      name: staging
23      url: https://staging.example.com
24    
25    steps:
26      - uses: actions/checkout@v3
27      
28      - name: Deploy to Staging
29        run: |
30          echo "Deploying to staging..."
31          # Deploy commands here
32  
33  deploy-production:
34    if: github.ref == 'refs/heads/main' || github.event.inputs.environment == 'production'
35    runs-on: ubuntu-latest
36    environment:
37      name: production
38      url: https://example.com
39    needs: [test, security-scan]  # Gates before production
40    
41    steps:
42      - uses: actions/checkout@v3
43      
44      - name: Deploy to Production
45        run: |
46          echo "Deploying to production..."
47          # Deploy commands here
48      
49      - name: Create release
50        uses: actions/create-release@v1
51        env:
52          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
53        with:
54          tag_name: v${{ github.run_number }}
55          release_name: Release v${{ github.run_number }}
56          draft: false
57          prerelease: false
58      
59      - name: Notify Slack
60        uses: 8398a7/action-slack@v3
61        if: always()
62        with:
63          status: ${{ job.status }}
64          text: 'Production deployment ${{ job.status }}'
65          webhook_url: ${{ secrets.SLACK_WEBHOOK }}

AWS Deployment Pipeline

1# .github/workflows/aws-deploy.yml
2name: Deploy to AWS
3
4on:
5  push:
6    branches: [main]
7
8jobs:
9  deploy-to-ecs:
10    runs-on: ubuntu-latest
11    
12    steps:
13      - uses: actions/checkout@v3
14      
15      - name: Configure AWS credentials
16        uses: aws-actions/configure-aws-credentials@v2
17        with:
18          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
19          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
20          aws-region: us-east-1
21      
22      - name: Login to Amazon ECR
23        id: login-ecr
24        uses: aws-actions/amazon-ecr-login@v1
25      
26      - name: Build and push image
27        env:
28          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
29          IMAGE_TAG: ${{ github.sha }}
30        run: |
31          docker build -t $ECR_REGISTRY/my-app:$IMAGE_TAG .
32          docker push $ECR_REGISTRY/my-app:$IMAGE_TAG
33      
34      - name: Update ECS service
35        run: |
36          aws ecs update-service \
37            --cluster my-cluster \
38            --service my-service \
39            --force-new-deployment
40      
41      - name: Wait for deployment
42        run: |
43          aws ecs wait services-stable \
44            --cluster my-cluster \
45            --services my-service
46      
47  deploy-lambda:
48    runs-on: ubuntu-latest
49    
50    steps:
51      - uses: actions/checkout@v3
52      - uses: actions/setup-node@v3
53      
54      - name: Install dependencies
55        run: npm ci --production
56      
57      - name: Package Lambda
58        run: zip -r function.zip .
59      
60      - name: Configure AWS credentials
61        uses: aws-actions/configure-aws-credentials@v2
62        with:
63          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
64          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
65          aws-region: us-east-1
66      
67      - name: Deploy to Lambda
68        run: |
69          aws lambda update-function-code \
70            --function-name my-function \
71            --zip-file fileb://function.zip
72      
73      - name: Publish new version
74        run: |
75          aws lambda publish-version \
76            --function-name my-function
77      
78      - name: Update alias
79        run: |
80          VERSION=$(aws lambda list-versions-by-function \
81            --function-name my-function \
82            --query 'Versions[-1].Version' --output text)
83          
84          aws lambda update-alias \
85            --function-name my-function \
86            --name production \
87            --function-version $VERSION

Security Scanning

1# .github/workflows/security.yml
2name: Security Scans
3
4on:
5  push:
6    branches: [main, develop]
7  schedule:
8    - cron: '0 0 * * 0'  # Weekly
9
10jobs:
11  dependency-check:
12    runs-on: ubuntu-latest
13    
14    steps:
15      - uses: actions/checkout@v3
16      
17      - name: Run npm audit
18        run: npm audit --audit-level=high
19      
20      - name: Check for known vulnerabilities
21        uses: snyk/actions/node@master
22        env:
23          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
24  
25  code-scanning:
26    runs-on: ubuntu-latest
27    
28    steps:
29      - uses: actions/checkout@v3
30      
31      - name: Initialize CodeQL
32        uses: github/codeql-action/init@v2
33        with:
34          languages: javascript
35      
36      - name: Perform CodeQL Analysis
37        uses: github/codeql-action/analyze@v2
38  
39  secrets-scanning:
40    runs-on: ubuntu-latest
41    
42    steps:
43      - uses: actions/checkout@v3
44        with:
45          fetch-depth: 0
46      
47      - name: Gitleaks scan
48        uses: gitleaks/gitleaks-action@v2
49        env:
50          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Best Practices

  • Use environment-specific secrets (staging vs production)
  • Implement approval gates for production deployments
  • Cache dependencies to speed up workflows (setup-node cache option)
  • Use matrix strategy to test multiple Node/Python versions
  • Set workflow-level timeouts to prevent hanging jobs
  • Use concurrency controls to cancel outdated runs
  • Store artifacts for debugging failed builds
  • Monitor workflow costs (Actions minutes) and optimize
  • Use self-hosted runners for private code or faster builds
  • Implement blue-green or canary deployments for zero-downtime
  • Use deployment protection rules for critical environments
  • Tag images with git sha for traceability
  • Run security scans on every PR before merge
  • Use composite actions to reuse workflow logic
  • Implement automatic rollback on deployment failure