Home CI / CD


Learning Outcomes

  • define continuous integration (CI)
  • define continuous delivery (CD)
  • define continuous deployment(CD)
  • distinguish between development, testing (or staging), and production environments
  • use webhooks to deploy a simple web application



Video walkthrough of this lab.

Set up a SSH Key for GitLab

Start the Google Cloud Shell.

Generate a SSH key:

ssh-keygen -t ed25519 -C "YOUR EMAIL ADDRESS HERE"

Display the public key:

cat .ssh/id_ed25519.pub

Copy the output into your clipboard. Log in to GitLab. Go to your settings and then to the “SSH Keys” page. Paste your public key into the “Key” field. Click on the “Add key” button.

Create a Deployment Script for an Application

Create myapp/deploy.sh with the following contents:

cd $HOME
wget -O $3.tar.gz https://gitlab.com/api/v4/projects/$1/repository/archive?private_token=$2
tar zxf $3.tar.gz
cp -R *-master-*/* $3
rm -r $3.tar.gz *-master-*
cd $3
npm install
pm2 reload www

Make sure myapp/deploy.sh has the execution bit set:

chmod +x ~/myapp/deploy.sh

Create a View for the Webhook

Create myapp/views/webhook.ejs with the following contents:

<!DOCTYPE html>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>

This page will never be seen, but Gitlab still needs a valid response to know that the webhook succeeded.

Create a Route for the Webhook

Create myapp/routes/webhook.js with the following contents:

var express = require('express');
var router = express.Router();
const { spawn } = require('child_process');

/* POST home page. */
router.post('/', function(req, res, next) {
    console.log( req.body );
    if ( process.env.WEBHOOK_TOKEN == req.headers['x-gitlab-token'] ) {
        const projectID = req.body['project']['id']
        const pathParts = req.body['project']['path_with_namespace'].split('/');
        const projectSlug = pathParts[1];
        const scriptPath = process.env.HOME +'/' + projectSlug + '/deploy.sh'
        const subprocess = spawn(
                detached: true,
                stdio: 'ignore'
    } else {
        var forbiddenError = new Error( 'invalid or missing x-gitlab-token header');
        forbiddenError.status = 403;
        throw forbiddenError;
    res.render('webhook', { title: 'Express: webhook POST' });

module.exports = router;

Require the webhook route in myapp/app.js:

var webhookRouter = require('./routes/webhook');

Link the webhook route to the webhook path:

app.use('/webhook', webhookRouter);

Create a .gitignore File

Create myapp/.gitignore with the following contents:


Set Up a GitLab Project

In GitLab, go to “Projects | Your projects”. Click on the “New project” button. Choose “Create blank project”. For “Project name”, enter “myapp”. Click on the “Create project” button. Follow the instructions to “Push an existing folder”.

Go to the project settings page. Go to the “Webhooks” page. For the URL, enter http://NNN.NNN.NNN.NNN:8080/webhook. For “Secret Token”, enter a random sequence of letters and numbers. For the list of triggers, make sure only “Push events” is checked. Make sure “Enable SSL verification” is unchecked.

Go to your GitLab profile settings page. Go to the “Access Tokens” page. For “Name”, enter “deploy”. For “Scopes”, make sure “read_api” and “read_repository” are checked. Click on the “Create personal access token” button. Record the generated token value. THIS IS THE ONLY TIME YOU CAN SEE IT.

Deploy Application by Hand to Bootstrap

SSH into the virtual machine:

gcloud compute ssh lab1 --zone YOUR_ZONE_GOES_HERE

Install wget:

sudo apt install wget

Make a note of project ID for your GitLab project. (It is located just below the project name on the project overview page). Retrieve the latest project archive (fill in your actual values for YOUR_PROJECT_ID and YOUR_ACCESS_TOKEN):

wget -O myapp.tar.gz https://gitlab.com/api/v4/projects/YOUR_PROJECT_ID/repository/archive?private_token=YOUR_ACCESS_TOKEN

Unpack the archive:

tar zxf myapp.tar.gz

We are going to need to re-start the application with the correct environment variables set so we will stop and delete the current application:

pm2 stop www
pm2 delete www

Copy the latest version of the application into the myapp directory:

cp -R *-master-*/* myapp

Remove the archive and the unpacked directory:

rm -r myapp.tar.gz *-master-*

Change to application bin directory:

cd myapp/bin

Re-start the application with the correct environment variables (substituting your actual values for YOUR_ACCESS_TOKEN and YOUR_WEBHOOK_SECRET):


View the streaming logs for the application:

pm2 logs

Verify that the Webhook Works

In a new tab, browse to the application using the external IP address for the virtual machine (http://NNN.NNN.NNN.NNN:8080).

Back in the Google Cloud Shell environment, change the title property in myapp\routes\index.js. Stage, commit, and push the changes to the repository. Verify that the change has be automatically deployed to http://NNN.NNN.NNN.NNN:8080 by refreshing the page.


  1. Add a new route to the application, /guestbook.
  2. In the router, create a global array variable that maintains the list of vistors.
  3. For a GET request, display the contents of the array, one per line. Also, display a form where the user can enter their name and submit the form (as a POST request).
  4. For a POST request, add the new name to the array and re-display the array.
  5. Stage, commit, and push your changes.
  6. Confirm that the changes are properly deployed to the virtual machine.