Introduction
If you’re self-hosting applications with Docker Compose, you’ve probably faced this question: how do I keep my container images up to date?
It’s a common challenge. You set up your stack, everything works great, and then… you forget about it. Months later, you realize you’re running outdated (and potentially vulnerable) images. You might not be able to just pull the latest version and restart your containers because of breaking changes.
And it gets more complicated with multi-container stacks. Images can have dependencies on each other, and updating one might break compatibility with another. For example, the application you’re running might not be ready to jump to the latest and greatest PostgreSQL release. You still need to be careful about these interdependencies.
Let’s explore the options available today, from the simplest to the most GitOps-oriented approach.
The Options
Option 1: The “Latest” Tag Approach
The simplest (and riskiest) approach: use the latest tag and pull new versions when restarting containers.
services:
myapp:
image: someimage:latestPros: Dead simple, no configuration needed.
Cons: You have no idea what version you’re running. An update might break everything. No audit trail. This is essentially hoping for the best.
Option 2: Watchtower
Watchtower has been the go-to solution for a long time. It runs as a container itself and automatically updates your other containers.
services:
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sockWatchtower uses Docker labels to control which containers get updated. You can include or exclude specific containers using the com.centurylinklabs.watchtower.enable label:
services:
myapp:
image: someimage:1.0
labels:
- "com.centurylinklabs.watchtower.enable=false" # Exclude from updatesPros: Set and forget, works well for simple setups, granular control via labels.
Cons: Still automatic updates without approval, limited rollback capabilities, requires modifying your compose files with labels.
Option 3: UI-Based Management Tools
There are several UI-based alternatives that give you more visibility and control:
- What’s Up Docker (WUD) - Monitors and notifies about available updates
- Portainer - Full container management with update capabilities
- Dockge - Lightweight docker-compose management
- Arcane - Modern container management
- Dockhand - Advanced features including GitOps workflows
These tools range from basic monitoring to full-featured management. Dockhand in particular stands out for its GitOps capabilities, but I won’t go into too much detail here.
Option 4: Renovate
This brings us to Renovate. It’s not the most straightforward option, but it’s the tool that taught me the most and helped me acquire genuinely useful GitOps skills.
Why do I prefer it?
- Pull request workflow: Updates come as PRs you can review before merging
- Full visibility: You see exactly what’s changing
- Rollback is trivial: Just revert the commit
- CI/CD integration: Trigger pipelines on merge
- Highly configurable: Fine-grained control over what gets updated and when
What is Renovate?
Renovate is a dependency update tool that automatically creates pull requests to keep your dependencies up to date. While it’s commonly used for npm, pip, or other package managers, it also works wonderfully with Docker Compose files.
But Docker images are just one use case. Renovate can also manage updates for:
- Terraform modules and providers
- Helm charts
- Kubernetes manifests
- Ansible roles and collections
- GitHub Actions
- And many more…
In this post, I’m focusing on the Docker Compose use case, but the same principles apply to all these other scenarios.
How Does It Work?
In a nutshell:
- You grant the Renovate app access to your Git repository (GitHub, GitLab, Bitbucket, etc.)
- You add a
renovate.json5configuration file to your repo - Renovate scans your files, detects Docker images, and creates PRs when updates are available
It’s that simple to get started.
Here’s the complete workflow visualized:
graph LR
A[New Image Available] --> B[Renovate Scans Repo]
B --> C[PR Created]
C --> D[Review & Merge]
D --> E[CI/CD Deploys]
E --> F[Container Updated]
Configuration
Here’s what my renovate.json5 configuration looks like:
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"dependencyDashboard": false,
"schedule": ["* 0-5 * * *"],
"timezone": "Europe/Brussels",
"packageRules": [
{
"matchManagers": ["docker-compose"],
"matchUpdateTypes": ["digest"],
"automerge": true,
"automergeType": "pr",
"automergeStrategy": "merge-commit",
"platformAutomerge": false,
"requiredStatusChecks": null
}
]
}Let me break down the key parts:
extends: ["config:recommended"]: Start with sensible defaultsschedule: Only check for updates between midnight and 5 AM (my timezone)matchManagers: ["docker-compose"]: Target Docker Compose files specificallymatchUpdateTypes: ["digest"]: Only match digest updates (you can also useminor,major,patch)automerge: Automatically merge certain low-risk updates
Fine-Tuning Your Configuration
You can be very specific about what gets updated:
- Only allow minor version updates
- Group related updates together
- Set different schedules for different services
- Require manual approval for major versions
The Renovate documentation has a comprehensive list of all available options.
How It Works in Practice
Once configured, Renovate will scan your repository and create pull requests when updates are available.
A typical PR will show you:
- The current version and the new version
- A link to the changelog (when available)
- Any breaking changes or release notes
You then have several options depending on your update strategy:
- Manual merge: Review each PR and merge when ready
- Automerge: Let Renovate merge low-risk updates automatically
- Scheduled merge: Queue updates to be merged during maintenance windows
Tips and Best Practices
Trigger CI/CD Pipelines
The real fun begins when you start triggering pipelines based on those commits or merges.
In my case, I use a GitHub Action that:
- Connects to the server where the app is deployed
- Pulls the latest changes from the repository
- Restarts the containers with
docker compose up -d - Verifies everything is up and running
- Rolls back automatically if something goes wrong and sends me an alert
This gives me fully automated updates with a safety net.
Grouping Updates
If you have many containers, you might want to group related updates to reduce PR noise. Renovate supports grouping by package name patterns, update type, or custom rules.
Scheduling
Don’t let Renovate create PRs during work hours. Schedule it for off-peak times so you can review updates when you’re ready.
Start with Manual Merges
I’d recommend starting with manual merges and gradually enabling automerge for services you trust (official images, well-maintained projects).
Conclusion
Is all of this completely overkill for a homelab? Absolutely.
But that’s kind of the point. By setting up this workflow, you’ll get familiar with important concepts:
- GitOps principles: Your infrastructure state lives in Git
- Pull request workflows: Changes are reviewed before being applied
- CI/CD pipelines: Automation triggered by code changes
- Rollback strategies: When things go wrong (and they will), recovery is straightforward
I’m certainly not a Renovate expert by any means, but I’ve learned a lot through this journey. The documentation is excellent, and the community is helpful.
If you’re tired of manually checking for updates or worried about Watchtower silently breaking things, give Renovate a try. Your future self will thank you.
Resources:
