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:
- Go to your repository settings
- Navigate to Environments
- Create a new environment called “production-approval”
- 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
- Navigate to your repository settings
- Go to Secrets and Variables → Actions
- Click “New repository secret”
- Add your secrets:
STAGING_DEPLOY_KEYPROD_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
- 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 - Scope Secrets Properly
# Scope secrets to specific environments environment: name: production # Secrets from this environment are only available in this job - Rotate Secrets Regularly
- Update deployment keys
- Rotate API tokens
- Change access credentials
Adding Status Checks
To enforce quality gates, add branch protection rules:
- Go to repository settings
- Navigate to Branches
- Add rule for your main branch
- Required status checks:
- build
- deploy-staging
- manual-approval
Monitoring and Debugging
View Workflow Runs
- Go to Actions tab
- Select your workflow
- 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
- Environment Variables
env: NODE_ENV: production APP_VERSION: ${{ github.sha }} - Caching Dependencies
- uses: actions/cache@v3 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - 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
-
Build Failures
- Check Node version matches
- Verify dependencies are locked
- Ensure tests are deterministic
-
Deployment Issues
- Verify environment secrets
- Check deployment service status
- Review access permissions
-
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.