Software development is a continuous process, with regular code updates from time to time. Whether from adding new features, fixing bugs or just performing general chores like updating project dependencies now and then. One effective approach to easily push and preview these local development changes to a staging or production environment is to create a continuous integration and deployment workflow. In the next few steps, I will teach you how to automatically deploy your project from GitHub to DigitalOcean using GitHub Actions.
Prerequisites:
- A GitHub account
- A DigitalOcean account. Please sign up using our referral link and get a $100 free credit valid for 60 days.
ā
Table of Contents:
Continuous Integration and Deployment
Continuous Integration (CI) is the automated processing of building and testing code changes made in a feature branch before it is merged into the main branch if the tests pass. This practice if done correctly prevents the introduction of defects into the codebase. Continuous Deployment (CD) is the automated processing of building, testing and deploying code pushed or merged to the main branch to a production or staging environment. If the process of deployment is not automated, it is referred to as Continuous Delivery. Continuous Integration and Deployment (CI/CD) provides rapid feedback, fast defects identification and seamless deployment of new features.
Continuous Integration and Deployment Pipeline
About GitHub Actions
GitHub Actions are event-driven features provided to automate software development workflows. Actions are composed of several components such as workflows, events, jobs, steps, actions and runners. Workflows are triggered when certain events occur or according to a schedule. Events are activities that trigger a workflow. Examples of such events are pushing commits to a repository, performing a pull request or setting a cronjob. You can find the complete list of events that can trigger workflow here. A job is a set of steps that execute on the same runner. You can run several jobs in a workflow, either sequentially or in parallel by default.
A runner is a server that hosts the GitHub Actions runner application installed. The Actions runner application can be hosted by GitHub in a virtual hosting environment or could be self-hosted. Every job has a composition of shell commands or steps made of standalone commands called actions.
Components of a GitHub Actions Workflow
Continuous Integration With GitHub Actions
Let's set up a continuous integration workflow to manage a project into feature branches. For this example, we will be using a basic [NestJS] application. NestJS is a progressive framework for building efficient, scalable Node.js web applications.
Create Your NestJS App
In your local development environment, clone the NestJS TypeScript starter project with Git.
git clone https://github.com/nestjs/typescript-starter.git my_server
cd my_server
npm install
Create A GitHub Repository
Log in to GitHub and create a new private or public repository to host the codebase. When the repository has been created, push the local project to the repository from the terminal.
git remote rm origin
git remote add origin https://github.com/username/repo_name.git
git branch -M main
git push -u origin main
Add Integration Workflow
All GitHub Actions are placed under the .github > workflows
directory in the project root folder. Let's create the continuous integration workflow to manage automated build and testing before merging to the codebase. Workflows are in YAML file format. You can pick any name you want for your workflow file except action.yml to avoid linting conflicts.
mkdir -p .github/workflows
cd .github/workflows
touch integration.yml
Create the Integration Workflow
name: Continuous Integration
on:
pull_request:
branches:
- main
jobs:
merge_pull_request:
runs-on: ubuntu-latest
steps:
- name: Checkout main branch
uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: '14.x'
- name: Install dependencies
run: npm ci
- name: Test application
run: npm test
- name: Build application
run: npm run build
Continuous Integration Workflow
git checkout -b feature-test
src > app.service.ts
and src > app.controller.spec, for example, change 'World' to 'Universe' and push the commit.
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello Universe!';
}
}
src/app.service.ts
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let app: TestingModule;
beforeAll(async () => {
app = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
});
describe('getHello', () => {
it('should return "Hello Universe!"', () => {
const appController = app.get<AppController>(AppController);
expect(appController.getHello()).toBe('Hello Universe!');
});
});
});
src/app.controller.spec.ts
Workflow Runs
Workflow Run Details
You can then merge the pull request if all the tests passed and other metrics like code quality and coverage have been met.
Setting Up Your Droplet
With your DigitalOcean account created and logged in, let's create a droplet to run the Ubuntu 20.04 LTS server. To quickly do that, use the DigitalOcean 1 Click Ubuntu server installation. On the setup page, you can choose your preferred server configuration covering the image, usage plan (amount of RAM, storage spaces, CPU cores and bandwidth), datacenter region and authentication (SSH preferably).
Droplet Server Configuration
After you have confirmed your preferred server configuration and chosen a hostname for your droplet, click the Create Droplet
button at the end of the page to create and initialise your droplet. You should be presented with the following screen when the droplet is ready. Note the IP address of the server.
Ubuntu 20.04 Server On a Droplet
To gain access to the Ubuntu server, connect securely using SSH via a terminal with the user as root and the IP address.
ssh root@your_ip_address
If you cannot connect via SSH due to "Error Permission denied (publickey)", take a look at this community question. If everything works fine, you should be logged in to your Droplet as the root user.
Continuous Deployment to DigitalOcean
With the deployment server set up and running, let's add a deployment workflow to push changes to the server automatically.
Project Setup
If you don't have a project to work with, you can clone the deployment-starter branch of this basic Nest.js project, then create a new repository on GitHub and push your project to your repository.
git clone -b deployment-starter https://github.com/krizten/digitalocean-actions.git
cd digitalocean-actions && npm install
git remote rm origin
git remote add origin https://github.com/username/repo_name.git
git branch -M main
git push -u origin main
Configure Self-hosted Runner
For deployment, we will be using the self-hosted runner option in the deployment workflow. For this example, the runner will be at a repository level hosted on the DigitalOcean Droplet that was set up in the previous section. Go to the Settings > Actions
section of the GitHub repository, then click the Add runner
button.
Self-hosted Runners
Since we have Ubuntu installed on the Droplet, select Linux
as the operating system and x64 as the architecture.
Self-hosted Runner Configuration
To integrate the self-hosted runner into the Ubuntu server, log in to the Droplet via the terminal and follow the Download
and Configure
instructions provided in your Settings > Actions
page.
ssh root@ip_address
adduser newusername sudo
sudo su - newusername
mkdir actions-runner && cd actions-runner
curl -o actions-runner-linux-x64-2.278.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.278.0/actions-runner-linux-x64-2.278.0.tar.gz
tar xzf ./actions-runner-linux-x64-2.278.0.tar.gz
./config.sh --url https://github.com/username/repo_name --token your_token
sudo ./svc.sh install
sudo ./svc.sh start
cd ~
curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -
sudo apt install nodejs
sudo npm install pm2 -g
Add Deployment Workflow
name: Continuous Deployment
on:
push:
branches:
- main
jobs:
deployment:
runs-on: self-hosted
steps:
- name: Checkout main branch
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: '14.x'
- name: Install dependencies
run: npm ci
- name: Test application
run: npm test
- name: Build application
run: npm run build
Deployment Workflow
cd /actions-runner/_work/digitalocean-actions/digitalocean-actions
pm2 start dist/main.js --name MyAppName
- name: Restart server application
run: pm2 restart MyAppName
Restart Step In Deployment Workflow
At this point, the project should be accessible on the public IPv4 address http://ip_address:3000
. You may want to configure a domain name and incorporate SSL certificates, but these are out of the scope of this tutorial.
Conclusion
GitHub Actions features an easy way to automate tasks within your software development life cycle. With GitHub Actions, you can manage the codebase, review code commits and handle continuous deployment to platforms like DigitalOcean all from GitHub.
If you need a deeper dive into GitHub Actions, head over to the official docs and learn how to integrate it to match your use case. You can also check DigitalOcean if you are looking for a good cloud infrastructure provider using our referral link.
If you have any questions or feedback to share, please don't hesitate to comment below. Don't forget to subscribe and share if you enjoyed reading this article.