How To: Automate version updates for your self-hosted Docker containers with Gitea, Renovate, and Komodo

Written By Nick Cunningham
Article Thumbnail
Published On Apr 7, 2025

In the world of self-hosting, containerized applications reign supreme these days. Personally, all of my self-hosted applications are deployed via Docker and Docker Compose. One thing that has always bothered me about my setup is how inconvenient the update process can be. I have tried a variety of different platforms such as watchtower, wud, and diun - all of which aim to make the process of managing your docker container's updates a bit easier through automated updates, notifications, etc. While all of these programs are fantastic, I was still left wanting for a better pipeline for managing updates.

I realized that I had an "ideal" situation in mind for managing updates. For quite some time, I have stored all of my Docker Compose configuration files in a repository on my Gitea instance. What I really wanted was something like Dependabot to parse my repo for image versions, check if there are any updates for it, and then automatically open a pull request for the update (and notify me as well). Taking things a bit further, I wanted any version updates merged into the repo to automatically redeploy with the new version. The idea was to make version updates literally as simple as clicking a button.

For a while I assumed none of this was possible using existing FOSS projects, so I didn't really bother looking into it. Things changed when I first learned about Renovate - which is essentially a self-hostable version of Dependabot. Next, I learned about Komodo - which is capable of quite a bit, but most importantly it can manage and deploy docker containers from compose files in a Git repo. After a bit of research, trial, and error I determined that Renovate, Komodo, and software I was already using actually made managing updates in the way that I had imagine possible. In this guide, I will show you how.

Overview

