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
- Create a sample application using React. I'm calling it "netlify-github-pipeline":
npx create-react-app netlify-github-pipeline
- Create a new repo on GitHub and link it to our local React project
- Create "main" and "dev" branches on GitHub's website
- Set the default branch to main in Settings > Branches
- Delete the master branch
- Pull the branches locally:
git pull
- Switch to main:
git checkout main
Netlify set up
- Instead of creating a project with git, drag our React project folder to the bottom of Netlify's UI
- Cancel the automatic build running in Netlify's UI
- Generate
NETLIFY_AUTH_TOKEN
by going to User Settings > Applications > New Access Token - Add the auth token to the GitHub repo by going to Settings > Secrets. Be sure to name it
NETLIFY_AUTH_TOKEN
- Get the Site ID in Netlify by going to Settings > Site Information > APP ID
- 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.
- Create a feature branch on GitHub
- Pull changes locally
- Change the text in the p tag in
src/App.js
- Save the changes and push them to the feature origin
- Create a pull request from the feature branch to dev
- Wait on the tests to run
- After the checks are successful, merge the PR to dev and delete the feature branch
- Visit your dev website, mines dev-netlify-github-pipeline.netlify.app
- After ensuring the changes are correct, create a PR from dev to main
- Wait on the tests to run
- After the checks are successful, merge the PR to main
- Switch to the main branch and pull the changes:
git checkout main; git pull
- Create a new tag to trigger production workflow:
git tag v1.0
- Push the tag:
git push origin v1.0
- Create a new release on GitHub's website
- 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
Top comments (7)
Great tutorial, but I cannot make it work.
I have turned off automatic build, and everything seems to work. It deploys, but not to the main site. It creates a preview site instead.
The output from the action log:
What am I missing?
You need to keep auto publishing on and uninstall Netlify from your repo.
It also ends up being my solution or my real solution was to switch to Heroku where there is a "Wait for CI to pass" setting.
Excellent Breakdown! You really helped decrease the barrier to entry.
Thanks!
ohh great tutorial thanks men
Awesome. Works like a charm!