We are going to set up a continuous integration / continuous deployment (CI/CD) pipeline with GitHub Actions to automatically run your automated tests on each pull request and automate deployment to Gigalixir when you push commits to your default (e.g. main) branch.
Overview
Here are the topics we’ll cover while deploying the project.
- What is Continuous Integration / Continuous Deployment (CI/CD)?
- Continuous Integration with GitHub Actions
- Add Continuous Deployment to Gigalixir using GitHub Actions
Prerequisites
To follow this guide, make sure you have at least the following applications installed on your machine. In the brackets are the versions with which this guide was written.
- Elixir (1.12.3-otp-24)
- Erlang (OTP 24.1)
- Phoenix (1.5.13)
- Node.js with npm (15.14.0)
Make sure to install at least Elixir 1.9 since we are using releases. We recommend using asdf to manage Erlang and Elixir versions.
Your new Phoenix project should be deployed to Gigalixir. We’ll start with the Gigalixir app we’ve been using to demo deployment on Gigalixir.
We’ve covered deploying Phoenix to Gigalixir and using Gigalixir’s remote debugging tools. Git clone from this commit in our project called HelloGigalixir.
What is Continuous Integration / Continuous Deployment (CI/CD)?
According to Red Hat, the "CI" in CI/CD always refers to continuous integration, which is an automation process for developers. Successful CI means new code changes to an app are regularly built, tested, and merged to a shared repository.
The "CD" in CI/CD refers to continuous delivery and/or continuous deployment.
Continuous delivery usually means a developer’s changes to an application are automatically bug tested and uploaded to a repository (like GitHub), where they can then be deployed to a live production environment by the operations team.
Continuous deployment (the other possible "CD") can refer to automatically releasing a developer’s changes from the repository to production.
We plan to create a complete CI/CD pipeline to deploy our Phoenix app to Gigalixir using GitHub Actions.
CI with GitHub Actions
First we will implement a CI workflow using GitHub Actions. We’ll use the workflow to run our automated test suite when we push code to GitHub and hopefully reduce bugs in production.
GitHub Actions allows you to start with a preconfigured workflow yaml file template. We’ll use the template for Elixir.
In you GitHub repository, click the "Actions" tab then "Set up this workflow".
We need to make some updates to the GitHub Actions Elixir example workflow template, the main change is to add the database.
We have a Phoenix app and we used the CI GitHub Action ci.yml file from the Elixir Companies website project on GitHub as a reference.
Update elixir.yml
Here is the updated elixir.yml
workflow yaml file for our Github Actions. We’ve named the workflow Elixir CI. We’ll discuss the details of each section below.
name: Elixir CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
name: Build and test
runs-on: ubuntu-latest
services:
db:
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
image: postgres:11
ports: ['5432:5432']
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v2
- name: Set up Elixir
uses: erlef/setup-elixir@885971a72ed1f9240973bd92ab57af8c1aa68f24
with:
elixir-version: '1.12.3' # Define the elixir version [required]
otp-version: '24.1' # Define the OTP version [required]
- name: Restore dependencies cache
uses: actions/cache@v2
with:
path: deps
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: ${{ runner.os }}-mix-
- name: Install dependencies
run: mix deps.get
- name: Run tests
run: mix test
- name: Check Formatting
run: mix format --check-formatted
For now, we’ll use the default settings to run our workflow for both pull requests and pushes to main
.
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
Then we’ll run our first job we’ve labelled Build and test
on the latest ubuntu. Before we get to the Elixir setup, we also need to set up our Postgres service. Postgres options include our environment variables.
test:
name: Build and test
runs-on: ubuntu-latest
services:
db:
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
image: postgres:11
ports: ['5432:5432']
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
With the database created, we can use Checkout and Setup Elixir to prepare the environment.
Setup Elixir includes defining your elixir version and otp version. Then to install dependencies run mix deps
and call mix test
to automatically run tests in the test suite.
You can also do things like check formatting with mix format
and static code analysis. You can accomplish static code analysis with a tool like Credo.
steps:
- uses: actions/checkout@v2
- name: Set up Elixir
uses: erlef/setup-elixir@885971a72ed1f9240973bd92ab57af8c1aa68f24
with:
elixir-version: '1.12.3' # Define the elixir version [required]
otp-version: '24.1' # Define the OTP version [required]
- name: Restore dependencies cache
uses: actions/cache@v2
with:
path: deps
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: ${{ runner.os }}-mix-
- name: Install dependencies
run: mix deps.get
- name: Run tests
run: mix test
- name: Check Formatting
run: mix format --check-formatted
Copy and paste the updated code into the new elixir.yml
file on GitHub and commit the change.
Run workflow
Now GitHub Actions should run your new test suite. Click the "Actions" tab to watch the progress and results of your Elixir CI
workflow.
If your mix format fails, you need to git pull the elixir.yml
changes to your local machine, run mix format on the file(s) and then push changes back to GitHub.
You may see some deprecation warnings, but the GitHub Actions workflow exits with success after the tests all pass.
Add CD to Gigalixir using GitHub Actions
Now we’ll add CD to our CI/CD pipeline. With continuous deployment, if the tests pass after pushing changes to your GitHub default branch, the changes will automatically get deployed to production.
We’ll add a deploy step in the workflow using the Gigalixir CI/CD guide. There are two options for GitHub Actions. We’ll use Jesse Shieh’s gist as a starting point with some minor modifications.
To begin this section, git pull
the changes to the git repository on your local machine from your default branch and checkout a new branch action
:
git pull
git checkout -b action
Add deploy job
Add the following to the end of your elixir.yml
workflow yaml file.
deploy:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
needs: test
steps:
- uses: actions/checkout@v1
- run: sudo pip install gigalixir --ignore-installed six
- run: gigalixir login -e "${{ secrets.GIGALIXIR_EMAIL }}" -y -p "${{ secrets.GIGALIXIR_PASSWORD }}"
- run: gigalixir git:remote ${{ secrets.GIGALIXIR_APP_NAME }}
- run: git push -f gigalixir HEAD:refs/heads/master
The deploy workflow will only run when commits are pushed to main, either directly or when you merge a pull request.
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
It also uses needs: test
to configure the workflow to run deploy
only after a successful test
job, see the jobs.<job_id>.needs documentation. Without this update, GitHub runs the jobs in parallel by default.
The deploy workflow steps will:
- Install gigalixir CLI
- Login in to Gigalixir through the CLI
- Add the gigalixir remote to git
- Push your code to initiate deployment including our automated database migration
Note: We automated database migrations in our previous post, so we don’t need to accomplish migrations in the workflow.
GitHub Secrets
We will use GitHub secrets to create and store encrypted secrets to use in the workflow for our Gigalixir username, password, and app name.
Follow the instructions for creating and storing encrypted secrets for GIGALIXIR_EMAIL
GIGALIXIR_PASSWORD
and GIGALIXIR_APP_NAME
.
You can get your app name using the gigalixir CLI with the command gigalixir apps
from your project folder.
Once you’ve stored your username, password, and app name:
git add .
git commit -m "add deploy job to GitHub Actions"
git push origin action
Then open a pull request on GitHub.
Run workflow
Now go to the "Actions" tab again to see the progress and results of your workflow. Your test should pass. They deploy job shouldn’t run since it only runs on pushes to main.
Now merge your changes with the main branch in your git repository and push to origin.
git checkout main
git merge action --ff-only
git push
This time both the test and deploy jobs should run successfully.
Now visit https://app_name.gigalixirapp.com/users and test your app running in production is deployed properly. If you run into problems, the finished project is available on GitHub as StakNine HelloGigalixir.
Conclusion
Congratulations! You’ve built a CI/CD pipeline with GitHub Actions to automatically run your automated tests on each pull request and automate deployment to Gigalixir when you push commits to your default (e.g. main) branch. Now that you’ve set up continuous deployment, check out our article covering the best error monitoring services for Elixir so you can deploy with confidence and effectively manage errors in your system.