To get all of this working, we need to configure a few different services (if you aren't running them already):

  • Some kind of Git Provider (Gitea, Forgejo, Github) - A repository will hold all of your your Docker Compose files and act as a single point of truth for the other programs we are using. In this article I will be using Gitea.
  • Some kind of CI/CD platform (Gitea/Forgejo/GitHub Actions, Drone) that is integrated with your Git provider - This will allow us to run the next service. In this article I will be using Gitea Actions.
  • Renovate - This will parse the compose files in your repository and search for any updates.
  • Komodo - This will handle the management & deployment of your Docker Compose stacks.

As you can see, there is quite a bit of work to be done here - but don't let that scare you! Think of this process as an investment - once things are set up here, managing your docker containers and their updates will be smooth sailing! That said, if you are just getting started on your self-hosting journey, this setup may not be for you. This guide will assume some level of knowledge and familiarity of Docker, Docker Compose, Git, and the operating system of your host machine.

I will go over setting these various services up in some detail, but if you find that your setup differs or you have different configuration requirements, you may need to refer to these programs' documentation.

Result

Here's a summary of the result we are trying to achieve with all of this:

  • Your Docker Compose files exist in a remote Git repository (Gitea in this case).

  • Komodo Clones this repo, and deploys your services using the Docker Compose files in that repo.

  • Renovate runs on a schedule (nightly in this case), scans your repo and checks if there are any updates for the images in your Docker Compose files.

    • If there is an update for an image, Renovate opens a Pull Request with the new version tag, and assigns the PR to your user.
    • You receive an email notification about the update's PR.
    • If you merge the PR, a webhook notifies Komodo about the merge
  • When Komodo's webhook is triggered:

    • It pulls the latest commit of your repo from your Git provider
    • Pulls updated docker images
    • Redeploys containers that have had their image version tag updated

Prerequisites

  • A host machine with Docker & Docker Compose installed and running. I will be using my custom built NAS running Unraid.
  • An email provider that supports SMTP (for sending notifications via Gitea)

Setting Up Gitea

Screenshot of my Docker Compose Repository

As mentioned, I will be using Gitea as my Git provider, though if I could do it all over again, I might choose Forgejo. Thankfully, at the time of writing, the setup and configuration of Gitea and Forgejo are essentially identical, and the process is easy and well documented here and here respectively.

Deploying Gitea

Here is my Docker Compose configuration:

# docker-compose.yaml

services:

  server:
    image: gitea/gitea:1.23.7
    container_name: gitea-server
    restart: always
    environment:
      - TZ=America/Los_Angeles
      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=db:5432
      - GITEA__database__NAME=gitea
      - GITEA__database__USER=gitea
      - GITEA__database__PASSWD=${GITEA_DB_PASSWORD}
    volumes:
      # replace the left-hand side from the ':' with your own path
      - /mnt/user/appdata/gitea/data:/data
    ports:
      - 3105:3000
      - 222:22
    depends_on:
      - db
      
  db:
    image: postgres:17.4
    container_name: gitea-db
    restart: always
    environment:
      - TZ=America/Los_Angeles
      - POSTGRES_DB=gitea
      - POSTGRES_USER=gitea
      - POSTGRES_PASSWORD=${GITEA_DB_PASSWORD}
    volumes:
      # replace the left-hand side from the ':' with your own path
      - /mnt/user/appdata/gitea/database:/var/lib/postgresql/data
# .env

GITEA_DB_PASSWORD="your own database password"

From here, run docker compose up from inside the folder your files are in, and you should be off to the races (once going through a short setup page). There's a lot of configuration you can do from here, but I will go over the necessary options we need for our setup.

Configuring Gitea

In app.ini

First we will be editing Gitea's configuration file. This will have been generated from the options you selected on the setup screen, and it will be located at /gitea/conf/app.ini relative to the path you mapped to the path /data on the server container in your compose file (mine is located at /mnt/user/appdata/gitea/data/gitea/conf/app.ini). Here are the options to set/verify:

  1. Set ENABLE_NOTIFY_MAIL = true under the [service] heading.
  2. Configure the mailer under the [mailer] heading - this is essential if you want to receive notifications about updates. You can reference the docs page here about setting up email, which covers setting up with Gmail, but any email provider with SMTP support will work.
  3. Set ENABLED = true under the [actions] heading - actions should be enabled by default, but set this anyways to be sure.
  4. Set ALLOWED_HOST_LIST = loopback,private,*.yourdomain.com under the [webhook] heading. This heading didn't exist for me, so I had to create it. Set *.yourdomain.com to your own domain if you are using a reverse proxy (which you should) for accessing your different services.

Assuming I didn't miss anything, that should be everything you need to do in app.ini for now, but be sure to configure Gitea to your liking, it's a fantastic tool. You can find Gitea's configuration reference here. Be sure to run docker compose up -d again to restart Gitea so it can pickup your configuration changes.

In Gitea's Interface

There's some things we need to do in Gitea's administration interface. Go to Site Administration from the profile button in the upper right of the screen.

  1. Test your mailer configuration under Configuration > Summary > Mailer Configuration. There will be a field to type in an email address and send a test email. If this spits an error, you may need to take another look at the configuration for it in app.ini (docs reference).
  2. Ensure your user has email notifications enabled via your user's settings under Account > Manage Email Addresses > Set Email Preference

Make A Repo

At this point, you should create a private repository for your Docker Compose files if you haven't already. There are a variety of ways you can organize compose files, but I prefer to use folders to group my compose files with their .env files, and any other files that are relevant to a particular stack:

/docker-compose
  /stack-one
    docker-compose.yaml
  /stack-two
    docker-compose.yaml
    .env

As long as your files end up in a repo, you should be good. Make sure you do not have any secrets in your compose files, and that you have set up a .gitignore file to ignore any .env files to avoid any confidential information getting committed to your repo.

# .gitignore

*.env

Setting up CI/CD

Gitea Actions Runners in the Gitea Dashboard

As mentioned in the overview, I will be using Gitea Actions for my CI/CD. Later on, when we are configuring actions/workflows, it is my understanding that the workflow syntax should be similar if not identical between Gitea/Forgejo/Github Actions.

Preparation

Gitea Actions uses 'runners' to run your CI/CD actions/workflows. Before we deploy the runner, we need to get a runner registration token from Gitea. This can be done in one of a few places, depending on how you want to scope the runner to your Gitea instance (reference). For this guide I will recommend registering your runner to the "Instance" level. To do this, go to Site Administration > Actions > Runners and click on Create new Runner and copy the token that displays in the popover. Save this for the next step.

Deploying the Runner

There are a few ways to deploy a runner for Gitea Actions, outlined here, but for this guide we will be deploying the runner via docker by adding it to our existing Gitea compose file - here's what it should look like:

services:

  server:
    # ...
    
  db:
    # ...
      
  runner:
    image: gitea/act_runner:0.2.11
    container_name: gitea_runner
    restart: always
    depends_on:
      - server
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      # replace the left-hand side from the ':' with your own path
      - /mnt/user/appdata/gitea/runner/config.yaml:/config.yaml
      # replace the left-hand side from the ':' with your own path
      - /mnt/user/appdata/gitea/runner/data:/data
    environment:
      - TZ=America/Los_Angeles
      - CONFIG_FILE=/config.yaml
      - GITEA_INSTANCE_URL=gitea-server:3000
      - GITEA_RUNNER_REGISTRATION_TOKEN=${GITEA_RUNNER_TOKEN}
      - GITEA_RUNNER_NAME=your-runner-name # change this

Make sure to add an environment variable GITEA_RUNNER_TOKEN to the .envfile we setup earlier, and paste the registration token we obtained in the previous step:

# .env

GITEA_DB_PASSWORD="your own database password"
GITEA_RUNNER_TOKEN="your token from the previous step"

Before you deploy the container, we need to generate a config file for the runner. To do this run the following command and move the output file to the location you specified for config.yaml in the compose file:

docker run --entrypoint="" --rm -it docker.io/gitea/act_runner:latest act_runner generate-config > config.yaml

Now you can run docker compose up runner -d to launch your runner. If you look in the Gitea admin interface, you should see your runner has been registered.

Setting Up Komodo

A List of Stacks Running in the Komodo Dashboard

Komodo is a super cool project. You can do a lot with it, and if I am being honest, the way we will be utilizing it in this guide is pretty simple compared to what it is capable of. The role Komodo will play in our setup is to:

  1. Manage and deploy our Docker Compose stacks/files
  2. Integrate with Gitea to update relevant containers when changes are made

Deploying Komodo

Komodo provides very detailed and commented example compose files in their documentation here. I recommend using the MondoDB setup, as the other database options use an adapter to work - I'd prefer to remove points of failure. If you have a specific place on your host machine that you would like your compose files to live (I do), I would recommend adding an additional volume mapping to the periphery container. Here is what my compose file for Komdo looks like (the .env file is far too long to post here, and full of secrets - reference the official example here):

services:

  mongo:
    image: mongo
    container_name: komodo-mongo
    command: --quiet --wiredTigerCacheSizeGB 0.25
    restart: unless-stopped
    logging:
      driver: ${COMPOSE_LOGGING_DRIVER:-local}
    volumes:
      - /mnt/user/appdata/komodo/mongo/db:/data/db
      - /mnt/user/appdata/komodo/mongo/config:/data/configdb
    environment:
      MONGO_INITDB_ROOT_USERNAME: ${KOMODO_DB_USERNAME}
      MONGO_INITDB_ROOT_PASSWORD: ${KOMODO_DB_PASSWORD}
    labels:
      komodo.skip: # Prevent Komodo from stopping with StopAllContainers

  core:
    image: ghcr.io/moghtech/komodo-core:${COMPOSE_KOMODO_IMAGE_TAG:-latest}
    container_name: komodo-core
    restart: unless-stopped
    depends_on:
      - mongo
    logging:
      driver: ${COMPOSE_LOGGING_DRIVER:-local}
    ports:
      - 9120:9120
    env_file: ./.env
    environment:
      KOMODO_DATABASE_ADDRESS: mongo:27017
      KOMODO_DATABASE_USERNAME: ${KOMODO_DB_USERNAME}
      KOMODO_DATABASE_PASSWORD: ${KOMODO_DB_PASSWORD}
    volumes:
      ## Core cache for repos for latest commit hash / contents
      - /mnt/user/appdata/komodo/core/repos:/repo-cache
    labels:
      komodo.skip: # Prevent Komodo from stopping with StopAllContainers

  periphery:
    image: ghcr.io/moghtech/komodo-periphery:${COMPOSE_KOMODO_IMAGE_TAG:-latest}
    container_name: komodo-periphery
    restart: unless-stopped
    logging:
      driver: ${COMPOSE_LOGGING_DRIVER:-local}
    env_file: ./.env
    environment:
      PERIPHERY_REPO_DIR: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/repos
      PERIPHERY_STACK_DIR: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/stacks
      PERIPHERY_SSL_KEY_FILE: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/ssl/key.pem
      PERIPHERY_SSL_CERT_FILE: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/ssl/cert.pem
    volumes:
      ## Mount external docker socket
      - /var/run/docker.sock:/var/run/docker.sock
      ## Allow Periphery to see processes outside of container
      - /proc:/proc
      ## Specify the Periphery agent root directory - must be the same inside and outside the container.
      - ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}:${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}
      ## Custom location for docker compose repo
      - /mnt/user/compose:/mnt/user/compose
    labels:
      komodo.skip: # Prevent Komodo from stopping with StopAllContainers

