Step-by-Step Tutorial Creating Your First CI/CD Pipeline

Step-by-Step Tutorial Creating Your First CI/CD Pipeline

Are you ready to take your development process to the next level? If you’ve been hearing buzz about CI/CD but haven’t yet dipped your toes in, you’re in for a treat. In this comprehensive guide, we’ll walk you through creating your very first Continuous Integration and Continuous Deployment (CI/CD) pipeline. By the end of this tutorial, you’ll have a solid understanding of CI/CD principles and a working pipeline to show for it. So, buckle up and let’s dive into the world of automated software delivery!

What is CI/CD and Why Should You Care?

Before we roll up our sleeves and get our hands dirty with code, let’s take a moment to understand what CI/CD is all about. CI/CD stands for Continuous Integration and Continuous Deployment (or Delivery). It’s a set of practices and tools that help developers integrate code changes more frequently and reliably. The goal? To build, test, and deploy applications with greater speed and fewer errors. Think of it as your personal assistant that takes care of the repetitive tasks, allowing you to focus on what truly matters – writing great code.

Continuous Integration is all about merging code changes into a central repository multiple times a day. Each integration kicks off an automated build and test process, helping catch bugs early. Continuous Deployment takes it a step further by automatically deploying all code changes to a testing or production environment after the build stage. This approach significantly reduces the time between writing code and getting it into the hands of your users.

Now, you might be wondering, “Why should I care about CI/CD?” Well, imagine being able to deploy new features or bug fixes several times a day without breaking a sweat. Picture a world where you catch errors before they reach production, saving you countless hours of debugging and firefighting. That’s the power of CI/CD. It’s not just about automating tasks; it’s about transforming the way you develop and deliver software.

Setting the Stage: What You’ll Need

Before we embark on our CI/CD journey, let’s make sure we have all our ducks in a row. Here’s what you’ll need to follow along with this tutorial:

  1. A GitHub account: We’ll be using GitHub as our version control system and to host our code repository.
  2. A simple web application: Don’t worry if you don’t have one handy. We’ll provide a basic Node.js application for this tutorial.
  3. A Heroku account: We’ll use Heroku as our deployment platform. It’s free to start and easy to use.
  4. Basic knowledge of Git: You should be comfortable with basic Git commands like clone, add, commit, and push.
  5. A text editor or IDE of your choice: Use whatever you’re most comfortable with.

Got everything? Great! Let’s move on to the exciting part – building our pipeline.

Step 1: Creating Your Application

For this tutorial, we’ll use a simple Node.js application. If you already have an application you’d like to use, feel free to skip this step. Otherwise, let’s create a basic “Hello World” app.

First, create a new directory for your project and navigate into it:

mkdir my-first-cicd-app
cd my-first-cicd-app

Now, let’s initialize a new Node.js project:

npm init -y

This command creates a package.json file with default values. Next, let’s install Express, a popular Node.js web framework:

npm install express

Create a new file called app.js and add the following code:

const express = require('express');
const app = express();
const port = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.send('Hello, CI/CD World!');
});

app.listen(port, () => {
  console.log(`App listening at http://localhost:${port}`);
});

This simple application creates a web server that responds with “Hello, CI/CD World!” when you visit the root URL. Now, let’s update the package.json file to include a start script. Open package.json and add the following line to the “scripts” section:

"scripts": {
  "start": "node app.js"
}

Great! We now have a basic application ready to go. Let’s move on to setting up our GitHub repository.

Step 2: Setting Up Your GitHub Repository

Now that we have our application, it’s time to get it under version control. If you haven’t already, create a new repository on GitHub. Once you’ve done that, initialize your local Git repository and push your code to GitHub:

git init
git add .
git commit -m "Initial commit"
git branch -M main
git remote add origin https://github.com/yourusername/my-first-cicd-app.git
git push -u origin main

Remember to replace yourusername with your actual GitHub username. At this point, your code should be safely stored in your GitHub repository. This is a crucial step because our CI/CD pipeline will be triggered by changes to this repository.

