CI/CD Pipeline with GitHub Actions
This tutorial guides you through setting up a complete CI/CD pipeline using GitHub Actions, helping you automate your software deployment process. You'll learn key concepts, practical steps, and best practices while building a simple Node.js application, enabling you to streamline your development workflow effectively.
Tutorial: CI/CD Pipeline with GitHub Actions
Learning Objectives and Outcomes
In this tutorial, you will learn how to:
- Understand the fundamentals of Continuous Integration and Continuous Deployment (CI/CD).
- Set up a CI/CD pipeline using GitHub Actions to automate your software deployment process.
- Write and configure GitHub Actions workflows using YAML.
- Build and push Docker images as part of your CI/CD pipeline.
- Deploy your application automatically using GitHub Actions.
- Manage secrets and environments securely in GitHub Actions.
By the end of this tutorial, you will have a working CI/CD pipeline integrated with your GitHub repository, enabling you to streamline your development process.
Prerequisites and Setup
Before diving into the tutorial, ensure you have:
- Basic knowledge of Git: You should understand how to clone repositories, commit changes, and push to GitHub.
- Basic knowledge of YAML: Familiarity with YAML syntax is essential for writing GitHub Actions workflows.
- Docker installed locally: Required for building and testing container images.
- A container registry account: Such as Docker Hub or GitHub Container Registry, for pushing images.
Setting Up Your Environment
- Create a GitHub Account: If you don't have one, sign up at GitHub.
- Create a Repository: Create a new repository where you will set up your CI/CD pipeline.
- Clone the repository locally and create a simple Node.js application to use throughout this tutorial.
Step-by-Step Instructions with Examples
Step 1: Workflow Basics
GitHub Actions workflows are YAML files stored in .github/workflows/ in your repository. Each workflow defines when to run and what to do.
- Create the workflows directory:
mkdir -p .github/workflows - Create
.github/workflows/ci-cd.ymlwith a basic structure:name: CI/CD Pipeline on: push: branches: - main pull_request: branches: - main jobs: build: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - Key concepts in this file:
on: Defines the trigger events — here, pushes and pull requests tomain.jobs: Each job runs in its own environment. You can have multiple jobs.runs-on: The operating system for the job runner (e.g.,ubuntu-latest).steps: Sequential tasks within a job, using eitherrun(shell commands) oruses(pre-built actions).
- Commit and push this file. GitHub automatically detects it and displays it under the Actions tab.
Step 2: Build and Test
Extend your workflow to install dependencies, run tests, and produce a build artefact.
- Update the
buildjob in your workflow file:jobs: build: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up 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 - name: Build application run: npm run build - The
cache: 'npm'option caches yournode_modulesbetween workflow runs, speeding up subsequent builds. - Use
npm ciinstead ofnpm installin CI pipelines — it installs exact versions frompackage-lock.jsonfor reproducible builds. - Push your changes. In the Actions tab, watch each step run in real time. If a test fails, the workflow stops and later jobs will not execute.
Step 3: Docker Build
After a successful build and test phase, build a Docker image and push it to a container registry.
- Create a
Dockerfilein your repository root:FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . EXPOSE 3000 CMD ["node", "index.js"] - Add a
dockerjob that depends on thebuildjob:docker: needs: build runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . push: true tags: ${{ secrets.DOCKER_USERNAME }}/my-app:latest,${{ secrets.DOCKER_USERNAME }}/my-app:${{ github.sha }} - The
needs: builddirective ensures the Docker job only runs after the build and test job succeeds. - Tagging with
${{ github.sha }}in addition tolatestgives you an immutable image reference for each commit.
Step 4: Deployment
With your Docker image pushed to a registry, automatically deploy it to your server.
- Add a
deployjob that depends on thedockerjob:deploy: needs: docker runs-on: ubuntu-latest environment: production steps: - name: Deploy to server uses: appleboy/ssh-action@v1 with: host: ${{ secrets.DEPLOY_HOST }} username: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.DEPLOY_SSH_KEY }} script: | docker pull ${{ secrets.DOCKER_USERNAME }}/my-app:${{ github.sha }} docker stop my-app || true docker rm my-app || true docker run -d \ --name my-app \ --restart unless-stopped \ -p 3000:3000 \ ${{ secrets.DOCKER_USERNAME }}/my-app:${{ github.sha }} - The
environment: productiondeclaration links this job to a GitHub Environment, enabling protection rules such as required reviewers before deployment proceeds. - Adapt the deployment script to your hosting target — for Kubernetes, use
kubectl set image; for AWS ECS, use the AWS CLI or a dedicated action.
Step 5: Secrets and Environments
Secrets keep sensitive values out of your source code. GitHub Environments add approval gates and environment-scoped overrides.
- Add repository secrets:
- Go to Settings → Secrets and variables → Actions → New repository secret.
- Create secrets for:
DOCKER_USERNAME,DOCKER_PASSWORD,DEPLOY_HOST,DEPLOY_USER,DEPLOY_SSH_KEY.
- Create a production Environment:
- Go to Settings → Environments → New environment and name it
production. - Enable Required reviewers to enforce a manual approval step before every production deployment.
- Add environment-specific secrets here if they differ from repository-level secrets.
- Go to Settings → Environments → New environment and name it
- Reference secrets in your workflow using
${{ secrets.SECRET_NAME }}. GitHub automatically masks them in job logs. - Use environment variables for non-sensitive configuration shared across jobs:
env: NODE_ENV: production PORT: 3000 - Never hardcode credentials, tokens, or connection strings in workflow files — always use secrets.
Key Concepts Explained Along the Way
- Continuous Integration (CI): The practice of automatically testing and integrating code changes into a shared repository.
- Continuous Deployment (CD): The process of automatically deploying code changes to production after passing tests.
- Workflows: A YAML file that defines the automated processes in GitHub Actions.
- Jobs: A collection of steps that run in the same environment. Jobs can run in parallel or be sequenced using
needs. - Steps: Individual tasks executed within a job, using either
runcommands or reusableusesactions. - Secrets: Encrypted values stored in GitHub, injected into workflows at runtime and masked in logs.
- Environments: Named deployment targets with optional protection rules and environment-specific secrets.
Common Mistakes and How to Avoid Them
- Using
npm installin CI: Usenpm ciinstead for reproducible, deterministic installs. - Not pinning action versions: Use
actions/checkout@v4(not@main) to avoid unexpected breaking changes. - Forgetting
needs: Withoutneeds, jobs run in parallel — always chain dependent jobs explicitly. - Incorrect branch name: Verify your workflow triggers on the correct branch (e.g.,
mainnotmaster). - Exposing secrets in logs: Never
echoa secret value directly. GitHub only masks values referenced via${{ secrets.NAME }}.
Exercises and Practice Suggestions
- Add a matrix build to test against multiple Node.js versions simultaneously using
strategy.matrix. - Configure a scheduled workflow (
on: schedule) to run nightly integration tests. - Add a Slack or email notification step that fires when a deployment succeeds or fails.
- Experiment with GitHub Environments by adding a required reviewer before the production deploy proceeds.
Next Steps and Further Learning
- Explore advanced GitHub Actions features such as reusable workflows and composite actions.
- Learn about deploying to managed cloud services (AWS ECS, Google Cloud Run, Azure Container Apps).
- Add security scanning steps — use
trivyfor container image scanning andsnykfor dependency auditing.
With these tools and knowledge, you can create powerful and efficient CI/CD pipelines using GitHub Actions, streamlining your development workflow and increasing your team's productivity.
Category
DevopsPrerequisites
- github actions
- docker
- nodejs
Steps
- 1Workflow Basics
- 2Build and Test
- 3Docker Build
- 4Deployment
- 5Secrets and Environments