Once you run docker compose up -d, you should be able to access the admin interface. Enter a username and a password, then click Sign Up to register the admin account.

Using Komodo

As mentioned, Komodo is capable of quite a bit and the interface can be a bit overwhelming. Worry not, we will be using it in a pretty straightforward way!

Configuring Git Credentials

Adding a Git Provider in Komodo

Since our compose repository is private, we need to tell Komodo how to authenticate with our Gitea instance. here's how to do that:

  1. Log into your Gitea instance, go to your user's settings by clicking the profile icon in the upper right corner of the screen and clicking Settings.
  2. Click on the Applications tab in the sidebar, and generate a new token with a name like komodo-token and read:repository permissions. Copy/save the token (it will only show this once).
  3. Log into your Komodo dashboard, and click on the Settings tab in the lower left corner of the screen.
  4. Click on the Providers tab near the top of the screen, and click New Account under the Git Providers heading. Fill in the URL of your Gitea instance (omit http/https), your username, and the token obtained in step 2.

Configure Your Repo

Docker Compose Repo in Komodo

Now that we have configured access to our Gitea instance, we can clone our docker compose repo in Komodo:

  1. Click on the Repos tab in the sidebar.

  2. Click New Repo, giving it a name, then clicking Create.

  3. On the following screen set:

    • Server - There should only be one option
    • Builder - 'local'
    • Source - Select the provider you configured in the previous section
    • Account - Select the account you configured in the previous section
    • Repo - Enter the scope/reponame of your compose repo - mine is ncunningham/docker-compose
    • Clone Path (Optional) - If you configured an additional volume mapping for the periphery container in your compose file, use that path here.
  4. Click Save and then click Clone under the Execute heading near the top of the page.