Step 3: Introducing GitHub Actions

Now comes the exciting part – setting up our Continuous Integration pipeline using GitHub Actions. GitHub Actions is a powerful automation tool that allows you to define custom workflows for your software projects. These workflows are triggered by events in your repository, such as pushing code or creating a pull request.

Let’s create our first workflow. In your project directory, create a new folder structure: .github/workflows. Inside this new folder, create a file named ci.yml. This YAML file will define our CI workflow.

Add the following content to ci.yml:

name: Node.js CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [14.x, 16.x, 18.x]

    steps:
    - uses: actions/checkout@v3
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v3
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm ci
    - run: npm run build --if-present
    - run: npm test

Let’s break down what this workflow does:

  1. The name field gives our workflow a name, in this case, “Node.js CI”.
  2. The on section specifies when this workflow should run. Here, it runs on pushes to the main branch and when pull requests are opened against the main branch.
  3. The jobs section defines the jobs that will run as part of this workflow. We have one job called “build”.
  4. runs-on specifies the type of machine to run the job on. We’re using the latest version of Ubuntu.
  5. The strategy section sets up a build matrix, allowing us to test our code against multiple versions of Node.js.
  6. The steps section outlines the individual steps in our job:
  • We check out the code using the actions/checkout action.
  • We set up Node.js using the actions/setup-node action.
  • We install dependencies using npm ci.
  • We run the build script (if it exists) using npm run build --if-present.
  • Finally, we run our tests using npm test.

Now, commit this new file and push it to GitHub:

git add .github/workflows/ci.yml
git commit -m "Add CI workflow"
git push

Congratulations! You’ve just set up your first CI pipeline. Every time you push code to the main branch or open a pull request, GitHub Actions will automatically run your tests across different Node.js versions.

Step 4: Adding Tests to Your Application

Of course, for our CI pipeline to be useful, we need some tests to run. Let’s add a simple test to our application. First, install Jest, a popular JavaScript testing framework:

npm install --save-dev jest

Now, update your package.json to include a test script. Add the following line to the “scripts” section:

"scripts": {
  "start": "node app.js",
  "test": "jest"
}

Next, create a new file called app.test.js in your project root and add the following content:

const request = require('supertest');
const app = require('./app');

describe('GET /', () => {
  it('responds with Hello, CI/CD World!', async () => {
    const response = await request(app).get('/');
    expect(response.status).toBe(200);
    expect(response.text).toBe('Hello, CI/CD World!');
  });
});

This test checks if our app responds with the correct message when we hit the root URL. To make this test work, we need to slightly modify our app.js file to export the app:

const express = require('express');
const app = express();
const port = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.send('Hello, CI/CD World!');
});

if (require.main === module) {
  app.listen(port, () => {
    console.log(`App listening at http://localhost:${port}`);
  });
}

module.exports = app;

Now, let’s commit these changes and push them to GitHub:

git add .
git commit -m "Add tests and update app for testing"
git push

When you push these changes, you should see your GitHub Actions workflow kick off automatically. You can check the progress in the “Actions” tab of your GitHub repository.

Step 5: Continuous Deployment with Heroku

We’ve got Continuous Integration set up, but what about Continuous Deployment? Let’s take it a step further and automatically deploy our application to Heroku whenever our tests pass on the main branch.

First, if you haven’t already, create a new app on Heroku. Take note of the app name, as we’ll need it later. Next, we need to set up a few secrets in our GitHub repository to allow GitHub Actions to deploy to Heroku. Go to your repository settings, click on “Secrets” in the sidebar, and add the following secrets:

  • HEROKU_API_KEY: Your Heroku API key
  • HEROKU_APP_NAME: The name of your Heroku app

Now, let’s update our ci.yml file to include a deployment step. Add the following job after the build job:

