Home ci/cd with gitlab
Post
Cancel

ci/cd with gitlab

Introduction

I end up repeating the same commands over and over when deploying changes to this blog so this time I decided to set it up properly. Instead of pushing to the docker registry and triggering the update I’ll let gitlab handle that for me. Once the source code is pushed into master, a runner picks up the task, builds the artifacts, pushes the latest release and updates the kubernetes deployment. I’ve done this in the past with github actions and jenkins but since I’m using gitlab and don’t have experience with it, I thought this would be the perfect use case for a CI/CD deployment on gitlab.

iframe

The script

Push the contents below at the root of your project in a file called .gitlab-ci.yml. This script pulls an ubuntu 24.04 image, builds a docker container, runs some commands inside of it and then destroys it. The variables section ensures that the container can run the docker compose build and docker push registry.company.ltd/kube/chirpy:latest commands

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
stages:
  - deploy

deploy_chirpy:
  stage: deploy
  image: ubuntu:24.04
  tags:
    - docker # only run on gitlab runners that have this tag configured
  only:
    - master # only run if changes are pushed to master
  variables:
    GIT_STRATEGY: none
    GIT_SSH_COMMAND: "ssh -i ~/.ssh/id_rsa_runner -o StrictHostKeyChecking=no"
    DOCKER_HOST: unix:///var/run/docker.sock
  before_script:
    # Update and install dependencies
    - apt-get update && apt-get install -y curl ca-certificates git openssh-client lsb-release gnupg

    # Install the Docker CLI
    - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
    - echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
    - apt-get update && apt-get install -y docker-ce-cli docker-compose-plugin
    - chmod 666 /var/run/docker.sock
    # Login to Docker registry
    - echo "$REGISTRY_PASSWORD" | docker login registry.company.ltd -u "$REGISTRY_USERNAME" --password-stdin
    # Setup SSH deploy key
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa_runner
    - chmod 600 ~/.ssh/id_rsa_runner
    - ssh-keyscan gitlab.company.ltd >> ~/.ssh/known_hosts

    # Install kubectl
    - curl -LO "https://minio.company.ltd/public-f1492f08-f236-4a55-afb7-70ded209cb27/kubectl"
    - install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
    - kubectl version --client

    # SSH config 
    - |
      cat >> ~/.ssh/config <<EOF
      Host gitlab.company.ltd
        User git
        Hostname gitlab.company.ltd
        Port 2022
        TCPKeepAlive yes
        IdentitiesOnly yes
        Preferredauthentications publickey
        IdentityFile ~/.ssh/id_rsa_runner
      EOF

    # Setup kubeconfig
    - mkdir -p ~/.kube
    - echo "$KUBE_CONFIG_CONTENT" > ~/.kube/config
    - chmod 600 ~/.kube/config

  script:
    # Clone the repo
    - git clone ssh://git@gitlab.company.ltd/kube/chirpy.git
    - cd chirpy
    # Build and push the Docker image
    - docker compose build
    - docker push registry.company.ltd/kube/chirpy:latest
    # Restart deployment to update production
    - kubectl -n chirpy rollout restart deployment jekyll

Configure the runner

Installing the runner on a docker host is done by running this compose stack

1
2
3
4
5
6
7
8
9
services:
  gitlab-runner:
    image: gitlab/gitlab-runner:latest
    container_name: gitlab-runner
    restart: always
    volumes:
      - /srv/gitlab-runner/config:/etc/gitlab-runner
      - ./ssh-key:/root/.ssh
      - /var/run/docker.sock:/var/run/docker.sock

create an rsa key

1
mkdir ssh-key && cd ssh-key && ssh-keygen -t rsa -b 4096 -f id_rsa_runner -C "gitlab-ci-deploy" -N ""

deploy it and register

1
2
3
docker compose up -d
# you can create the token by going through the step in the next section "Setting up gitlab"
docker exec -it gitlab-runner gitlab-runner register --url https://gitlab.company.ltd --token XXXXXXXXXXXXXXXXXXXXX

Make sure to edit the toml file /srv/gitlab-runner/config/config.toml created by the previous command to specify a “privileged” container and edit the volumes variables so that the container has access to the docker socket: “/var/run/docker.sock:/var/run/docker.sock”

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
concurrent = 1
check_interval = 0
shutdown_timeout = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = "docker-app03"
  url = "https://gitlab.company.ltd"
  id = 1
  token = "XXXXXXXXXXXXXXXXXXXXXXXXXXXX"
  token_obtained_at = 2025-10-28T09:19:32Z
  token_expires_at = 0001-01-01T00:00:00Z
  executor = "docker"
  [runners.cache]
    MaxUploadedArchiveSize = 0
    [runners.cache.s3]
    [runners.cache.gcs]
    [runners.cache.azure]
  [runners.docker]
    tls_verify = false
    image = "ubuntu:24.04"
    privileged = true
    volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    shm_size = 0
    network_mtu = 0

Setting up gitlab

Add the environment variables into projectname>ci/cd settings as pictured below. You’ll need to create a role and service account for the deployment operator if you’re using k8s. I’ve explained how to do this in a previous article. The registry user and password are your gitlab’s username and the password can be created on gitlab’s User icon on the top left>Preferences>Access tokens.

iframe

Set up a gitlab instance runner which can be used by any pipeline by going to the admin area>CI/CD>runner and going through the wizard. Make sure to specify the “docker” tag. Hold on to the secret token given during the wizard and refer to the previous section on how to configure to register the runner.

iframe

Specify the ssh key in the projectname>repository settings>deploy keys. This ensures that the pipeline is able to clone the repository

iframe

Once you commit your changes and push them to gitlab you should see a new job appear in the projectname>build>jobs

iframe

Ready, set, go

Conclusion

Another short article to close up the month.

The next step would be to have encrypted helm charts deployed through gitlab. Helm provides flexibility for a use case where you have to deploy multiple versions of the same app with different parameters and configurations. One version would include your devtools for example whereas the production one would be barebones and optimized for performance. Another typical example would be if you want to have multiple prod environments and a blue-green deployment strategy. As an MSP you would also want to deploy the same stack for several customers for example which would also be a prime use case for helm charts. Helm commands are issued by the helm cli and work in the same way kubectl does. It’s just another layer of abstraction that allows you to do templating on your manifests.

The first gotcha I got while using helm charts is that once you deploy it you can only apply changes through helm. So even if you see the helm deployment or replicas in your cluster you won’t be able to apply changes through yaml manifests.

Hope you liked this one. Stay safe and see you on the next one

Cheers

This post is licensed under CC BY 4.0 by the author.