Self-hosted service
k8up-io/k8up avatar
k8up-io/k8up

K8up wraps restic in a Kubernetes operator with two custom resources

Kubernetes and OpenShift Backup Operator

1,036 stars113 forksGoApache-2.0

At a glance

What is it?
K8up is a Kubernetes and OpenShift backup operator built on restic, where the whole user interface is a schedule object and a credentials object in a namespace. It has been used in production since 2019, ships the restic binary inside its own image, and exposes a Prometheus endpoint, and the price of that convenience is an operator to run and CRDs to install.
Who is it for?
K8up fits a cluster team that wants scheduled backups of persistent volumes and applications with metrics, without writing restic commands and Kubernetes jobs by hand. It is the wrong choice if you want no operator in the cluster, since a plain CronJob running restic is fewer moving parts for a single volume.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The user interface is two objects in a namespace

K8up is a Kubernetes backup operator based on restic that handles PVC and application backups on a Kubernetes or OpenShift cluster. The README's summary of the usage is two sentences long and worth quoting in spirit: you create a schedule and a credentials object in the namespace you would like to back up, and K8up takes care of the rest. It also provides a Prometheus endpoint for monitoring.

That is a small and legible interface, and it is where the design decision sits. A backup tool for Kubernetes has to answer three questions that are not all the same question: what to back up, where to put it, and when to do it. K8up splits them into the two custom resources, with the schedule naming what and when, and the credentials naming where.

The scale of the thing is also stated, and it is the kind of claim worth taking seriously because it is old. K8up is production ready and has been used in production deployments since 2019. The homepage is k8up.io, the documentation is written in AsciiDoc and published with Antora, and the source of the docs is in the docs/ directory of the repository.

The topic list of the repository mentions both Kubernetes and OpenShift operators, and the codebase is built with Kubebuilder, so this is a conventional operator that follows the Operator SDK structure rather than something that invented its own pattern.

A permanent operator and a job that ends

The code structure section is the part of the README that explains how the thing works, and it is a two module design.

The operator module is the part that runs constantly within Kubernetes and contains the various reconciliation loops. The restic module is the interface to the restic binary, and it is invoked whenever a Backup or Restore custom resource, or something similar, is instantiated. When its job is done, for example doing a backup or a restore, the process ends.

So the operator is a controller that watches for custom resources and turns them into short lived jobs that run restic. That split has a practical consequence for anyone running it: the restic invocation is not a long lived process you have to babysit, and the operator does not hold the backup state itself. The state lives in the cluster, in the custom resources, and the work happens in pods that come and go.

The directory listing in the README follows the same shape. There is api/ for the Go types for the custom resource definitions, cmd/ for the command line definition and entrypoints, common/ for code that is not specific to either module, config/ for the Operator SDK configuration files, and controllers/ for the reconciliation loops. The operator/ directory is described as code otherwise related to the operator module but not part of the recommended Operator SDK structure. The restic/ directory makes up the restic module. The e2e/ directory holds the Bats based end to end tests, and envtest/ holds the infrastructure code for the integration tests.

One thing that stands out is the cli/ directory at the repository root, alongside cmd/. The operator is not only a controller you install, it is also a command line tool, which is the shape most modern Kubernetes operators have grown into.

The restic binary is fetched and baked into the image

The Dockerfile shows how the two pieces meet, and it is worth reading because it explains the version question.

There are two stages, both based on alpine 3.24. The first is named restic, and it installs bash, ca-certificates and curl, copies go.mod and fetch_restic.sh, and then runs the script to place the binary at /usr/local/bin/restic, followed by a version check. The second stage is the k8up image itself, with the entrypoint set to k8up, and it copies the restic binary across with the environment variable RESTIC_BINARY pointing at /usr/local/bin/restic.

The important consequence is that the restic version is not chosen by you at runtime. It is pinned in the image, and the script that downloads it is part of the build. A repository listing at the top level includes fetch_restic.sh, and the module dependency is github.com/restic/restic at version 0.19.1, so the Go side and the binary side are versioned in step.

That matters more than it might appear. Restic is the component that talks to your storage backend, and the behaviour of a backup tool across storage backends is a function of the restic version. If your S3 compatible storage or your SFTP repository needs a restic behaviour that landed after the version baked into the operator image, you have an upgrade of the operator ahead of you rather than a configuration change.

The runtime image also carries kubectl, jq, fuse, openssh-client, tzdata, bash, curl and ca-certificates, and it runs as the non root user 65532 with a writable cache directory. Those extra tools are the operational footprint of a backup operator: it needs to reach the Kubernetes API to restore into workloads, and it needs a way to move files around.

The go.mod also shows a Prometheus client, so the monitoring endpoint mentioned in the README is backed by the usual client library, and there is a robfig/cron dependency, which is how the schedules are timed.

CRD installation has a documented caveat

There is one sharp edge in the README, and it is about installing the custom resource definitions.

CRDs can be either installed on the cluster by running make install or by using kubectl apply -f config/crd/apiextensions.k8s.io/v1. The README then says that currently there is an issue using make install, related to how the CRDs are specified, linking to an upstream Kubebuilder issue, and that therefore the second approach is the one to settle on for now.

The reason this is worth a paragraph of its own is that a broken CRD install produces a confusing failure. The operator can be running, the pods can be healthy, and the schedule and credentials objects you create will be rejected by the API server because their types do not exist. The kubectl apply route avoids that entirely, and it is one command.

