Skip to main content

GitLab CI Pipeline - Deploy website with Nginx Docker Container

850 words·
GitLab CI Pipeline Nginx Docker Git

Overview
#

In this tutorial I’m using the following setup based on Ubuntu 22.04 servers, GitLab is dockerized:

192.168.70.4 # GitLab
192.168.70.5 # GitLab Runner
192.168.70.6 # Main / Production Deployment 
192.168.70.7 # Dev Deployment

GitHub Repository:
https://github.com/jueklu/gitlab-ci-container-deployment


Prerequisites
#

Create SSH Key
#

Create a SSH key pair on the server where GitLab is deployed:

# Create SSH key pair
ssh-keygen -t rsa -b 2048

Create a User for the Container Deployment
#

On the deployment servers, create a new user “gitlab-deployment” for the GitLab CI pipeline:

# Create user for the GitLab deployment
sudo adduser gitlab-deployment

# Create user for the GitLab deployment: Optional without password
sudo adduser --disabled-password gitlab-deployment

# Add the user to the Docker group
sudo usermod -aG docker gitlab-deployment

Add Public Key & Fingerprint
#

Copy the public SSH key to the authorized keys file of the Deployment servers:

# Copy the public SSH key: Default "id_rsa.pub" key
ssh-copy-id gitlab-deployment@192.168.70.6
ssh-copy-id gitlab-deployment@192.168.70.7

# Copy the public SSH key: Specific key
ssh-copy-id -i ~/.ssh/filename.pub gitlab-deployment@192.168.70.6
ssh-copy-id -i ~/.ssh/filename.pub gitlab-deployment@192.168.70.7

SSH into the deployment servers to add the fingerprint:

# SSH into deployment server
ssh gitlab-deployment@192.168.70.6
ssh gitlab-deployment@192.168.70.7

DNS Entry
#

Make sure the deployment servers can resolve the DNS names of GitLab and the GitLab Registry:

# Add the following DNS entry
192.168.70.4 gitlab.jklug.work gitlab-registry.jklug.work



GitLab Repository
#

Create Dev Branch
#

# Create a new "dev" branch
git checkout -b dev

# Verify the current branch
git branch

# Shell output:
* dev
  main
# Push changes to remote repository
git push origin dev

File & Folder Structure
#

The file and folder structure of both the main and thedev branch of the GitLab repository look like this:

├── Dockerfile
├── .gitlab-ci.yml
├── README.md
└── website
    └── index.html

SSH Key / CI/CD Variable
#

Add the previously created private SSH key to the GitLab Repository:

  • Go to: (Project)

  • Select “Settings” > “CI/CD”

  • Expand the “Variables” section

  • Click “Add variable” to add a variable for your private SSH Key.

Note: Remove the “Protect variable” option.


Dockerfile
#

Dockerfile
FROM nginx:1.18

COPY website/index.html /usr/share/nginx/html
EXPOSE 80

index.html
#

Create some example HTML files for the dev and the production / main branch:

website/index.html

Dev branch:

<!DOCTYPE html>
<html>

<head>
	<title>jklug.work</title>
</head>

<body>
	<h1>Deploy website with Nginx container</h1>
  <p>dev branch</p>
</body>

</html>

Main branch:

<!DOCTYPE html>
<html>

<head>
	<title>jklug.work</title>
</head>

<body>
	<h1>Deploy website with Nginx container</h1>
  <p>main / production branch</p>
</body>

</html>

GitLab CI Pipeline
#

Overview
#

The following pipeline consists of two stages:

Stage 1: build

  • Creates a Nginx container that hosts an index.html file and pushes the Nginx container to the GitLab container registry.

Stage 2: deploy

  • Uses an alpine container to connect to a remote server, pulls the Nginx container from stage 1, removes - if existing - the old Nginx container and starts a new Nginx container from the pulled image.

CI Pipeline
#

.gitlab-ci.yml

Dev branch:

stages:
  - build
  - deploy


build:
  stage: build
  image: docker:20.10.16
  variables:
    DOCKER_DRIVER: overlay2
    DOCKER_HOST: tcp://docker:2375
    DOCKER_TLS_CERTDIR: ""
  services:
    - docker:20.10.16-dind

  script:
    - docker build -t $CI_REGISTRY_IMAGE:latest .
    - docker tag $CI_REGISTRY_IMAGE:latest $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
    - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME

# Deploy job for dev branch
deploy_development:
  stage: deploy
  image: alpine:latest
  variables:
    IP: "192.168.70.7"
    USER: "root"
  before_script:
    - apk update && apk add openssh-client
    - if [ -f "$ID_RSA" ]; then chmod og= $ID_RSA; fi
  script:
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $USER@$IP "docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY"
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $USER@$IP "docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME"
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $USER@$IP "docker container rm -f website-preview || true"
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $USER@$IP "docker run -d -p 80:80 --restart=unless-stopped --name website-preview $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME"

  only:
    - dev

Main branch:

stages:
  - build
  - deploy

build:
  stage: build
  image: docker:20.10.16
  variables:
    DOCKER_DRIVER: overlay2
    DOCKER_HOST: tcp://docker:2375
    DOCKER_TLS_CERTDIR: ""
  services:
    - docker:20.10.16-dind

  script:
    - docker build -t $CI_REGISTRY_IMAGE:latest .
    - docker tag $CI_REGISTRY_IMAGE:latest $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
    - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME

# Deploy job for main / production branch
deploy_production:
  stage: deploy
  image: alpine:latest
  variables:
    IP: "192.168.70.6"
    USER: "root"
  before_script:
    - apk update && apk add openssh-client
    - if [ -f "$ID_RSA" ]; then chmod og= $ID_RSA; fi
  script:
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $USER@$IP "docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY"
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $USER@$IP "docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME"
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $USER@$IP "docker container rm -f website-preview || true"
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $USER@$IP "docker run -d -p 80:80 --restart=unless-stopped --name website-preview $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME"

  only:
    - main

Verify the deployment
#

# Curl the main / production deployment
curl 192.168.70.6:80
curl 192.168.70.7:80

Verify the Docker containers:

# List containers on main / production deployment server:
docker ps

# Shell output:
CONTAINER ID   IMAGE                                                     COMMAND                  CREATED         STATUS         PORTS                               NAMES
7d3582cf47ec   gitlab-registry.jklug.work/root/01-nginx-container:main   "/docker-entrypoint.…"   3 minutes ago   Up 3 minutes   0.0.0.0:80->80/tcp, :::80->80/tcp   website-preview
# List containers on dev deployment server:
docker ps

# Shell output:
CONTAINER ID   IMAGE                                                    COMMAND                  CREATED              STATUS              PORTS                               NAMES
f24932a33911   gitlab-registry.jklug.work/root/01-nginx-container:dev   "/docker-entrypoint.…"   About a minute ago   Up About a minute   0.0.0.0:80->80/tcp, :::80->80/tcp   website-preview