This page looks best with JavaScript enabled

Automatic deployment of a web service

 ·  ☕ 5 min read

Introduction

For this workflow, we are going to use docker to build the images, which later are going to be used to deploy the service. In order to update it automatically we are going to use Gitlab CI/CD that updates the docker image whenever a merge request completes on the specified branches (keep in mind we are going to use Gitflow). Lastly Watchtower is going to check if there is a new image on the registry, if so it’s going to update it automatically.

Here is a small image to showcase the workflow:

st=>start: Create git repository
sdev=>operation: Develop the base app
dck=>operation: Create docker configuration
dep=>operation: Deploy the app
runwt=>subroutine: Execute watchtower
devft=>operation: Develop new feature
mrg=>operation: Merge to develop
accept=>condition: Passes all tests?
bddck=>subroutine: Build new docker image
fixerr=>subroutine: Fix errors
bddck=>subroutine: Build new docker image

st->sdev->dck->dep->runwt->devft->mrg->accept
bddck->devft
accept(yes)->bddck
accept(no)->fixerr->mrg

Git

Before anything else let’s setup the git repository. Start by creating a git repository on gitlab and clone it.
We can initialize the project by creating a README:

echo "Automatic deploy project" > README.md
git add .
git commit -m "Initialized the repository"
git push --set-upstream origin main

Now we can switch to develop branch based on main and push it to origin:

git checkout -b develop
git push --set-upstream origin develop

Sample app

In order to be more ilustrative I’m going to create a pretty sample API using Node.js.
Start by creating a feature branch:

git checkout -b feature/api

Create the node repository:

mkdir src
cd src
npm init -y
npm i express

Now create a file named index.js in the src folder with the following content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const express = require('express')
const app = express()
const port = 3000

const products = [
  {
    'id': 0,
    'name': 'Laptop'
  },{
    'id': 1,
    'name': 'Radio'
  }
]

app.get('/api/products', (req, res) => {
  res.send(products)
})

app.get('/api/products/:id', (req, res) => {
  res.send(products[req.params.id])
})

// Running server
if (process.env.NODE_ENV === 'test') {
	module.exports = app;
} else {
	app.listen(port,'0.0.0.0', () => {
        console.log(`Sample app listening at http://0.0.0.0:${port}`)
    })
}

At this point we have an already functional web api. However to make it more realistic, let’s create some tests. Create a folder named src/test , inside of it add the file index.js with the following tests:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// Import the dependencies
var chai = require('chai')
var chaiHttp = require('chai-http')
// Import the application to test
var app = require('../index');
// Configure Chai
chai.use(chaiHttp)
chai.should()

// City API
describe('Testing city API', () => {
  it('should return the first product', done => {
    chai
      .request(app)
      .get('/api/products/0')
      .end((error, response) => {
        response.text.should.equal(JSON.stringify({'id': 0,'name': 'Laptop'}))
        done()
      })
  });
})

Remember to install dependencies with npm i -D mocha chai chai-http while being on src

Finally modify package.json to add a couple custom scripts:

1
2
    "start": "node index.js",
    "test": "NODE_ENV=test npx mocha"

The app is fully finished, before pushing it to the origin let’s add node_modules to gitignore:

echo "src/node_modules/" > .gitignore
git add .
git commit -m "Created sample api"
git push --set-upstream origin feature/api

Don’t forget to merge the feature to develop within GitLab

Docker

The next step is to configure how the Docker image should be build, to do so we create a Dockerfile in the root of the repository:

1
2
3
4
5
6
7
8
FROM node:14
WORKDIR /usr/src/app
COPY src/package*.json ./
RUN npm install -D
COPY src .
EXPOSE 3000
CMD ["start"]
ENTRYPOINT ["/usr/local/bin/npm", "run"]

Let’s also create a file called .dockerignore:

src/node_modules/

We should be able to build and run the docker image locally:

docker build . -t automaticdeploy
docker run --rm -it automaticdeploy

Gitlab

Now we are going to set up Gitlab CI/CD, so let’s create a file named .gitlab-ci.yml in the root of the repository:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
stages:
  - test
  - build

all_tests:
    image: node:14
    stage: test
    script:
        - cd src
        - npm install -D
        - npm run test

docker-build-master:
  # Official docker image.
  image: docker:latest
  stage: build
  services:
    - docker:dind
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
  script:
    - docker build --pull -t "$CI_REGISTRY_IMAGE" .
    - docker push "$CI_REGISTRY_IMAGE"
  only:
    - main

docker-build:
  # Official docker image.
  image: docker:latest
  stage: build
  services:
    - docker:dind
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
  script:
    - docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_BRANCH" .
    - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_BRANCH"
  only:
    - develop

Additionally create the following variables in Gitlab repo (Remember to change the values):
Variables

We can now merge develop to main.

Deployment

Once both starting images have been deployed to the registry (dockerhub in this case), we can run the service in our server:

docker run -d -p 3000:3000 itasahobby/automaticdeploy:latest
docker run -d -p 3333:3000 itasahobby/automaticdeploy:develop
docker run -d --name watchtower -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower --schedule "0 * * * * *"

Finally we have on port 3000 main version running, and develop on port 3333. Whenever we update our repository (and passes all tests) the image will be updated by Watchtower, relaunching the container.

So we can keep adding features to the app without taking care of updating the running service.

You can also check the source code of the original repository or a similar one using Github Actions

Share on

ITasahobby
WRITTEN BY
ITasahobby
InTernet lover