Post

GitHub Actions in Practice: A Complete CI/CD Pipeline Demo

A hands-on guide to building a complete CI/CD pipeline with GitHub Actions, including manual approvals, secrets management, and deployment strategies.

GitHub Actions in Practice: Building a Complete CI/CD Pipeline

In this practical guide, we’ll create a complete CI/CD pipeline using GitHub Actions. We’ll build a Node.js application, run tests, and deploy it to different environments with manual approval gates.

Project Setup

Let’s start with a simple Node.js Express application. Here’s our project structure:

my-node-app/ ├── .github/ │ └── workflows/ │ └── main.yml ├── src/ │ └── app.js ├── tests/ │ └── app.test.js ├── package.json └── Dockerfile

Basic Application Code

// src/app.js const express = require("express"); const app = express(); app.get("/", (req, res) => { res.json({ message: "Hello from my Node.js app!" }); }); module.exports = app;
// tests/app.test.js const request = require("supertest"); const app = require("../src/app"); describe("GET /", () => { it("responds with hello message", async () => { const response = await request(app).get("/"); expect(response.status).toBe(200); expect(response.body.message).toBe("Hello from my Node.js app!"); }); });

The Complete CI/CD Workflow

Here’s our comprehensive workflow that includes building, testing, and deploying with manual approvals:

name: CI/CD Pipeline on: push: branches: [main] pull_request: branches: [main] # Enable manual trigger workflow_dispatch: env: NODE_VERSION: "18.x" jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v3 with: node-version: $ cache: "npm" - name: Install dependencies run: npm ci - name: Run tests run: npm test - name: Build Docker image run: | docker build -t my-node-app:$ . # Save the build artifacts - name: Upload build artifact uses: actions/upload-artifact@v3 with: name: app-build path: . deploy-staging: needs: build runs-on: ubuntu-latest environment: name: staging url: https://staging.myapp.com steps: - name: Download build artifact uses: actions/download-artifact@v3 with: name: app-build - name: Deploy to staging env: STAGING_SECRET: $ run: | echo "Deploying to staging..." # Your deployment commands here manual-approval: needs: deploy-staging runs-on: ubuntu-latest environment: name: production-approval steps: - name: Manual approval step run: echo "Approved for production deployment" deploy-production: needs: manual-approval runs-on: ubuntu-latest environment: name: production url: https://myapp.com steps: - name: Download build artifact uses: actions/download-artifact@v3 with: name: app-build - name: Deploy to production env: PROD_SECRET: $ run: | echo "Deploying to production..." # Your production deployment commands here

Understanding Manual Gates

In our workflow, we’ve implemented a manual approval gate before production deployment using GitHub Environments:

  1. Go to your repository settings
  2. Navigate to Environments
  3. Create a new environment called “production-approval”
  4. Add required reviewers who can approve deployments

The manual-approval job will pause the workflow until an authorized person approves the deployment through the GitHub UI.

Managing Secrets

GitHub Secrets provide secure storage for sensitive information. Here’s how to use them:

Setting Up Secrets

  1. Navigate to your repository settings
  2. Go to Secrets and Variables → Actions
  3. Click “New repository secret”
  4. Add your secrets:
    • STAGING_DEPLOY_KEY
    • PROD_DEPLOY_KEY

Using Secrets in Workflows

steps: - name: Use secret env: MY_SECRET: $ run: | # Your commands here # Never echo or print secrets!

Best Practices for Secrets

  1. Never Log Secrets
# DON'T DO THIS - name: Debug run: echo $ # DO THIS - name: Use secret safely env: SECRET: $ run: ./deploy.sh # Use secret internally
  1. Scope Secrets Properly
# Scope secrets to specific environments environment: name: production # Secrets from this environment are only available in this job
  1. Rotate Secrets Regularly
    • Update deployment keys
    • Rotate API tokens
    • Change access credentials

Adding Status Checks

To enforce quality gates, add branch protection rules:

  1. Go to repository settings
  2. Navigate to Branches
  3. Add rule for your main branch
  4. Required status checks:
    • build
    • deploy-staging
    • manual-approval

Monitoring and Debugging

View Workflow Runs

  1. Go to Actions tab
  2. Select your workflow
  3. View detailed logs and artifacts

Debug with SSH (if needed)

# Add this step to ssh into the runner for debugging steps: - uses: actions/checkout@v4 - name: Setup tmate session uses: mxschmitt/action-tmate@v3 if: ${{ failure() }}

Real-World Considerations

  1. Environment Variables
env: NODE_ENV: production APP_VERSION: ${{ github.sha }}
  1. Caching Dependencies
- uses: actions/cache@v3 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
  1. Notifications
- name: Notify Slack if: always() uses: 8398a7/action-slack@v3 with: status: $ fields: repo,message,commit,author,action,workflow env: SLACK_WEBHOOK_URL: $

Common Issues and Solutions

  1. Build Failures

    • Check Node version matches
    • Verify dependencies are locked
    • Ensure tests are deterministic
  2. Deployment Issues

    • Verify environment secrets
    • Check deployment service status
    • Review access permissions
  3. Manual Approval Timeout

    • Default timeout is 24 hours
    • Can be configured in environment settings

Conclusion

This practical implementation demonstrates a production-ready CI/CD pipeline with GitHub Actions. The workflow includes all essential elements:

  • Automated testing
  • Multi-environment deployment
  • Manual approval gates
  • Secure secrets management
  • Status checks and protection

Remember to adapt the deployment steps to your specific hosting platform (AWS, Azure, GCP, etc.) and add any necessary environment-specific configurations.

Additional Resources

This post is licensed under CC BY 4.0 by the author.