Create two deployment workflows using AWS.
Tags: GitHub Actions, Workflows, CD, Amazon Web Services (AWS), Deployment
This course is about continuous delivery, or CD, with GitHub Actions. CD is a crucial part of modern software development, and it can have a monumental impact on development projects.
Continuous Delivery is the practice of delivering software faster and with higher quality. Doing so successfully requires many different practices, behaviors, and technologies. GitHub Actions makes implementing CD easier, faster, and more reliable.
In this course, we focus on using workflows in GitHub Actions to deploy pull requests automatically to a staging environment, and to deploy all merged commits to production.
Please note: You may need a credit card to create an account with a cloud provider. If you're a student, you may also be able to take advantage of the Student Developer Pack for access to a cloud provider. If you'd like to continue with the course without an account with a cloud provider, Learning Lab will still respond, but none of the deployments will work.
We'll answer common questions like:
- What is Continuous Delivery?
- What is a workflow file and how are workflows used with GitHub Actions?
- What are deployment triggers and how are they used with deployments?
- How can secrets store tokens?
And when you're done you'll be able to:
- Use GitHub Actions and create workflow files
- Use secrets to store tokens
- Deploy to staging and production using GitHub Actions to implement CD
In this course, we'll be walking through a CD deployment using GitHub Actions, and creating a Tic Tac Toe game!
Before you start, you should be familiar with GitHub and Continuous Integration. We recommend taking the following three Learning Labs prior to this course:
This course makes use of the following technologies:
- Node.js
- AWS - create an account if you don't already have one.
- Amazon S3 Buckets
Developers, DevOps Engineers, students, teams
We'll learn how to create a workflow that enables Continuous Delivery. You'll:
- create a workflow to deploy to staging based on a label
- create a workflow to deploy to production based on merging to main
Before you start, you should be familiar with GitHub and Continuous Integration. If you aren't sure where to start, you may want to check out these two Learning Lab courses:
Martin Fowler defined Continuous Delivery very simply in a 2013 post as follows:
Continuous Delivery is a software development discipline where you build software in such a way that the software can be released to production at any time.
A lot of things go into delivering "continuously". These things can range from culture and behavior to specific automation. In this course, we're going to focus on deployment automation.
Every deployment is kicked off by some trigger. Engineers at many companies, like at GitHub, typically use a ChatOps command as a trigger. The trigger itself isn't incredibly important. In our use case, we'll use labels. When someone applies a "stage" label to a pull request, that'll be our indicator that we'd like to deploy our application to a staging environment.
In a GitHub Actions workflow, the on
step defines what causes the workflow to run. In this case, we want the workflow to run whenever a label is applied to the pull request.
- Edit the
deploy-staging.yml
file on this branch, or [use this quick link]({{ repoUrl }}/edit/staging-workflow/.github/CHANGETHIS/deploy-staging.yml?) (We recommend opening the quick link in another tab) - Change the name of the directory
CHANGETHIS
toworkflows
, so the title of this file with the path is.github/workflows/deploy-staging.yml
- Edit the contents of this file to trigger on a label
Your result should look like this:
name: Staging deployment
on:
pull_request:
types: [labeled]
jobs:
build:
runs-on: ubuntu-latest
It looks like you took an action I didn't expect.
I expected you to {{ expected }}. Please try that to continue!
GitHub Actions features powerful controls for when to execute jobs and the steps within them. One of these controls is if
, which allows you run a job only when a specific condition is met. See jobs.<job_id>.if
in Workflow syntax for GitHub Actions for more information.
Workflows are part of the GitHub ecosystem, so each workflow run gives you access to a rich set of data that you can use to take fine-grained control.
We'd like to run our workflow on a specific label called stage, so we'll achieve this in a single line that packs a punch. We'll use:
if:
is the conditional that will decide if the job will runcontains()
is a function that allows us to determine if a value like say, a label named"stage"
, is contained within a set of values like say, a label array["bug", "stage", "feature request"]
github.event.pull_request.labels
is specifically accessing the set of labels that triggered the workflow to run. It does this by accessing thegithub
object, and thepull_request
event that triggered the workflow.github.event.pull_request.labels.*.name
uses object filters to filter out all information about the labels, like their color or description, and lets us focus on just the label names.
Let's put all this together to run our job only when a labeled named "stage" is applied to the pull request.
- Edit the
deploy-staging.yml
file on this branch, or [use this quick link]({{ repoUrl }}/edit/staging-workflow/.github/workflows/deploy-staging.yml?) (We recommend opening the quick link in another tab) - Edit the contents of the file to add a conditional
Your results should look like this:
name: Staging deployment
on:
pull_request:
types: [labeled]
jobs:
build:
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'stage')
It looks like you took an action I didn't expect.
I expected you to {{ expected }}. Please try that to continue!
So far, the workflow knows what the trigger is and what environment to run in. But, what exactly is supposed to run? The "steps" section of this workflow specifies actions and scripts to be run in the Ubuntu environment when new labels are added.
We won't be going into detail on the steps of this workflow, but it would be a good idea to become familiar with the actions we're using. They are:
The course Using GitHub Actions for CI also teaches how to use most of these actions in details.
- In a new tab, create an AWS account if you don't already have one.
Note: You may need a credit card to create an AWS account. If you're a student, you may also be able to take advantage of the Student Developer Pack for access to AWS. If you'd like to continue with the course without an AWS account, Learning Lab will still respond, but none of the deployments will work.
- Add a user in the IAM service with administrator permission.
- For detailed instructions on adding users, see Creating an IAM User in Your AWS Account in the AWS docs.
- In the confirmation screen, copy both the Access key ID and the Secret access key to a safe space. We'll use these in the next few steps as follows:
- Access key ID ➡️
AWS_ACCESS_KEY
- Secret access key ️️️ ➡️
AWS_SECRET_KEY
- Access key ID ➡️
- Back on GitHub, click on this repository's [Secrets]({{ repoUrl }}/settings/secrets) in the Settings tab.
- Click Add a new secret
- Name your new secret AWS_ACCESS_KEY and paste the value from the Access key ID generated on AWS.
- Click Add secret.
- Click Add a new secret again.
- Name the second secret AWS_SECRET_KEY and paste the value from the Secret access key generated on AWS.
- Click Add secret
- Back in this pull request, edit the
.github/workflows/deploy-staging.yml
file to use a new action, or [use this quick link]({{ repoUrl }}/edit/staging-workflow/.github/workflows/deploy-staging.yml?) (We recommend opening the quick link in another tab)- name: Deploy to AWS uses: github/deploy-nodejs@master env: AWS_ACCESS_KEY: {% raw %}${{ secrets.AWS_ACCESS_KEY }}{% endraw %} AWS_SECRET_KEY: {% raw %}${{ secrets.AWS_SECRET_KEY }}{% endraw %}
If you'd like to copy the full workflow file, it should look like this:
name: Staging deployment
on:
pull_request:
types: [labeled]
jobs:
build:
if: contains(github.event.pull_request.labels.*.name, 'stage')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: npm install and build webpack
run: |
npm install
npm run build
- uses: actions/upload-artifact@main
with:
name: webpack artifacts
path: public/
deploy:
name: Deploy Node.js app to AWS
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Download built artifact
uses: actions/download-artifact@main
with:
name: webpack artifacts
path: public
- name: Deploy to AWS
uses: github/deploy-nodejs@master
env:
AWS_ACCESS_KEY: {% raw %}${{ secrets.AWS_ACCESS_KEY }}{% endraw %}
AWS_SECRET_KEY: {% raw %}${{ secrets.AWS_SECRET_KEY }}{% endraw %}
Nice job, you've done it!
It won't be "working" yet, because our next step is to work on the configuration files that AWS will need. But, the logic for this workflow is complete.
- Merge this pull request
- Delete the
staging-workflow
branch
GitHub Actions is cloud agnostic, so any cloud will work. We'll show how to deploy to AWS in this course.
Amazon S3 Buckets are a very flexible type of data storage -- they can be configured to work in many different types of ways. They're popular for their security, scalability, and dependability. Our S3 Bucket will store our application, both in staging and in production.
- Navigate to the Amazon S3 service and click on Create bucket.
- See Create a Bucket on AWS documentation for the most detailed instructions.
- Name the bucket whatever you'd like, and jot the name down.
- Select a region, and jot it down. You'll need it later. These examples use US West (Oregon), also known as us-west-2. If you'd like to choose another region, make sure to update the
aws-config.yml
file to match. - For all other options, accept the defaults.
- Edit the
.github/aws-config.yml
file on this branch, or [use this quick link]({{ repoUrl }}/edit/aws-configuration/.github/aws-config.yml?). (We recommend opening the quick link in another tab.) - Change the value of
s3_bucket:
to match your chosen bucket name. - In the same file, confirm that the
region:
value matches your chosen region for the S3 bucket. - Commit your changes.
I'll respond when I detect a commit on this branch.
You can find your next steps in the [next pull request]({{ url }}).
To deploy successfully to our S3 bucket on AWS, we need a few special configuration files.
The aws-config.yml
file is needed with the Deploy to AWS
Action that we're using. How did we know that it's needed? In the documentation for the GitHub action, there are specific instructions about including this file, and where it needs to sit within the repository.
Whenever you're using a GitHub Action, it's important to read the documentation. There may be details like what secrets or other template files are required for the Action to work as expected.
This file is a bit trickier. The template aws-config.yml
file that was documented with the action has a placeholder for this template, but doesn't specify what we should do.
This file is specific to deploying a serverless application to AWS. Specifically, an AWS SAM template tells AWS how to set up the application's architecture. Read more about it in AWS SAM Template Concepts in the AWS documentation.
In our case, we created the sam-template.yml
for you. It contains information that's specific about the application's endpoints and structure.
I've requested your approval on this pull request. Once you approve this, I will merge.
- Approve this pull request
Uh oh - you reviewed this pull request, but you didn't approve it. Please approve it.
Now that the proper configuration and workflow files are present, let's test this action out!
In this pull request, there's a small change to the game. Once you add the label, you should be able to see the deployment!
- On the right hand side, click Labels or the gear next to it
- Select the label titled stage
You can find your next steps in the [next pull request]({{ url }}).
Deployments to production can be manual (like through a Chat Ops command), or automated (if, say, we trust our pull request review process and we've followed continuous integration practices).
We'll trigger a deployment to the production environment whenever something is committed to main. Our main branch is protected, so the only way for commits to appear on main is for a pull request to have been created and gone through the proper review process and merged.
Let's create a new workflow that deals specifically with commits to main and handles deployments to prod.
- Edit the
deploy-prod.yml
file on this branch, or [use this quick link]({{ repoUrl }}/edit/production-deployment-workflow/.github/CHANGETHIS/deploy-prod.yml?) (We recommend opening the quick link in another tab) - Rename the file in this pull request to
.github/workflows/deploy-prod.yml
- Add a
push
trigger - Add
branches
inside the push block - Add
- main
inside the branches block - Commit your changes to this branch
The file should look like this when you're finished:
name: Production deployment
on:
push:
branches:
- main
The deployment may take a few moments but you've done the right thing. Once the deployment is successful, you'll see green check marks for each run, and you'll see a URL for your deployment.
You can wait for the deployment, or move on to the next steps in the [next pull request]({{ url }}).
If you'd like to come back and merge this once their other workflow is done, you can. 🎉
It looks like you took an action I didn't expect.
I expected you to {{ expected }}. Please try that to continue!
Great! The syntax you used tells GitHub Actions to only run that workflow when a commit is made to the main branch.
Just like with the other workflow, we'll need to build our application and deploy to AWS using the same action as before because we are working with the same Node.js
app.
Continuous delivery is a concept that contains many behaviors and other, more specific concepts. One of those concepts is test in production. That can mean different things to different projects and different companies, and isn't a strict rule that says you are or aren't "doing CD".
In our case, we can match our production environment to be exactly like our staging environment. This minimizes opportunities for surprises once we deploy to production.
- Edit the
deploy-prod.yml
file on this branch, or [use this quick link]({{ repoUrl }}/edit/production-deployment-workflow/.github/workflows/deploy-prod.yml?) (We recommend opening the quick link in another tab) - Add a
build
anddeploy
job to the workflow
It should look like the file below when you are finished. Note that not much has changed from our staging workflow, except for our trigger.
name: Production deployment
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: npm install and build webpack
run: |
npm install
npm run build
- uses: actions/upload-artifact@main
with:
name: webpack artifacts
path: public/
deploy:
name: Deploy Node.js app to AWS
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Download built artifact
uses: actions/download-artifact@main
with:
name: webpack artifacts
path: public
- name: Deploy to AWS
uses: github/deploy-nodejs@master
env:
AWS_ACCESS_KEY: {% raw %}${{ secrets.AWS_ACCESS_KEY }}{% endraw %}
AWS_SECRET_KEY: {% raw %}${{ secrets.AWS_SECRET_KEY }}{% endraw %}
We'll add a final section to our production workflow that packages up our application in a Docker container and publishes it to GitHub Packages. This step is important for the traceability of your deployed artifacts.
We'll only use one new action here created by a GitHubber, which allows us to push a container to GitHub Packages.
mattdavis0351/actions/docker-gpr
All of this happens automatically once a pull request is merged!
- Edit the
deploy-prod.yml
file on this branch, or [use this quick link]({{ repoUrl }}/edit/production-deployment-workflow/.github/workflows/deploy-prod.yml?) (We recommend opening the quick link in another tab) - Add a job to your workflow as follows:
Build-and-Push-Docker-Image: runs-on: ubuntu-latest needs: build name: Docker Build, Tag, Push steps: - name: Checkout uses: actions/checkout@v1 - name: Download built artifact uses: actions/download-artifact@main with: name: webpack artifacts path: public - name: Build, Tag, Push uses: mattdavis0351/actions/docker-gpr@v1 with: repo-token: {% raw %}${{ secrets.GITHUB_TOKEN }}{% endraw %} image-name: {{user.login}}-aws-ttt
- Commit the workflow to this branch.
The complete workflow file should look like this:
name: Production deployment
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: npm install and build webpack
run: |
npm install
npm run build
- uses: actions/upload-artifact@main
with:
name: webpack artifacts
path: public/
deploy:
name: Deploy Node.js app to AWS
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Download built artifact
uses: actions/download-artifact@main
with:
name: webpack artifacts
path: public
- name: Deploy to AWS
uses: github/deploy-nodejs@master
env:
AWS_ACCESS_KEY: {% raw %}${{ secrets.AWS_ACCESS_KEY }}{% endraw %}
AWS_SECRET_KEY: {% raw %}${{ secrets.AWS_SECRET_KEY }}{% endraw %}
Build-and-Push-Docker-Image:
runs-on: ubuntu-latest
needs: build
name: Docker Build, Tag, Push
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Download built artifact
uses: actions/download-artifact@main
with:
name: webpack artifacts
path: public
- name: Build, Tag, Push
uses: mattdavis0351/actions/docker-gpr@v1
with:
repo-token: {% raw %}${{ secrets.GITHUB_TOKEN }}{% endraw %}
image-name: {{user.login}}-aws-ttt
Nice job, you've done it!
- Merge this pull request
- Delete the branch
Now, we just have to wait for the deployment to occur, and for the package to be published to GitHub Packages. When it's completed, you should be able to see it in the [Packages]({{ repoUrl }}/packages) section of your repository. You can get the deployment URL in the [Actions]({{ repoUrl }}/actions) log, just like the staging URL.
- IAM user
- S3 bucket
This course is now complete! I'll stop responding but the fun doesn't have to stop here.
Now...[what will you learn next]({{ host }}/courses)?