The rest of the local development story follows the usual operator pattern. Code generation for the CRD structs is done with make generate. The operator can be run in four ways: as a container image, with make run-operator against your own kubeconfig, with make kind-run, which uses KIND to install a cluster in Docker and provides its own kubeconfig in testbin/, or from your favourite IDE.

There is one external dependency for the dev environment that catches people. The README says it is best to have minio installed somewhere so that the needed environment values can be set up, and that it needs to be reachable from within your dev cluster. A local object storage service stands in for S3 during development, and without it the backup path has nowhere to write.

The end to end suite is Bats with DETIK

The test story is unusually explicit for an operator, and the README lists the extra tools the end to end run needs on top of the development ones.

The end to end tests are Bats based, and they run with DETIK, which is the bats-core bats-detik project, and the README says you need node and npm to run the tests. To run them you execute make e2e-test, and to run a single file you pass the BATS_FILES variable.

bash
make e2e-test
bash
make e2e-test -e BATS_FILES=test-02-deployment.bats

Cleanup has its own targets, e2e-clean to remove the local KIND cluster and other end to end resources, and make clean for all created artifacts.

bash
make e2e-clean
bash
make clean

The Makefile shows the shape of the whole build. The default goal is help, which prints the available targets, and the most common ones are listed in the README as build, test, docker-build, run and kind-run, with make help giving an overview of the relevant targets and their intentions. The build target depends on generate, fmt, vet, the binary and a docs update step, so the CRD types and the documentation are regenerated as part of a normal build rather than as a separate chore.

The tests are split into two kinds. The unit and integration side is envtest, the standard Kubernetes test environment, and the end to end side is Bats driving a KIND cluster. The Makefile includes a separate Makefile.restic-integration.mk, which suggests the restic side is tested against a real binary rather than a mock, and there is a clean.sh, an exec.sh and a kill.sh at the top level for local workflows.

Two release flavours, and a project with a governance layer

The release history is worth reading carefully, because it contains two naming schemes. The listed releases are v2.16.0 on 2026-07-17, k8up-4.10.0 on 2026-07-17, and v2.15.0 on 2026-03-25. The v2 series is the operator module, and the module path in go.mod confirms it is github.com/k8up-io/k8up/v2. The k8up- prefix on a 4.x version is a different line, and the README does not explain which artefact it refers to, so that is a question for the release notes rather than something to guess at.

The last push was on 2026-10-19, so the work is current. The module requires Go 1.26.6 and builds against k8s.io/api at 0.35.2 with controller-runtime at 0.23.3, so the project tracks current Kubernetes libraries closely rather than lagging behind a supported range.

The governance signals are unusually visible in the README for a project of this size. There is a CII Best Practices badge and an OpenSSF Scorecard badge, the licence is Apache 2.0, and the repository carries GOVERNANCE.md, CODEOWNERS, OWNERS.md, CODE_OF_CONDUCT.md and SECURITY.md. The README says the project uses Snyk to test the code regularly for vulnerabilities and other security issues, and points at a vulnerability reporting process for anyone who finds a security issue, which is the behaviour you want from something that holds your backup credentials.

For the community side, the project is present in the CNCF Slack workspace in the #k8up channel, and it hosts a monthly community meeting.

Taken together, this is a project with the institutional trappings of a mature operator, which is a reasonable signal for something whose job is to hold the keys to your data.

Editorial conclusion

K8up fits a cluster team that wants scheduled backups of persistent volumes and applications with metrics, without writing restic commands and Kubernetes jobs by hand. It is the wrong choice if you want no operator in the cluster, since a plain CronJob running restic is fewer moving parts for a single volume. Verify first that the restic version inside the image matches what your storage backend expects, that the CRDs are installed with the kubectl apply route rather than the make install target, and that the credentials object points at a bucket you can actually restore from.

Frequently asked questions

What does K8up do?

K8up is a Kubernetes and OpenShift backup operator based on restic that handles PVC and application backups on a cluster. You create a schedule and a credentials object in the namespace you want to back up, and it also provides a Prometheus endpoint for monitoring.

How do I install the K8up custom resource definitions?

The README says to use kubectl apply -f config/crd/apiextensions.k8s.io/v1 rather than make install, because there is a current issue with make install related to how the CRDs are specified, linked to an upstream Kubebuilder issue.

Which restic version does K8up use?

The restic binary is fetched during the image build by fetch_restic.sh and placed at /usr/local/bin/restic, with the RESTIC_BINARY environment variable pointing at it. The Go module depends on github.com/restic/restic at version 0.19.1, so the version is pinned by the image rather than chosen at runtime.

How do I run the K8up end to end tests locally?

You need node and npm, because the suite runs with DETIK, and the extra tools listed are helm 3, jq, yq from mikefarah, bash, base64 and find. Run make e2e-test, or a single file with make e2e-test -e BATS_FILES=test-02-deployment.bats, and clean up with make e2e-clean.

Is K8up suitable for production use?

The README states that K8up is production ready and has been used in production deployments since 2019. The project is Apache 2.0 licensed, carries a CII Best Practices badge and an OpenSSF Scorecard badge, and documents a vulnerability reporting process.

Official sources

  1. k8up-io/k8up on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/k8up-io-k8up.svg)](https://hysenlabs.com/projects/k8up-io-k8up)

Was this useful?