Keel: Automated Kubernetes Deployments Without GitOps Overhead

Image-driven rollouts for the homelab cluster that doesn't need Argo

The orthodox answer to “how should my Kubernetes cluster deploy updates” is GitOps: Argo CD or Flux watching a Git repository, reconciling the cluster to match declared state, the whole pull-based pipeline. It is genuinely the right answer for a team shipping a product. For a homelab cluster running Sonarr, a couple of dashboards, and the odd hobby project, it is also a small mountain of YAML, a controller to babysit, and a Git workflow you have to actually follow. Sometimes you just want third-party images to update themselves when a new tag lands, without standing up a GitOps engine to do it.

That’s exactly the gap Keel fills. It’s a single lightweight controller that watches your workloads, notices when a newer image is available, and updates the deployment for you. No CRDs to learn for basic use, no Git repo, no reconciliation loop to reason about — you annotate a deployment and Keel handles the rest.

Advertisement

Keel works off the image references already in your manifests plus a handful of annotations that tell it which updates to accept. The key concept is a policy: do you want every new tag, only patch releases, only minor releases, or a specific tag whose underlying digest changes (the latest case)?

The policies map cleanly onto semantic versioning:

  • major — take any newer version, including breaking ones
  • minor1.4.01.5.0 but never 2.0.0
  • patch1.4.01.4.1 only, the safe default
  • force — re-pull when a fixed tag like latest or stable points at a new digest
  • all — accept anything, including pre-releases

You attach a policy with annotations on the workload:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sonarr
  annotations:
    keel.sh/policy: minor
    keel.sh/trigger: poll
    keel.sh/pollSchedule: "@every 1h"
spec:
  template:
    spec:
      containers:
        - name: sonarr
          image: lscr.io/linuxserver/sonarr:4.0.9

That’s the whole contract. keel.sh/policy: minor says “track minor and patch bumps.” keel.sh/trigger: poll with a schedule tells Keel to check the registry hourly, because not every registry can push notifications. When a new minor tag appears, Keel updates the deployment’s image and lets Kubernetes do its normal rolling update.

Keel supports two discovery mechanisms and you pick per workload. Polling is the universal fallback: Keel periodically queries the registry for tags matching the policy. It works against any registry, including Docker Hub and the linuxserver.io GHCR mirror, at the cost of a little latency and some registry traffic.

Webhooks are the push alternative. If your registry or CI can call Keel’s HTTP endpoint when an image is built, updates happen the instant the image lands rather than at the next poll. Keel exposes a native webhook plus first-class handlers for Docker Hub, Quay, and GCR. For a homelab that mostly consumes upstream images you didn’t build, polling is usually what you want.

Keel ships a Helm chart and the install is refreshingly small. The whole thing is one controller deployment with RBAC to watch and patch workloads:

$ helm repo add keel https://charts.keel.sh
$ helm repo update
$ helm upgrade --install keel keel/keel \
    --namespace keel --create-namespace \
    --set helmProvider.enabled=false \
    --set notificationLevel=info

By default Keel also exposes a small web UI and an admin endpoint, plus a Prometheus /metrics endpoint so you can graph how many updates it’s processed. Set Slack or generic webhook notifications and you get a message every time it rolls something forward, which turns “did anything change overnight” into a glance rather than an audit.

Letting a controller mutate production whenever upstream tags a release is, reasonably, terrifying for some workloads. Keel has a built-in approval workflow for exactly this. Annotate a deployment with a required vote count and Keel will prepare the update but hold it until you approve, via the UI or a Slack button:

metadata:
  annotations:
    keel.sh/policy: minor
    keel.sh/approvals: "1"

Now your media stack can self-update on patch with no ceremony, while your reverse proxy or database sits behind a one-click approval so nothing changes the front door without a human nodding first. You can check pending approvals from the CLI against Keel’s API, or just click the Slack message.

Be honest about the boundary. Keel mutates live cluster objects directly — it does not write changes back to Git, so your manifests and your running state can drift. There’s no audit trail beyond Keel’s logs and notifications, no pull-request review, no easy git revert of a bad rollout. If multiple people manage the cluster, or you need an authoritative record of what’s deployed and why, that drift is exactly the problem GitOps exists to solve, and you should reach for Flux or Argo instead.

Keel is the right tool when you have a small, mostly single-operator cluster full of third-party images and you want them to keep themselves current without you wiring up a GitOps pipeline. Set patch on the noisy stuff, gate the load-bearing services behind an approval, point a Slack webhook at it, and you’ve automated 90% of your update toil in an afternoon with one controller. The moment you need an audit trail, multi-person review, or Git as the source of truth, you’ve outgrown it — and that’s fine, because by then you’ll know you have.

Advertisement

Related Content

Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.