Assuming you configured everything correctly, Komodo should be able to successfully clone your repo!

Configure Your Stacks

Immich Stack in Komodo

Now we will configure Komodo to manage and deploy your compose files:

  1. Click on Stacks in the sidebar.

  2. Click on New Stack, giving it a name matching it's folder in your repo, then click Create.

  3. Scroll down a bit and click on the drop-down Choose Mode, selecting the option Files On Server.

  4. Set the following values:

    • Run Directory - the full path to the desired stack. Mine would be /mnt/appdata/compose/stackname because I manually specified the path that I cloned my repo to. If you did not do this, your repo would be located at whatever path you configured PERIPHERY_ROOT_DIR to be in your Komodo's .env file.
    • File Paths - if your compose files are not named compose.yaml exactly, specify the name of the file here (I use docker-compose.yaml for mine).
    • Environment - if your compose file uses any environment variables, place them here. It will automatically generate a .env file next to your compose file with the values entered.

Repeat this process for each of the Docker Compose files you have defined in your repo. Do not add the Docker Compose file for Komodo if you configured Komodo in the same repo as the rest of your services - I don't even know what would happen if you did this, but it's not a good idea on principle - see ouroboros - Komodo can't manage itself. Now Komodo is managing your compose stacks, and provides a pretty nice interface for stopping, starting, etc. your containers! We will return to Komodo a bit later towards the end of this guide.

Setting Up Renovate

