loading...
Cover image for CI/CD Pipeline with Netlify and GitHub Actions

CI/CD Pipeline with Netlify and GitHub Actions

curtiscodes profile image Dan Curtis ・6 min read

In this tutorial, we'll set up a modern CI/CD Pipeline with GitHub Actions and Netlify. Show me the code!

☑️ Development and production environments (websites)
☑️ GitHub Actions for CI/CD (saves Netlify build minutes)
☑️ Netlify for free, secure hosting
☑️ Cypress for end-to-end testing

CI stands for Continous Integration, which means that developers are continuously integrating their changes into the main branch.

CD stands for Continous Development or Delivery, which means that changes are automatically pushed to customers barring a failed test.

Project set up

  1. Create a sample application using React. I'm calling it "netlify-github-pipeline": npx create-react-app netlify-github-pipeline
  2. Create a new repo on GitHub and link it to our local React project
  3. Create "main" and "dev" branches on GitHub's website
  4. Set the default branch to main in Settings > Branches
  5. Delete the master branch
  6. Pull the branches locally: git pull
  7. Switch to main: git checkout main

Netlify set up

  1. Instead of creating a project with git, drag our React project folder to the bottom of Netlify's UI
  2. Cancel the automatic build running in Netlify's UI
  3. Generate NETLIFY_AUTH_TOKEN by going to User Settings > Applications > New Access Token
  4. Add the auth token to the GitHub repo by going to Settings > Secrets. Be sure to name it NETLIFY_AUTH_TOKEN
  5. Get the Site ID in Netlify by going to Settings > Site Information > APP ID
  6. Add the App ID to GitHub with the name of NETLIFY_SITE_ID

Repeat these steps for another website, only changing the NETLIFY_SIDE_ID to DEV_NETLIFY_SITE_ID in steps 5 and 6.

If we used Netlify to build and deploy, we would link our GitHub repo directly. We would also use their build preview feature instead of creating a "separate" dev website.

Using Netlify is cleaner, but they limit their free build minutes to 300/month. GitHub Actions has 3000 for private repos and is unlimited for public repos.

GitHub Workflow set up

If you're not familiar with GitHub Actions, I would suggest checking out their docs. I don't go into a bunch of detail on how the workflow files work.

We're going to create production and development environments. We should have two branches and Netlify Site IDs set up to reflect these environments. We also want to run tests every time a Pull Request is created. So we need three workflows: productions, development, and test.

  • Create a directory in netlify-github-pipeline's project root called .github and a workflows directory inside of .github: mkdir .github/workflows

Development Workflow

  • Create a development.yml file in the workflows directory:
# Name of workflow
name: Development workflow

# When workflow is triggered
on:
  pull_request:
    branches:
      - dev

# Jobs to carry out
jobs:
  deploy:
    # Operating system to run job on
    runs-on: ubuntu-latest

    # Steps in job
    steps:
      # Get code from repo
      - name: Checkout code
        uses: actions/checkout@v1
      # Install NodeJS
      - name: Use Node.js 12.x
        uses: actions/setup-node@v1
        with:
          node-version: 12.x
      # Run npm install and build on our code
      - run: npm install
      - run: npm run build --if-present
      # Deploy to Netlify using our dev secrets
      - name: Deploy to netlify
        uses: netlify/actions/cli@master
        env:
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
          NETLIFY_SITE_ID: ${{ secrets.DEV_NETLIFY_SITE_ID }}
        with:
          args: deploy --dir=build --prod
          secrets: '["DEV_NETLIFY_AUTH_TOKEN", "NETLIFY_SITE_ID"]'

Creating a pull request to the dev branch triggers this workflow file. The file runs Node on Ubuntu to build our app and then deploys the app to the Netlify dev website. We can manually test our changes before merging into main and creating a release.

Production Workflow

  • Create a production.yml file in the workflows directory:
#  Name of workflow
name: Production workflow

# When workflow is triggered
on:
  push:
    tags:
      - "v*"

# Jobs to carry out
jobs:
  deploy:
    # Operating system to run job on
    runs-on: ubuntu-latest
    # Steps in job
    steps:
      # Get code from repo
      - name: Checkout code
        uses: actions/checkout@v1
      # Install NodeJS
      - name: Use Node.js 12.x
        uses: actions/setup-node@v1
        with:
          node-version: 12.x
      # Run npm install and build on our code
      - run: npm install
      - run: npm run build --if-present
      # Deploy to Netlify using our production secrets
      - name: Deploy to netlify
        uses: netlify/actions/cli@master
        env:
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
        with:
          args: deploy --dir=build --prod
          secrets: '["NETLIFY_AUTH_TOKEN", "NETLIFY_SITE_ID"]'

