Setting up continuous integration and continuous deployment (CI/CD) is essential for modern software development. GitHub Actions provides a powerful, integrated platform for automating your build, test, and deployment workflows directly within your repository. This guide will walk you through creating a production-ready CI/CD pipeline from scratch.
Understanding GitHub Actions
GitHub Actions is a CI/CD platform that allows you to automate your software development workflows. Every push, pull request, or other GitHub event can trigger automated workflows that build, test, and deploy your code.
Key Concepts
Before diving in, let’s understand the core components:
- Workflow: An automated process defined in YAML that runs in your repository
- Job: A set of steps that execute on the same runner
- Step: An individual task that can run commands or actions
- Action: A reusable unit of code that can be shared across workflows
- Runner: A server that runs your workflows (GitHub-hosted or self-hosted)
Prerequisites
To follow this tutorial, you’ll need:
- A GitHub account and repository
- Basic knowledge of YAML syntax
- A project with tests (we’ll use Node.js as an example)
- Understanding of your deployment target (cloud platform, server, etc.)
Step 1: Create Your First Workflow
Create a .github/workflows directory in your repository root and add a workflow file:
mkdir -p .github/workflows
touch .github/workflows/ci.yml
Here’s a basic CI workflow for a Node.js application:
name: CI Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x, 18.x, 20.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test
- name: Build application
run: npm run build
This workflow:
- Triggers on pushes to
mainanddevelopbranches and on pull requests - Tests against multiple Node.js versions using a matrix strategy
- Runs linting, tests, and builds your application
Step 2: Add Code Quality Checks
Enhance your pipeline with code quality tools:
- name: Run tests with coverage
run: npm test -- --coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/coverage-final.json
fail_ci_if_error: true
Pro Tip: Code coverage tools help identify untested code paths and improve overall code quality.
Step 3: Implement Deployment
Add deployment to your workflow. Here’s an example deploying to AWS:
deploy:
needs: build-and-test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Deploy to AWS
run: |
npm ci
npm run build
aws s3 sync ./build s3://my-app-bucket --delete
[aws cloudfront](https://terabyte.systems/posts/how-to-deploy-react-app-to-aws-s3-cloudfront/) create-invalidation --distribution-id ${{ secrets.CLOUDFRONT_DIST_ID }} --paths "/*"
Step 4: Manage Secrets Securely
Never commit credentials to your repository. Use GitHub Secrets:
- Navigate to your repository settings
- Select Secrets and variables → Actions
- Click New repository secret
- Add your secrets (API keys, credentials, tokens)
Reference secrets in your workflow using ${{ secrets.SECRET_NAME }}.
Step 5: Optimize Your Pipeline
Caching Dependencies
Speed up your workflows by caching dependencies:
- name: Cache node modules
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
Conditional Execution
Run steps only when needed:
- name: Deploy to production
if: github.ref == 'refs/heads/main'
run: npm run deploy
- name: Deploy to staging
if: github.ref == 'refs/heads/develop'
run: npm run deploy:staging
Advanced Patterns
Multi-Stage Pipeline
Separate build, test, and deployment into distinct jobs:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- uses: actions/upload-artifact@v3
with:
name: build-artifact
path: ./build
test:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v3
with:
name: build-artifact
- run: npm test
deploy:
needs: [build, test]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
- run: ./deploy.sh
Reusable Workflows
Create reusable workflows for common patterns:
# .github/workflows/reusable-deploy.yml
on:
workflow_call:
inputs:
environment:
required: true
type: string
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v4
- run: ./deploy.sh ${{ inputs.environment }}
Call it from another workflow:
jobs:
deploy-staging:
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: staging
Comparison: GitHub Actions vs Other CI/CD Tools
| Feature | GitHub Actions | Jenkins | GitLab CI | CircleCI |
|---|---|---|---|---|
| Integration | Native GitHub | Self-hosted | Native GitLab | Third-party |
| Cost | Free tier generous | Free (self-hosted) | Free tier available | Limited free tier |
| Setup Complexity | Low | High | Low | Medium |
| Matrix Builds | Yes | With plugins | Yes | Yes |
| Marketplace | Extensive | Plugins | Limited | Orbs |
Monitoring and Debugging
View Workflow Runs
Monitor your workflows in the Actions tab of your repository. You can:
- View real-time logs
- Re-run failed jobs
- Download artifacts
- Cancel running workflows
Debug Mode
Enable debug logging for troubleshooting:
- name: Debug step
run: echo "Debugging value: ${{ secrets.DEBUG_VALUE }}"
env:
ACTIONS_STEP_DEBUG: true
Best Practices
- Keep workflows fast: Use caching, parallel jobs, and efficient commands
- Fail fast: Run quick checks (linting) before expensive operations (building)
- Use matrix strategies: Test across multiple environments simultaneously
- Secure your secrets: Never log or expose sensitive information
- Version your actions: Pin action versions for reproducibility (
actions/checkout@v4) - Monitor workflow usage: GitHub Actions has usage limits; optimize to stay within quotas
- Use branch protection: Require CI checks to pass before merging
Common Pitfalls and Solutions
Problem: Workflows run too long
Solution: Implement caching, use matrix strategies wisely, and parallelize independent jobs.
Problem: Intermittent test failures
Solution: Use retry mechanisms and ensure tests are deterministic (avoid time-based tests).
Problem: Secrets not available
Solution: Verify secrets are set at the repository level and referenced correctly.
Related Articles
- AWS US-EAST-1 DynamoDB Outage
- How can you get started with Prowler?
- ISO27001: Core Information Security Standard
- How to Set Up Continuous Deployment Pipelines
Conclusion
Setting up a CI/CD pipeline with GitHub Actions streamlines your development workflow, catches bugs early, and automates deployment. Start simple with a basic build-and-test workflow, then gradually add complexity as your needs grow.
The native integration with GitHub makes Actions particularly powerful—no third-party authentication, seamless PR integration, and a vast marketplace of pre-built actions. Whether you’re deploying a simple static site or orchestrating complex microservices, GitHub Actions provides the flexibility and power you need.
As you build more workflows, explore the GitHub Actions marketplace for community-maintained actions that can save you time and effort. Remember: the best CI/CD pipeline is one that’s reliable, fast, and continuously improving alongside your project.