deploy:
  needs: build
  runs-on: ubuntu-latest
  if: github.ref == 'refs/heads/main'

  steps:
  - uses: actions/checkout@v3
  - uses: akhileshns/heroku-deploy@v3.12.14
    with:
      heroku_api_key: ${{secrets.HEROKU_API_KEY}}
      heroku_app_name: ${{secrets.HEROKU_APP_NAME}}
      heroku_email: ${{secrets.HEROKU_EMAIL}}

This new job does the following:

  1. It only runs if the build job is successful (needs: build).
  2. It only runs on the main branch (if: github.ref == 'refs/heads/main').
  3. It uses the akhileshns/heroku-deploy action to deploy our app to Heroku.

We also need to add a Procfile to our project to tell Heroku how to run our application. Create a file named Procfile (no extension) in your project root with the following content:

web: npm start

Now, let’s commit these changes and push them to GitHub:

git add .github/workflows/ci.yml Procfile
git commit -m "Add CD workflow and Procfile"
git push

When you push these changes, your GitHub Actions workflow will run your tests and, if they pass, automatically deploy your application to Heroku. Magic!

Putting It All Together: Your CI/CD Pipeline in Action

Let’s recap what we’ve accomplished:

  1. We created a simple Node.js application.
  2. We set up a GitHub repository for our code.
  3. We implemented a Continuous Integration pipeline using GitHub Actions, which runs our tests on every push and pull request.
  4. We added a Continuous Deployment step to automatically deploy our application to Heroku when tests pass on the main branch.

This is a fully functional CI/CD pipeline! Every time you push changes to your main branch, your tests will run, and if they pass, your application will be automatically deployed to Heroku. This workflow allows you to catch errors early and deploy with confidence.

Best Practices and Next Steps

Now that you have your first CI/CD pipeline up and running, here are some best practices to keep in mind:

Write Good Tests: Your pipeline is only as good as your tests. Make sure you have a comprehensive test suite that covers various aspects of your application.

Keep Your Pipeline Fast: Long-running pipelines can slow down your development process. Try to optimize your tests and build process for speed.

Use Branch Protection Rules: Set up branch protection rules on GitHub to ensure that code can’t be merged into main unless it passes your CI checks.

Monitor Your Deployments: Keep an eye on your application after deployments. Consider implementing application monitoring and logging.

Continuously Improve: CI/CD is an iterative process. Regularly review and refine your pipeline based on your team’s needs and feedback.

As for next steps, consider exploring these areas:

  1. Environment-Specific Deployments: Set up different environments (like staging and production) and configure your pipeline to deploy to the appropriate environment based on the branch.
  2. Automated Version Bumping: Implement automatic version number increments based on your commit messages.
  3. Slack or Email Notifications: Configure your pipeline to send notifications about build and deployment status.
  4. Code Quality Checks: Integrate tools like ESLint or SonarQube into your pipeline to enforce code quality standards.
  5. Performance Testing: Add performance tests to your pipeline to catch performance regressions before they reach production.

Conclusion

Congratulations! You’ve just built your first CI/CD pipeline. You’ve taken a significant step towards more efficient, reliable software development. Remember, CI/CD is not a destination, but a journey. Keep learning, keep improving, and most importantly, enjoy the process of building and deploying software with confidence.

By implementing CI/CD, you’re not just automating tasks; you’re transforming your development workflow. You’re catching bugs earlier, deploying faster, and ultimately delivering more value to your users. So pat yourself on the back – you’re now part of the CI/CD club!

As you continue on your CI/CD journey, remember that there’s always more to learn and explore. The world of DevOps is vast and ever-evolving. But with the foundation you’ve built today, you’re well-equipped to tackle whatever challenges come your way.

Happy coding, and may your deployments always be smooth!

Disclaimer: This tutorial is intended for educational purposes only. While we strive for accuracy, technology and best practices in the field of CI/CD are constantly evolving. Always refer to the official documentation of the tools and services mentioned for the most up-to-date information. If you notice any inaccuracies in this tutorial, please report them so we can correct them promptly.

Leave a Reply

Your email address will not be published. Required fields are marked *


Translate »