Renovate Running in Gitea Actions

As mentioned in the overview, Renovate is the tool we are using to check for updates to our docker images. If you are familiar with GitHub's Dependabot or similar tools, Renovate works similarly and will check your Docker Compose files for version updates on whatever interval you would like.

While you can deploy Renovate as it's own container, I found that running it on demand via Gitea Actions works best, which is why we set that up earlier.

Preparation

Before Renovate can do it's job, it's important that the images in your compose files are tagged properly. Often, installation guides and example compose files will either use no image tag, or the tag 'latest' or something similar. Outside of the context of this guide, this is often convenient, but its not great if you want to ensure that there aren't breaking changes, regressions, or other issues with whatever the 'latest' image is. Within the context of this guide, in order for Renovate to know what version your images are, your images need to be tagged with greater specificity.

The best case scenario is that you tag an image in your compose files with the full semantic version, but not all projects use image tagging effectively or follow best practices. Thankfully there are a variety of ways to tag your images which I will outline below in order of worst to best:

# bad - don't do this
image: gitea/gitea
image: gitea/gitea:latest

# ok - better than nothing, but not specific enough for renovate to do it's job
image: gitea/gitea:1
image: gitea/gitea:1.23

# good - the @sha256 pins latest to a specific build digest, but obfuscates the real version of the image
image: gitea/gitea:latest@sha256:01bb6f98fb9e256554d59c85b9f1cb39f3da68202910ea0909d61c6b449c207d

# better - pins the image to a clear and specific image version
image: gitea/gitea:1.23.6

# best - pins the image to a specific version AND digest, makes the specific version immutable
image: gitea/gitea:1.23.6@sha256:01bb6f98fb9e256554d59c85b9f1cb39f3da68202910ea0909d61c6b449c207d

For my purposes, I currently use the full semver tag, without the build digest. Before proceeding any further, I would go through your compose files and make sure they are appropriately tagged!

Create A Renovate Bot Account

A Bot Account for Renovate to use in Gitea

Technically this is optional and you could use your own user to run renovate, but I wouldn't. Unfortunately Gitea doesn't support something akin to a "Service Account", so it is best to simply create a new user in Gitea to act on behalf of renovate. Depending on your Gitea configuration, you may have to create this new user from your admin panel, or you may simply be able to log out of your primary account and register for a new account on your Gitea instance. I called my new user "renovate-bot". I whipped up this profile image using Renovate's logo to make things look nicer, you are welcome to use it.

Create A Renovate Configuration Repository

Still logged in as your Renovate Bot account, create a new repository to configure and run Renovate from.

Configure Renovate

First, create a file in the root of the repo called config.js and paste the following contents:

module.exports = {
	platform: 'gitea',
	endpoint: 'https://example.com/api/v1/', // set this to the url of your gitea instance
	gitAuthor: 'Renovate Bot <renovate-bot@example.com>', // set the email address to whatever email your gave this user in your gitea
	username: 'renovate-bot',
	autodiscover: true,
	onboardingConfig: {
		$schema: 'https://docs.renovatebot.com/renovate-schema.json',
		extends: ['config:recommended'],
	},
	optimizeForDisabled: true,
	persistRepoData: true,
};

This file is the base configuration that Renovate will use.

Configure Renovate's Workflow

Next you will want to create a file located in .gitea/workflows/renovate.yaml and paste the following contents:

name: renovate

on:
  workflow_dispatch: # allows the workflow to be run manually when desired
    branches:
      - main
  schedule: # runs this workflow at the scheduled time (uses UTC, adjust for your timezone)
    - cron: "0 12 * * *"
  push: # runs this workflow when pushes to the main branch are made
    branches:
      - main

jobs:
  renovate:
    runs-on: ubuntu-latest
    container: ghcr.io/renovatebot/renovate:latest
    steps:
      - uses: actions/checkout@v4
      - run: renovate
        env:
          RENOVATE_CONFIG_FILE: ${{ gitea.workspace }}/config.js
          LOG_LEVEL: "debug"
          RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
          GITHUB_COM_TOKEN: ${{ secrets.RENOVATE_GITHUB_TOKEN }} # optional