Creating a release triggers this workflow file. We can create releases to manage the version of our app. The code in main does not always reflect the code in the release!

You can create releases using the GitHub website or by create a tag, git tag v1.0 then pushing the tag, git push origin v1.0

The file runs Node on Ubuntu to build our app and then deploys the app to our Netlify production website. Now we need to set up tests!

Test Workflow

We have to do a little configuration to get Cypress to run a simple test that visits the app and checks to see if it runs. This blog post has more info on Cypress and React.

  • Create a cypress.json file in the project root
{
  "baseUrl": "http://localhost:3000"
}
  • Create a cypress directory in the root
  • Create an integration directory in the cypress directory
  • Create an init.spec.js file in the integration directory
describe('Cypress', () => {
  it('is working', () => {
    expect(true).to.equal(true);
  });

  it('visits the app', () => {
    cy.visit('/');
  });
});
  • Install cypress npm install cypress
  • Create a test.yml file in the workflows directory
#  Name of workflow
name: Test workflow

# Trigger workflow on all pull requests
on:
  pull_request:
    branches:
      - dev
      - main

# Jobs to carry out
jobs:
  test:
    # Operating system to run job on
    runs-on: ubuntu-latest
    # Steps in job
    steps:
      # Get code from repo
      - name: Checkout code
        uses: actions/checkout@v1
      # Install NodeJS
      - name: Use Node.js 12.x
        uses: actions/setup-node@v1
        with:
          node-version: 12.x
      # Build the app using cypress
      - name: Cypress run
        uses: cypress-io/github-action@v1
        with:
          build: npm run build
          start: npm start
          wait-on: http://localhost:3000
          browser: chrome

Cypress opens up our app in Chrome at localhost:3000 and ensure's it works every time we create a pull request.

Putting it all together

You should now have 3 workflow files, a cypress directory and json file, two branches and Netlify websites. You can go ahead and push all of your changes to main. Below I walk through a development example to demonstrate the new pipeline.

Pipeline Example

Let's say we want to add a super cool feature. That super cool feature is changing the default text that create-react-app shows.

Instead of making changes in main or dev, we should create a feature branch off of dev called "feature/change-text." Then we'll make our changes and merge it into dev. We can see the changes on the dev website, and then merge it into main. Then we can create a new release for all of our users!

This is all easy to do with our new CI/CD pipeline.

  1. Create a feature branch on GitHub
  2. Pull changes locally
  3. Change the text in the p tag in src/App.js
  4. Save the changes and push them to the feature origin
  5. Create a pull request from the feature branch to dev
  6. Wait on the tests to run
  7. After the checks are successful, merge the PR to dev and delete the feature branch
  8. Visit your dev website, mines dev-netlify-github-pipeline.netlify.app
  9. After ensuring the changes are correct, create a PR from dev to main
  10. Wait on the tests to run
  11. After the checks are successful, merge the PR to main
  12. Switch to the main branch and pull the changes: git checkout main; git pull
  13. Create a new tag to trigger production workflow: git tag v1.0
  14. Push the tag: git push origin v1.0
  15. Create a new release on GitHub's website
  16. Check out your new production web app by visiting the production Netlify URL 🥳

If your GitHub Actions are failing, be sure to wait for them to run before merging pull requests.

Conclusion

We went over a lot. I tried to define a CI/CD Pipeline while simultaneously implementing one.

The general idea is that we have two branches on GitHub that match two websites, one for users and one for development. Our changes are tested with every pull request. To push our development changes to users, we create a release.

This is an over-simplified workflow. Companies usually have staging and testing environments on top of production and development. Adding those would be pretty easy now that we know the basics of GitHub Actions.

Acknowledgments:

Cover photo by Samuel Sianipar on Unsplash

Posted on by:

curtiscodes profile

Dan Curtis

@curtiscodes

Engineering software, van life, coffee in reverse order.

Discussion

pic
Editor guide
 

Excellent Breakdown! You really helped decrease the barrier to entry.