Software management with GitLab, Renovate Bot and Docker

Insider Blog

Managing a multiservice server can be tricky. Not every software is compatible with another, for example different requirements for database or PHP-versions.

Additionally, all the software should be in the latest version, to mitigate possible security risks.

There are many ways to handle this: Ansible, Chef etc.

Out goal was to have an easy to use, automated and free solution.

Here are the goals:

  • use the GitOps approach to store and version control the deployment
  • use containers to run the software
  • get automated security updates and opt in for minor/major version updates

This is the stack we ended with:

About the stack

We have been using Docker in production for some time now. Depending on the situation, we create the compose file directly on the server, manage it over portainer or scp it from a pipeline.

GitLab is out primary tool for version control. Additionally, a GitLab Runner takes care of running pipelines.

Renovate automates dependency updates. PHP, Go, Python, Docker - to name a couple. We already use it for various projects.

Container with Docker and Docker Compose

The main reason why we chose Docker is the ability to access a remote Docker host and execute Docker commands. Refer to the official documentation for more information.

We are using ssh to access our target server.

DOCKER_HOST=ssh://[username@]<IP or host>[:port] docker compose up --wait

GitOps with GitLab

The idea behind GitOps is to use a git repository to store the configuration. Here is an example:

.
├──.gitlab-ci.yml             # pipeline definition
├── renovate.json             # Renovate configuration
├── nextcloud
   ├── docker-compose.yml   # Nextcloud file hosting and collaboration
└── traefik
    └── docker-compose.yml   # Traefik reverse proxy configuration
nextcloud/docker-compose.yaml
volumes:
  nextcloud:
  db:

services:
  db:
    image: mariadb:11.8
    restart: unless-stopped
    volumes:
      - db:/var/lib/mysql
    environment:
      - MARIADB_ROOT_PASSWORD=${NEXTCLOUD_MARIADB_ROOT_PASSWORD:?error}
      - MARIADB_PASSWORD=${NEXTCLOUD_MARIADB_PASSWORD:?error}
      - MARIADB_DATABASE=nextcloud
      - MARIADB_USER=nextcloud
    command:
      - --transaction-isolation=READ-COMMITTED
      - --log-bin=binlog
      - --binlog-format=ROW
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 15s
      timeout: 5s
      retries: 6

  nextcloud:
    image: nextcloud:32.0.0
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - nextcloud:/var/www/html
    environment:
      - MYSQL_PASSWORD=${NEXTCLOUD_MARIADB_PASSWORD:?error}
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_HOST=db
NEXTCLOUD_MARIADB_ROOT_PASSWORD=
NEXTCLOUD_MARIADB_PASSWORD=

are stored as CI/CD Variables


To deploy the stack, we have a pipeline:

.gitlab-ci.yml
stages:
  - deploy

deploy:
  stage: deploy
  image: docker:28
  variables:
    DOCKER_HOST: ssh://[username@]<IP or host>[:port]
  script:
    - for file in $(find . -type f -name docker-compose.yml); do docker compose -f $file up --remove-orphans --wait; done
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

The pipeline runs with every commit on the default branch, iterates through all docker-compose.yml files, and deploys them.

Keep your deployments up-to-date with Renovate Bot

Here is where Renovate kicks in.

renovate.json
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:best-practices"
  ]
}

Renovate will create Merge Requests for every update. Nice!

Automated security updates and opt-in for minor/major version updates

The current configuration creates Merge Requests for every update, but we want security updates to happen without user interaction.

You need to understand how Docker images are versioned / tagged. It depends on the image, but let's take a look on the official MariaDB

There are 11.8.3-noble, 11.8-noble, 11-noble, lts-noble, 11.8.3, 11.8, 11, lts, all of which refer to the same image.

11.8.3-noble means that we get MariaDB in version 11.8.3 based on Ubuntu Noble. 11.8-noble means that we get MariaDB in version 11.8.<latest_path> based on Ubuntu Noble.

When a neu version of MariaDB is released, e.g 11.8.4-noble, a new 11.8.4-noble tag will be pushed, but the 11.8-noble will be updated.

This same is true for the Ubuntu update. The 11.8.3-noble tag can be updated, if the image is rebuild with the latest Ubuntu image.

Running docker compose up on mariadb:11.8-noble will do nothing, because Docker is not aware of that change.

In the example above we reference mariadb:11.8, because we want to use the latest patch version based on the most current OS.


How to tell Docker that there is a new version?

The main idea is to attach a digest to the Docker image.

When running Renovate for the first time, it will find the reference to mariadb:11.8 and create a merge request to pin the Digest to something like mariadb:11.8@sha256:ae6119716edac6998ae85508431b3d2e666530ddf4e94c61a10710caec9b0f71

Renovate will also monitor the upstream changes, so that every time the image get updated, the digest will change and Renovate will create a merge request.

To merge these updates automatically, we need to do some adjustments.

renovate.json
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:best-practices",
    "default:automergeDigest"
  ],
  "automergeType": "branch",
  "ignoreTests": true
}

This instructs Renovate to auto-merge the Digest updates without creating a merge request prior. This reduces noise since there is no merge request notification. You can read more about automergeType and ignoreTests

Author

Robert Juzak , B.Sc.

Characteristics

released:

October 30, 2025

categories:

What moves us, DevOps

Tags:

DevOps, Open Source
previous article