Automating your workflow with GitHub Actions
From basics to advanced deployments with real-world examples

Search for a command to run...
From basics to advanced deployments with real-world examples

Gf
Introduction In web development, efficient data fetching is essential for responsive applications. While REST APIs have been the traditional choice for client-server communication, their limitations often lead to over-fetching or under-fetching data....

Breaking Boundaries with Node.js: Crafting a Custom Compiler with Advanced Optimization Techniques and Expanded Functionality

It’s with immense gratitude and excitement that we look back on the phenomenal success of HACKHAZARDS, an event that was not only a celebration of innovation and technology but also a showcase of remarkable partnerships. A key player in enhancing the...

Introduction Structured Query Language (SQL) is a powerful tool for managing and manipulating data in databases. It serves as a universal language for interacting with relational databases, allowing users to retrieve, update, and analyze data efficie...

With GitHub Actions, developers can automate activities, workflows, and processes within their repositories. GitHub Actions is a potent CI/CD (Continuous Integration and Continuous Deployment) solution that is incorporated directly into GitHub. You can build, test, and publish your code directly from GitHub using GitHub Actions, which boosts productivity and expedites development cycles. We'll go over the fundamentals of GitHub Actions, examine its basic ideas, and offer some examples in this blog post so you can get going right away.
GitHub Actions allows you to automate tasks and workflows based on events within your GitHub repository. These events can include pushes to the repository, creating pull requests, and more. There is a wide range of possibilities for actions which can be used to trigger these workflows which are defined using YAML syntax and stored in the .github/workflows directory of your repository.
Workflow: A workflow is an automated process defined in a YAML file that runs one or more jobs. Workflows are triggered by specific events (e.g., push, pull_request).
Job: A job is a set of steps that execute on the same runner. Jobs can run sequentially or in parallel.
Step: A step is an individual task that can run commands, use actions, or run scripts.
Action: An action is a custom application that performs a complex but frequently repeated task and can be reused in different workflows.
GitHub Actions offers several benefits:
Integration: Seamlessly integrates with your GitHub repository.
Customization: Define custom workflows tailored to your project's needs.
Scalability: Handle simple automation tasks or complex CI/CD pipelines.
Community Support: Leverage a vast library of pre-built actions created by the GitHub community.
Let's start by creating a simple GitHub Action that runs whenever code is pushed to the repository. This action will install dependencies, run tests, and build the project.
Create a New Repository: Start by creating a new repository on GitHub.
Create a Workflow File: In your repository, create a new directory called .github/workflows. Inside this directory, create a file named ci.yml.
Define the Workflow: Open ci.yml and add the following YAML configuration:
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '20'
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
- name: Build project
run: npm run build
name: Specifies the name of the workflow.
on: Defines the events that trigger the workflow. Here, the workflow is triggered by a push event.
jobs: Contains one or more jobs that execute in the workflow.
runs-on: Specifies the type of runner to execute the job (e.g., ubuntu-latest).
steps: Lists the individual steps in the job.
name: Describes the step.
uses: Specifies an action to run (e.g., actions/checkout@v2 to check out the repository).
run: Runs a command directly on the runner.
Next, let's create a workflow to deploy a static site to GitHub Pages whenever changes are pushed to the main branch.
Create the Workflow File: Create another workflow file in the .github/workflows directory named deploy.yml.
Define the Deployment Workflow: Add the following YAML configuration:
name: Deploy to GitHub Pages
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '20'
- name: Install dependencies
run: npm install
- name: Build project
run: npm run build
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist
on: Specifies that the workflow runs on a push to the main branch.
deploy job: Includes steps to check out the code, set up Node.js, install dependencies, build the project, and deploy to GitHub Pages using peaceiris/actions-gh-pages.
For a more complex example, let's create a workflow that builds a Docker image and deploys it to a container registry whenever a pull request is merged into the main branch.
Create the Workflow File: Create a new workflow file in the .github/workflows directory named docker-deploy.yml.
Define the Docker Deployment Workflow: Add the following YAML configuration:
name: Build and Deploy Docker Image
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v2
with:
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/my-app:latest
- name: Deploy to Kubernetes
run: |
kubectl config set-context --current --namespace=my-app
kubectl apply -f k8s/deployment.yaml
on: Triggers the workflow on a push to the main branch.
build job: Includes steps to check out the code, set up Docker Buildx, log in to Docker Hub, build and push the Docker image, and deploy to Kubernetes.
docker/setup-buildx-action@v1: Sets up Docker Buildx, a Docker CLI plugin for extended build capabilities with BuildKit.
docker/login-action@v2: Logs in to Docker Hub using credentials stored in GitHub Secrets.
docker/build-push-action@v2: Builds and pushes the Docker image to Docker Hub.
kubectl commands: Deploy the new Docker image to a Kubernetes cluster.
In this example, we'll create a workflow that deploys a multi-service application composed of multiple microservices using Docker Compose. Each microservice will have its own Docker container and the entire application will be orchestrated and deployed using Docker Compose.
Create the Workflow File: Create a new workflow file in the .github/workflows directory named multi-service-deploy.yml.
Define the Deployment Workflow: Add the following YAML configuration:
name: Deploy Multi-Service App
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker images
run: |
docker-compose -f docker-compose.yml build
docker-compose -f docker-compose.yml push
- name: Deploy using Docker Compose
run: |
docker-compose -f docker-compose.yml up -d
Compose File: Create a docker-compose.yml file in the root of your repository. Define the services and their configurations within this file. For example:
version: '3'
services:
service1:
image: my-service1:${{ github.sha }}
ports:
- "3000:3000"
environment:
- NODE_ENV=production
# Add any other service configurations as needed
service2:
image: my-service2:${{ github.sha }}
ports:
- "4000:4000"
environment:
- NODE_ENV=production
# Add any other service configurations as needed
# Add more services as required
Dockerfiles: Create Dockerfiles for each microservice in your repository. These Dockerfiles will define how to build the Docker images for each service.
Example Dockerfile for service1:
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
Example Dockerfile for service2:
FROM python:3.9
WORKDIR /app
COPY requirements.txt ./
RUN pip install -r requirements.txt
COPY . .
EXPOSE 4000
CMD ["python", "app.py"]
on: Triggers the workflow on a push to the main branch.
deploy job: Includes steps to check out the code, set up Docker Buildx, log in to Docker Hub, build and push Docker images using Docker Compose, and deploy the multi-service application using Docker Compose.
You can pass environment variables to your services using the environment key in the docker-compose.yml file. This allows you to configure your services dynamically based on the environment.
Docker Compose supports defining health checks for your services, ensuring that they are healthy and ready to receive traffic before being added to the pool. You can also define dependencies between services to ensure they start up in the correct order.
Deploying multi-service applications with Docker Compose and GitHub Actions provides a scalable and efficient solution for managing complex architectures. By automating the deployment process using GitHub Actions, you can ensure consistency and reliability in your deployments while reducing manual effort. Experiment with different configurations and scaling options to find the setup that best fits your application's requirements.
With this example, you can take your automation efforts to the next level and efficiently manage even the most complex deployments with ease.
Matrix builds allow you to run multiple jobs in parallel with different configurations. This is particularly useful for testing your application across different environments.
Example: Testing a Node.js application on multiple versions of Node.js:
name: Node.js CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12, 14, 16]
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
To speed up your workflows, you can cache dependencies so that they don't need to be downloaded on every run. This is particularly useful for large projects with many dependencies.
Example: Caching npm dependencies:
name: Node.js CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: '20'
- name: Cache npm dependencies
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
GitHub Actions supports self-hosted runners, which allow you to run workflows on machines that you control, such as your own servers or virtual machines. Self-hosted runners provide more control over the execution environment and can be customized to meet specific requirements, such as hardware configurations, software dependencies, and network access. This feature is especially useful for running workflows that require access to private resources or specialized hardware.
Example: Using a self-hosted runner for CI/CD:
name: CI
on: [push]
jobs:
build:
runs-on: self-hosted
steps:
- name: Checkout code
uses: actions/checkout@v2
# Add build steps here
GitHub Actions allows you to define environment variables and store sensitive information securely as secrets. Environment variables can customize your workflows dynamically, while secrets provide a secure way to store sensitive data such as API keys, tokens, and passwords. You can reference these variables and secrets within your workflow YAML files, enabling greater flexibility and security in your automation pipelines.
Example: Using environment variables and secrets:
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
environment:
NODE_ENV: production
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '20'
- name: Install dependencies
run: npm install
# Use environment variables in your workflow steps
- name: Run tests
run: npm test
env:
API_KEY: ${{ secrets.API_KEY }}
Use Secrets for Sensitive Data: Store sensitive information such as API keys and passwords in GitHub Secrets, and reference these secrets in your workflows using ${{ secrets.SECRET_NAME }}.
Limit Permissions: Minimize the permissions granted to the GitHub token in your workflows to reduce the risk of unauthorized access.
Review Third-Party Actions: Carefully review the code and permissions of third-party actions before using them in your workflows to ensure they do not introduce security vulnerabilities.
GitHub Actions provides a versatile and efficient way to automate your CI/CD pipelines directly within GitHub. From simple workflows that run tests and build projects to more complex workflows that deploy Dockerized applications to Kubernetes, GitHub Actions can handle it all. By following these examples, you can quickly set up workflows to automate testing, building, and deploying your projects. Start experimenting with GitHub Actions today to streamline your development process and increase productivity. Happy coding!
To further enhance your understanding and capabilities with GitHub Actions, consider exploring the following resources:
By leveraging these resources, you'll be well on your way to becoming proficient with GitHub Actions and reaping the benefits of automated workflows.