Fundamentals of GitHub Actions - Week 2


In this post, I'll be sharing my journey through the GitHub Actions Bootcamp.
The purpose is to share all the knowledge learned during this bootcamp.
I'll be following the Github Actions Bootcamp. The main goal of this free initiative is to help developers to dominate GitHub Actions and prepare for the official GitHub Actions Certification.
In this bootcamp, I'll learn how to create and maintain automated workflows, develop custom actions, and manage GitHub Actions implementations in an enterprise environment.
YAML and Workflows
YAML means YAML Ain't Markup Language. It is a human-readable format and It's oriented to the creation of configuration files.
Characteristics
Key-Value format: It'a way to organise data. The key allows us to identify the associate value. Example: key: value
Data types: Supports various types such as booleans, strings, integers, floats, and more.
Indentation using spaces: It's a common way to organise code. We use one or more spaces at the beginning of each line to define structure and hierarchy.
Management of text multi-line: We can use separators for multi-line text blocks in order to ensure clarify and readability.
Workflows
In the deploy.yaml
file, the workflow is named "Deploy to server". This will be triggered when changes are pushed to the repository (git push
). This workflow contains two jobs, both running on Ubuntu OS. Each job have steps that print a message in the terminal using the echo
command. The second job uses needs
to ensure the execution only after the first job completes.
name: Deploy to server
on: push
jobs:
first_job:
runs-on: ubuntu-latest
steps:
- name: Hello World
run: echo "Hello World!"
second_job:
needs: first_job
runs-on: ubuntu-latest
steps:
- name: Hello World
run: echo "Hello World! Second Job"
We can also use conditionals. In order to use it, we need to make use of contexts. Contexts are a way to access information about workflow runs, variables, and more. You can find the official documentation of contexts here.
We can access to contexts using the following syntax: ${{ <context> }}
For example, in the deploy.yaml
file, we can use the if
keyword to add a conditional to the second job. In this case, this job will be only executed when the GitHub reference is 'refs/heads/main'
.
# ...
second_job:
if: ${{ github.ref == 'refs/heads/main' }}
needs: first_job
runs-on: ubuntu-latest
steps:
- name: Hello World
run: echo "Hello World! Second Job"
If we need to define our own environment variables, we can use the env
keyword. In this example, the first job uses a variable called NAME
with the value "github"
. This value will be used in the echo
command.
# ...
first_job:
runs-on: ubuntu-latest
env:
NAME: github
steps:
- name: Hello World
run: echo "Hello World! You're name is $NAME"
# ...
We can use actions created by other users. To do this, we use the uses
keyword under the steps
section. In this example, I modified the first job to use the checkout
action. The syntax to use an action is: uses: <username>/<action-name>@<version>
# ...
first_job:
runs-on: ubuntu-latest
env:
NAME: github
steps:
- name: Working dir
run: ls -al
- name: Action
run: actions/checkout@v4
- name: Working dir after action
run: ls -al
# ...
It's possible to share information between steps by using the $GITHUB_OUTPUT
variable. To do this, we need to assign an id
to the step and use the following run
command to save the data: run: echo "<variable-name>=<value>" >> "$GITHUB_OUTPUT"
In order to get the stored data, we need the use of the steps
context. The syntax to get the value is: ${{ steps.<step_id>.outputs.<variable-name> }}
.
# ...
jobs:
first_job:
runs-on: ubuntu-latest
steps:
- name: Saving data
id: step_1
run: echo "test=hello" >> "$GITHUB_OUTPUT"
- name: Get data
run: echo "${{ steps.step_1.outputs.test }}"
# ...
Workflow artifacts
Artifacts allow to store data after a job has completed and share that data with another job in the same workflow. By default, Github stores artifacts for 90 days, but this period can be customized.
In this example, in the first_job, I create a step that generate a log file named test.log
to store some information. Next, I add a step to upload this artifact using uses: actions/upload-artifact@v4
, specifying some parameters such as artifact's name and the file path.
In the second_job, I use the needs
keyword to ensure it runs only after the first job completes. Then, I create a step to download the artifact using uses: actions/download-artifact@v4
with the parameter name
. Finally, I add a step that prints the contents of the artifact using cat test.log
.
name: Deploy to server
on: push
jobs:
first_job:
runs-on: ubuntu-latest
steps:
- name: Creating file
run: |
echo "Test file. Hello world." >> test.log
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: test-file
path: test.log
second_job:
runs-on: ubuntu-latest
needs: first_job
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: test-file
- name: Cat file
run: cat test.log
Continuous Integration (CI) with GitHub Actions
It's a process where developers integrate changes frequently in the repository. Each integration triggers an automatic build and tests. It helps to detect errors as soon as possible and reduce conflicts between branches.
Benefits
Software delivery faster and more frequent.
Best quality in the software due to automatic tests.
Reduction of errors and conflicts.
More reliability in the deployments.
Early feedback.
Environment variables and Secrets
This variables are used in the workflows. It provide a way to pass data into the workflow without hardcoding in the yaml file.
Both type of variables works the same way. The difference is that secrets are encrypted in order to store sensitive information.
To create a new variable or secret on Github, We need to follow this steps:
Navigate to the repository on Github.
Go to Settings > Secrets & Variables and click Actions.
Select between Variables or Secrets tab.
Click on New Repository Value.
Enter a name for your variable and the value.
Service containers
We can use service containers to connect databases, memory caches, and other tool to the workflow. In this example, I added a Redis service using the following parameters: the image name and the exposed ports.
The first step in this job is to install redis-cli. After that, I validate the Redis service using the ping command. Next, I set up node using an action called setup-node
in version 4 with the node-version
parameter to specify the use of node version 18.
name: Deploy
on: push
jobs:
first_job:
runs-on: ubuntu-latest
services:
redis:
image: redis
ports:
- 6379:6379
steps:
- name: Install redis cli
run: sudo apt-get update -y && sudo apt get redis-tools -y
- name: Test redis
run: redis-cli -h localhost -p 6379 ping
- name: setup
uses: actions/setup-node@v4
with:
node-version: "18"
Building, Testing and Deploying with Workflow Automation
The following exercise consists of a simple web page. It's a Node.js app that uses Express as a server and Jest as a testing framework. This project includes a build script that compiles the app and generates the final artifact. In this case a single html index. It also includes a test script that validate the page contains a specific string.
Inside the .github/workflows
directory, I created a deploy.yaml
file. This workflow will be executed whenever changes are pushed to the repository.
For the Build job, It runs on the latest Ubuntu OS and includes the following steps:
Check out the repository code
Set up Node.js, specifying version 18.
Install all the project dependencies.
Run tests to ensure everything passes.
Build the page
Upload the static files as an artifact using the
upload-pages-artifact
action (version 3), specifying the directory where the static files are located.
For the Deploy job, I use the needs
keyword to ensure it runs only after the Build job completes. It also runs on the Ubuntu OS latest version.
I set the required permissions to enable GitHub pages deployment, also I define environment variables for Github Pages, such as the deployment name and URL.
This job contains a single step that is responsible for deploying the page using the actions/deploy-pages
action (version 4), with the job ID set to deployment-pages
.
For the Notify job, I use the needs
keyword to ensure their execution only after the Deploy job completes. It runs on the Ubuntu OS latest version and is configured to execute even if the previous job fails or is canceled.
This job only needs a single step to send a notification to Slack. It uses the action-slack
action (version 3) with parameters such as status and the fields you want to include in the notification.
Additionally, I added a secret variable named SLACK
, which value is the Webhook URL provided by Slack. In order to pass this value to the action, I define an environment variable called SLACK_WEBHOOK_URL
that is required to send the notifications.
name: Deploy
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out the repository
uses: actions/checkout@v4
- name: Setup of Node
uses: actions/setup-node@v4
with:
node-version: "18"
- name: Install dependencies
run: npm install
- name: Run tests
run: npm run test
- name: Build page
run: npm run build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: public/
deploy:
runs-on: ubuntu-latest
needs: build
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
permissions:
pages: write
id-token: write
# Deploy to the github-pages environment
environment:
name: github-pages
url: ${{ steps.deployment-pages.outputs.page_url }}
steps:
- name: Deploy in Github Pages
id: deployment-pages
uses: actions/deploy-pages@v4
notify:
runs-on: ubuntu-latest
needs: deploy
if: always()
steps:
- name: Notify to Slack
uses: 8398a7/action-slack@v3
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK }}
with:
status: ${{ job.status }}
fields: repo,commit,author,job,took
Conclusion
This week, I learned about YAML, Workflows, Artifacts and CI with GitHub Actions! I started with the fundamental concepts with the purpose of understand how to craft powerful workflows. I've learned how to manage artifacts, ensuring our builds and deployments run smoothly. As a practical outcome, I built a workflow that builds the project, deploys it to GitHub Pages, and even sends notifications to Slack.
Subscribe to my newsletter
Read articles from Saul Hernandez directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Saul Hernandez
Saul Hernandez
Continuously learning and growing in software development, with experience in web, mobile, and SQL. I love sharing my journey and exploring innovation in tech! ๐