Setting Up CI/CD with GitHub Actions
Automate your entire software delivery pipeline from testing to deployment with GitHub Actions, shipping faster with confidence
Remember the Friday afternoon deployments? The collective breath-holding as you manually ran commands, the Slack channel erupting with bug reports minutes after release, the weekend spent rolling back changes? Those days can be over. Modern CI/CD pipelines don’t just make deployments less stressful—they fundamentally transform how teams ship software, turning what was once a nerve-wracking event into a boring, repeatable, automated process that runs dozens of times per day.
GitHub Actions brings enterprise-grade automation directly into your repository, eliminating the complexity of external CI/CD platforms while giving you the power to build, test, and deploy with confidence. Every push becomes an opportunity to validate your code. Every merge becomes a potential release. And every deployment becomes predictable, traceable, and reversible.
Why CI/CD Matters
Continuous Integration and Continuous Deployment isn’t just about automation—it’s about shortening the feedback loop between writing code and getting it in front of users. When your pipeline runs automatically on every commit, you catch bugs minutes after they’re introduced, not days later. When deployments are automated, you ship features the moment they’re ready, not when someone remembers to run the deploy script.
The benefits compound quickly. Teams with strong CI/CD practices deploy more frequently, recover from failures faster, and spend less time on manual testing and deployment ceremonies. Instead of dedicating Friday afternoons to releases, developers focus on building features while their pipeline handles the mechanical work of testing, building, and shipping.
Prerequisites
Before building your CI/CD pipeline, you’ll need:
- A GitHub repository with your project code
- Basic understanding of YAML syntax
- A deployment target (cloud platform, server, or hosting service)
- Tests that can run in an automated environment
Don’t worry if your test coverage is minimal—you can always expand it later. The important thing is having something your pipeline can validate.
Your First Workflow
GitHub Actions workflows live in your repository under .github/workflows/. Each workflow is a YAML file defining when to run and what to do. Let’s start with a simple workflow that runs on every push:
name: CI Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
Save this as .github/workflows/ci.yml and push it to your repository. GitHub will automatically detect the workflow and run it on your next push.
The workflow structure is straightforward: the on section defines triggers, jobs define what to run, and steps define the individual commands. The uses keyword pulls in pre-built actions from the GitHub marketplace, while run executes shell commands.
Testing and Building
A robust CI pipeline doesn’t just run tests—it validates multiple aspects of your code. Expand your workflow to include linting, type checking, and building:
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint code
run: npm run lint
- name: Type check
run: npm run type-check
- name: Run tests
run: npm test -- --coverage
- name: Build project
run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build
path: dist/
The upload-artifact step saves your build output, making it available to other jobs or for download later. This is crucial for deployment—you want to deploy exactly what you tested, not rebuild in production.
For projects with multiple test suites or different environments, you can run jobs in parallel:
jobs:
test-unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run test:unit
test-integration:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run test:integration
Jobs run in parallel by default, so your test suite completes faster.
Automated Deployment
The real power of CI/CD comes when you automate deployment. Once tests pass, your code should automatically flow to production. Add a deployment job that depends on your test job:
jobs:
test:
# ... previous test job configuration
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: build
path: dist/
- name: Deploy to production
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: |
npm run deploy
The needs keyword ensures deployment only runs after tests pass. The if condition restricts deployment to the main branch. The secrets.DEPLOY_TOKEN pulls sensitive credentials from GitHub’s encrypted secrets storage—never hardcode credentials in your workflow files.
For cloud platforms, specialized actions simplify deployment. Deploying to Vercel looks like:
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
AWS, Azure, Google Cloud, and most hosting platforms have official actions that handle authentication and deployment complexity.
Environment Protection
For production deployments, add environment protection rules through GitHub’s repository settings. Navigate to Settings > Environments > New environment, create a “production” environment, and enable required reviewers. Then reference the environment in your workflow:
deploy:
needs: test
runs-on: ubuntu-latest
environment:
name: production
url: https://your-app.com
steps:
# deployment steps
Now deployments to production require manual approval, preventing accidental releases while keeping the process smooth for intentional ones.
What’s Next
Your CI/CD pipeline is running, but there’s always room to grow. Consider adding:
Matrix builds to test across multiple Node versions or operating systems:
strategy:
matrix:
node-version: [18, 20, 22]
os: [ubuntu-latest, windows-latest, macos-latest]
Security scanning to catch vulnerabilities before they reach production:
- name: Run security audit
run: npm audit --audit-level=high
Performance testing to ensure changes don’t degrade speed:
- name: Run Lighthouse CI
run: npm run lighthouse:ci
Automated releases using semantic-release to publish new versions based on commit messages.
The goal isn’t to automate everything immediately—it’s to build confidence in your pipeline incrementally. Start with tests and basic deployment. Once that’s reliable, add security scanning. Then performance tests. Then automated releases. Each addition makes your pipeline more powerful and your deployments more confident.
The best CI/CD pipeline is the one you trust enough to deploy to production without checking Slack every five minutes. Build that trust one automated step at a time, and soon those Friday afternoon deployments will be a distant memory.
Learn, Contribute & Share
This guide has a companion repository with working examples and code samples.