This file defines a workflow for your Gitea Actions Runner to run.

Provide Renovate Secrets

In order for renovate to run, it needs to authenticate as your Renovate Bot user. To do this we need to provide the runner with an access token via your repository's Actions Secrets:

  1. Go to the account settings of your Renovate Bot user, click on Applications and then Generate Token with a name like renovate-token and the following permissions:

    • read:misc
    • read:notification
    • read:organization
    • read:package
    • write:issue
    • write:repository
    • read:user
  2. Go to the repository settings of your Renovate Config repo, clicking on Actions > Secrets in the sidebar and create a new secret called RENOVATE_TOKEN with the contents of your access token from the previous step.

  3. Optionally, you can provide a token from GitHub as well, which Renovate uses to fetch change logs for updates. You can see more about that here.

Renovate should now be able to successfully run via Gitea Actions, though it will have nothing to do yet.

Configure Renovate In Your Docker Compose Repo

Now that we have configured Renovate to run, we must configure Renovate to:

  1. Know about your Docker Compose repo
  2. Know what to do with your Docker Compose repo

If you haven't already, sign back into your primary Gitea user.

Add Renovate Bot as a Collaborator

Renovate Bot is Added to the Repo as a Collaborator

In order for Renovate to discover your Docker Compose repo, you must add the Renovate Bot account we created as a collaborator:

  1. Go to the repository settings of your Docker Compose repo
  2. Click on the Collaborators tab of the sidebar
  3. Search for and add your Renovate Bot user as a collaborator

Now when Renovate runs, it will be able to access your Docker Compose repo.

Configure Renovate in the Repo

If you have used something like GitHub's Dependabot before, you know it is configured via a file called dependabot.yaml that is located locally in each repo that it runs in. Renovate works similarly, via a file renovate.json that we must create at the root of our Docker Compose repository. Here is what the basic configuration file should look like:

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:recommended"],
  "dependencyDashboard": true,
  "dependencyDashboardTitle": "Renovate Dashboard",
  "assignees": ["your-gitea-user"],
  "labels": ["renovate"],
  "configMigration": true,
  "prHourlyLimit": 0,
  "docker-compose": {
    "hostRules": [
      {
        "matchHost": "docker.io",
        "concurrentRequestLimit": 2
      }
    ],
  }
}

This file defines how renovate should behave inside of this repo. Notably it tells renovate to:

  • Create an issue in your repo that acts as a Renovate "Dashboard" which will provide a summary of Renovate's status in the repo.
  • Assign pull requests that Renovate opens to your Gitea user. This will send you an email if you have Gitea's mailer configured and notifications turned on.
  • Apply a renovate label to pull requests that Renovate opens.
  • Remove rate limits on PR creation (there are no limits on a self hosted Gitea instance)
  • Rate limit queries to DockerHub, which does impose some sort of limits on frequency of pulls.

Unfortunately, you will likely need to add additional configuration depending on the images you are deploying, and the way in which those images are tagged. You can do this via entries under "packageRules" in your renovate.json file (configuration reference). Here's some examples of the issues I encountered that required additional configuration:

  • The redis and database containers used in immich are pinned at a specific version recommended by the developers. I don't want Renovate to try to update these.
  • The linuxserver.io Jellyfin image I use has non semver tags that are numerically greater than the current semver of Jellyfin (v2021 > 10.10.6). I want Renovate to ignore those versions.
  • The Duplicacy image that I use has tags that are prefixed with 'release-' (release-1.8.0) and renovate was having trouble with this. I want Renovate to identify the semver within the tag, omitting the prefix.

