GitHub Actions in Practice: A Complete CI/CD Pipeline Demo
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_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
- Never Log Secrets
```yaml
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 ```yaml
env: NODE_ENV: production APP_VERSION: ${{ github.sha }}
2. **Caching Dependencies**
```yaml
- uses: actions/cache@v3
with:
path: ~/.npm
key: $-node-$
- 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.