So I have ended up with a config file that looks more like this:

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:recommended"],
  "dependencyDashboard": true,
  "dependencyDashboardTitle": "Renovate Dashboard",
  "assignees": ["your-gitea-user"],
  "labels": ["renovate"],
  "configMigration": true,
  "prHourlyLimit": 0,
  "docker-compose": {
    "hostRules": [
      {
        "matchHost": "docker.io",
        "concurrentRequestLimit": 2
      }
    ],
    "packageRules": [
      {
        "matchPackageNames": "redis",
        "matchCurrentVersion": "6.2-alpine",
        "enabled": false
      },
      {
        "matchPackageNames": "tensorchord/pgvecto-rs",
        "enabled": false
      },
      {
        "matchPackageNames": "ghcr.io/hotio/duplicacy",
        "versionCompatibility": "^(?<compatibility>release-)(?<version>.+)$"
      },
      {
        "matchPackageNames": ["ghcr.io/linuxserver/jellyfin"],
        "allowedVersions": "<2021"
      },
    ]
  }
}

Going into the details of these packageRules is beyond the scope of this article, but all of this is to say that you will likely need to do some additional Renovate Configuration to get things working as desired.

Bringing Everything Together

At this point we have:

  • Setup a Gitea instance
  • Setup a Gitea Actions Runner
  • Created a repostory in Gitea where our Docker Compose files will live
  • Setup Komodo
  • Cloned our Docker Compose repo from Gitea into Komodo
  • Configured Komodo to deploy our Docker Compose stacks from the repo
  • Created a user to act as a bot for Renovate to run as
  • Created a repo under the Renovate Bot user to configure and run Renovate
  • Configured Renovate to run via Gitea Actions on a daily schedule
  • Configured Renovate to open Pull Requests in our Docker Compose repo for any updates that it finds, which will alert us via email.

Now that I've written all of this out, Kudos if you are still with me! The good news is that we are almost at the end, and we are about to bring all these different components together.

Create A Procedure In Komodo

Configured Procedure in Komodo's Dashboard

Back in the Komodo dashboard:

  1. Click on the Procedures tab in the sidebar, select New Procedure, giving it a name like "update-stacks" and clicking Create.

  2. Under the Config heading, click Add Stage with the following properties:

    • Execution - Pull Repo
    • Target - Select your Docker Compose repo
  3. Add a second stage with the following properties:

    • Execution - Batch Deploy Stack If Changed
    • Target - *
  4. Under the Webhook URL - Run heading, copy the webhook URL for this Procedure. Either set a secret unique to this specific webhook, or set nothing and it will use the default secret configured in Komodo's .env file as KOMODO_WEBHOOK_SECRET.

  5. Click Save

Use the Procedure's Webhook in Gitea

Configured Webhook in Gitea

Back in the Gitea:

  1. Go to the settings of your Docker Compose repo, selecting the Webhooks tab in the sidebar, and click Add Webhook choosing the type "Gitea"

  2. On the following screen, set these properties:

    • Target URL - paste the webhook URL for your procedure from the previous section here.
    • Secret - paste the webhook secret configured in the previous step here.
    • Trigger On - Push Events
    • Branch filter - set to "main"
  3. Click Add Webhook

  4. Once the webhook is created, you can test if it works via the Test Delivery button under the Recent Deliveries section of the webhook's config page. A green checkmark is success! If its not working, make sure you are using the right secret.

If you go back to the page for your procedure in Komodo, you will see that triggering the webhook test in Gitea will have triggered the procedure to run in Komodo. Now when you merge a Pull Request from renovate into your main branch, the procedure in Komodo will run, which will pull the latest change from your repo, and then redeploy any containers that have updated image tags.

Wrapping Things Up

A Merged Pull Request from Renovate in Gitea

Well that was a lot of work, but if you made it this far it will hopefully pay off - now updating your self-hosted services is literally as easy as merging a pull request. As mentioned in the Renovate configuration section, it is very likely you may need to add some additional configuration for Renovate in your Docker Compose repo. You really won't know what oddities you will face until Renovate either opens an issue complaining about something, or opens a Pull Request for a weird tag that it thinks is an update, but isn't.

Having gotten through the process of fine tuning Renovate and Komodo, I am very pleased with how this has all turned out. Prior to this, I was using Watchtower (which seems to not be maintained anymore) to blindly update all of my containers using latest tagged images. Being in control of my update process is a solid improvement, and that control does not come at the expense of the convenience of automation. A few weeks ago, I wouldn't have thought this kind of setup for managing updates was possible, but thanks to the magic off FOSS, it is!