Tools Used in Kubernetes · Velero

Velero

Every cluster eventually needs an answer to three separate emergencies: someone ran kubectl delete namespace against the wrong context, a control-plane host disappeared along with the region it lived in, and a workload needs to move to a brand-new cluster without weeks of manual re-provisioning. GitOps can rebuild the Deployment and ConfigMap objects a workload is made of, but it has nothing to say about the twelve gigabytes inside a PersistentVolumeClaim or the Secret a controller minted at runtime and never committed to Git. Velero is the tool that covers that gap: it walks the Kubernetes API, writes what it finds into an object storage bucket as a portable archive, and — separately — captures the bytes inside every attached volume, either through a CSI snapshot or by reading the filesystem directly. Run in reverse, that same archive rebuilds a namespace, a dead cluster, or an entirely different one.

☺ Explain it like I'm 10

Picture a family moving to a new apartment, except they hire movers who do something clever. Before touching a single box, the movers walk through every room with a camera, noting exactly which chair sits where and which shelf holds which book — that's Velero photographing the Kubernetes API. Then they pack the actual belongings — the mattress, the filing cabinet, the fish tank — into labeled crates and drive them to a storage unit across town, which is Velero copying the data inside your volumes. If the old apartment burns down, the movers rebuild the exact same layout in a brand-new apartment anywhere in the city, using nothing but the photos and the crates. Nobody has to remember where the bookshelf went — the movers already wrote it down, and they can unpack it wherever you point them.

🐘Your host for this topic: Ellie the Elephant — the platform's memory. Ellie already tracks what the cluster looked like last Tuesday at 2 a.m.; handing her the tool that writes that memory to a warehouse and reads it back is exactly her kind of job.

What a Velero backup actually contains

☺ Like you're 10: Two separate jobs happen every time — one camera-click for the API objects, one truck for what's inside the volumes — and either one can succeed while the other quietly doesn't.

A Backup is two things, captured by two different mechanisms, stored side by side in one object storage bucket. First, a tarball of API objects — the JSON of every resource that matched your namespace, label, and resource filters, plus the cluster-scoped resources those namespaced objects depend on. Second, volume data, captured either by asking the storage driver for a CSI snapshot or by having Velero's own agent read the volume's filesystem directly with Kopia (its default uploader; the older restic uploader still works but is the legacy path). Those two halves fail independently, which is the single most important thing to internalize before you trust a green checkmark.

◆ Key idea

Velero backs up API objects and, separately, the bytes inside PersistentVolumes. Almost every confusing Velero outcome — "the restore ran but the Pods came back empty," "the data's there but nothing's running" — is one half succeeding while the other quietly didn't.

Velero is deliberately not two other things it gets confused with. It is not an etcd snapshot — it talks to kube-apiserver as an ordinary client, so it captures what the API exposes, never the raw control-plane database, the static Pod manifests, or the certificates a self-managed control plane depends on. Cluster Architecture, Installation & Configuration covers etcdctl snapshot save as its own, separate discipline, and Best Practices & Operating Model already lays out exactly where etcd snapshots and Velero backups each end and why a healthy operating model runs both. And it is not GitOpsGitOps on Kubernetes can rebuild your declared configuration from a Git commit in minutes, but it was never told about the runtime Secret a controller generated last night or the rows inside a database's volume. Velero exists for exactly the state Git never held.

Architecture: the server, the node-agent, and where the bucket fits

☺ Like you're 10: One deployment does the talking to Kubernetes and the cloud; one helper rides along on every node for volumes that need reading byte by byte; and everything either of them produces lands in one shared warehouse.

The velero CLI is a thin client — nearly everything it does is create a Backup, Schedule, or Restore object and then watch it. All the real work happens server-side in the Velero server, a Deployment in the velero namespace that bundles the backup, restore, schedule, backup-sync, and garbage-collection controllers into one binary. That matters in practice: because a Schedule just stamps out ordinary Backup objects on a cron, a backup triggered at 1 a.m. runs through the exact same code path as one you type by hand at your desk, and the whole tool is drivable through plain kubectl apply if that's how your platform prefers to manage it.

Two optional pieces round it out. The node-agent is a DaemonSet, one Pod per node, that mounts each node's Pod-volume directory and runs Kopia against it — it's only needed when you want filesystem backup instead of, or as well as, CSI snapshots, and it's also what moves a CSI snapshot's contents into the bucket when you ask for that. Everything provider-specific — talking to a particular cloud's object store, taking a particular cloud's disk snapshot — arrives as a plugin container selected at install time, which is why the same Backup spec works unmodified against AWS, Azure, GCP, or an on-prem S3-compatible store like MinIO.

velero CLI Schedule (cron) 🐘 Velero server discover via kube-apiserver trigger CSI VolumeSnapshot hand volumes to node-agent node-agent DaemonSet Kopia filesystem backup, one per node Object storage bucket API object tarball Kopia repo data (if fs backup) logs · resource list Cloud volume snapshot stays in this region only Cluster B same BackupStorageLocation upload snapshotMoveData velero restore the bucket is the durable artifact — any cluster pointed at it can read the same history

The resources you actually write

☺ Like you're 10: Three little files: where the warehouse is, what to pack and how often, and how to unpack it — possibly somewhere new.

A BackupStorageLocation is the foundation everything else sits on; velero install usually creates one for you, but writing it by hand is how you add a read-only location for restores, or point a second cluster at the same bucket for migration.

apiVersion: velero.io/v1
kind: BackupStorageLocation
metadata:
  name: primary
  namespace: velero
spec:
  provider: aws                    # matches the installed object-store plugin
  default: true                    # used whenever a Backup omits storageLocation
  objectStorage:
    bucket: acme-k8s-backups
    prefix: prod-eu                # lets several clusters share one bucket safely
  credential:
    name: velero-cloud-credentials
    key: cloud
  accessMode: ReadWrite             # ReadOnly on a DR-standby cluster: restore, never write
  backupSyncPeriod: 1m              # how often it re-reads the bucket's own backup list
---
apiVersion: velero.io/v1
kind: VolumeSnapshotLocation         # only needed for NATIVE cloud disk snapshots
metadata:
  name: eu-west-1
  namespace: velero
spec:
  provider: aws
  config:
    region: eu-west-1                # the snapshot is created HERE and stays HERE

A Schedule wraps a Backup template around a cron expression — the controller stamps out ordinary backups from it. The ttl field is not a hint; it's a hard expiry that deletes the backup and its snapshots the moment it elapses.

apiVersion: velero.io/v1
kind: Schedule
metadata:
  name: checkout-nightly
  namespace: velero
spec:
  schedule: "0 1 * * *"              # 01:00 — the server's clock, not yours
  template:
    includedNamespaces: [checkout]
    snapshotVolumes: true            # take CSI / cloud snapshots of the PVs
    defaultVolumesToFsBackup: false  # true = use Kopia for every volume instead
    ttl: 720h0m0s                    # 30 days, then garbage-collected — snapshots too
    hooks:
      resources:
        - name: quiesce-postgres
          labelSelector:
            matchLabels: { app: postgres }
          pre:                        # runs BEFORE the volume is captured
            - exec:
                container: postgres
                command: ["/bin/sh","-c","pg_dump -U postgres -Fc -f /var/lib/postgresql/data/velero/dump.sql app"]
                onError: Fail          # Fail = abort the backup; Continue = carry on anyway

Hooks are what turns a snapshot into a consistent one — a raw volume snapshot of a running database is crash-consistent, which most engines survive but which is a bad bet on your only copy of production data. A Restore is where the interesting decisions live: by default Velero skips anything that already exists rather than overwriting it, which surprises people expecting a plain undo.

apiVersion: velero.io/v1
kind: Restore
metadata:
  name: checkout-restore-20260827
  namespace: velero
spec:
  backupName: checkout-nightly-20260827010000
  namespaceMapping:
    checkout: checkout-recovery       # restore SIDE BY SIDE, verify, then cut over
  restorePVs: true
  existingResourcePolicy: none        # "none" (default) = skip anything that exists
                                       # "update" = patch it to match the backup instead
ResourceWhat it declares
BackupOne backup: filters, whether to snapshot volumes, storage location, TTL, hooks
ScheduleA cron expression plus an embedded Backup template
RestoreA restore from a named backup: filters, namespace mapping, existing-resource policy
BackupStorageLocationWhere object data lives: provider, bucket, prefix, credentials, access mode
VolumeSnapshotLocationWhere native cloud volume snapshots are taken — provider and region
PodVolumeBackup / PodVolumeRestorePer-volume work items the node-agent picks up during filesystem backup

Day-to-day commands

☺ Like you're 10: Ask for a backup, watch it, read what it actually did, then — the important one — practice unpacking it somewhere safe.

Velero ships as a Helm chart or via its own CLI installer; either path lands the same server Deployment.

# Install — pin a plugin version and point it at your bucket
$ velero install --provider aws \
    --bucket acme-k8s-backups --prefix prod-eu \
    --backup-location-config region=eu-west-1 \
    --plugins velero/velero-plugin-for-aws:v1.10.0 \
    --secret-file ./credentials-velero \
    --use-node-agent

# Create backups
$ velero backup create checkout-adhoc --include-namespaces checkout --wait
$ velero backup create checkout-fs --include-namespaces checkout --default-volumes-to-fs-backup
$ velero schedule create checkout-nightly --schedule="0 1 * * *" --include-namespaces checkout --ttl 720h0m0s

# Read, debug, prove what happened — the second line is the one nobody runs enough
$ velero get backups                                          # phase, errors, expiry
$ velero backup describe checkout-nightly-20260827010000 --details
$ velero backup logs checkout-nightly-20260827010000 | grep -iE 'error|warn'
$ velero backup-location get                                  # Available vs Unavailable — check first

# Restore, into a mapped namespace so a drill is safe to run in production
$ velero restore create checkout-drill \
    --from-backup checkout-nightly-20260827010000 \
    --namespace-mappings checkout:checkout-drill \
    --wait
$ velero restore describe checkout-drill --details
🐘 Ellie's-eye view

"I don't trust a backup because the CLI exited zero — I trust it because velero backup describe --details shows the resource count and the volume count I expected, not just the word Completed. A backup whose selector matched nothing finishes cleanly, with zero errors, having protected exactly nothing. That's the one that gets teams in front of an incident channel explaining, in real time, why the thing they thought they had doesn't exist."

One mechanism, three jobs

☺ Like you're 10: The exact same box and the exact same truck can rebuild your old room, a matching new room across town, or a totally different apartment in another city — you just decide where to send it.

Velero's restore path never asks whether the destination is the cluster it came from. That single fact is why the same mechanism covers three jobs that feel operationally different but are mechanically identical.

Cluster A Schedule writes nightly Object storage bucket one Backup history, read by any cluster Same cluster, mapped ns accidental-delete recovery New cluster, same shape disaster recovery Different cluster entirely planned migration
# Migration / DR, on the NEW cluster — same bucket, same prefix
$ velero install --provider aws \
    --bucket acme-k8s-backups --prefix prod-eu \
    --plugins velero/velero-plugin-for-aws:v1.10.0 \
    --secret-file ./credentials-velero --use-node-agent

$ kubectl -n velero patch backupstoragelocation primary \
    --type=merge -p '{"spec":{"accessMode":"ReadOnly"}}'   # restore-only until cutover

$ velero get backups                                          # confirm history synced back
$ velero restore create --from-backup checkout-nightly-20260827010000 --wait

Gotchas

☺ Like you're 10: The scary failures aren't loud errors — they're backups that say "mostly fine" and restores that quietly skip things nobody asked them to skip.

Velero vs. the alternatives

☺ Like you're 10: A few other boxes and helpers exist. Some are free, some come with a bill and a fancy dashboard, and one of them isn't really a backup at all.

These are mostly complementary layers, not rivals — a serious operating model runs more than one at once, which is exactly what Best Practices & Operating Model recommends: etcd snapshots for the control plane, Velero for namespaces and volumes, and the database's own native backup for the handful of datastores where that matters most.

OptionModelReach for it when
VeleroController copying API objects to object storage plus CSI or Kopia volume dataNamespace-granular recovery, cluster migration, cross-cloud portability, no license
etcd snapshotRaw dump of the control-plane datastore via etcdctl snapshot saveSelf-managed control planes — total cluster rebuild after control-plane loss, not usable on a managed control plane you don't own
Cloud disk snapshotStorage-layer snapshot taken by policy, outside Kubernetes entirelyA cheap safety net under the storage layer — you get disks back, not workloads; reassembly is manual
GitOps re-applyRebuild the cluster by syncing manifests from GitGenuinely stateless workloads, and always as the fast path for configuration — restores no volume data
Kasten K10 / Portworx PX-BackupCommercial Kubernetes data-management platformsApplication-aware policies, a full UI, and vendor support are worth the license cost
✎ Try it

On a throwaway kind cluster, install a local MinIO instance and point Velero at it as an S3-compatible BackupStorageLocation. Deploy anything with a PVC and write a recognizable file into it. Back the namespace up without --default-volumes-to-fs-backup, delete the namespace, and restore — the Deployment comes back, the file doesn't. Read velero backup describe --details and find the line that told you so. Now repeat with --default-volumes-to-fs-backup, restore into a mapped namespace this time, and confirm the file is there. That gap between "the restore said Completed" and "the data is actually present" is the entire lesson of this page.

Velero isn't a named domain on the CKA blueprint — but the judgment underneath it is: PersistentVolume reclaim policy, RPO/RTO thinking, and why an untested restore is a hope rather than a plan all sit inside the storage domain and Anti-Patterns & Pitfalls. For the deeper platform-engineering treatment — self-service backup APIs, quiesce-hook nuance, and where backup sits in a platform's reliability story — Platform Engineering's own Velero tool page goes considerably further than this page needs to, and the wider Golden Kubestronaut ladder those platform-engineering certifications belong to is mapped on the sibling Golden Astronaut course.

🎬 At the Pod Squad
🐘

Ellie the Elephant: Quarterly drill time — when did anyone last actually restore last night's checkout-nightly backup, rather than just glance at the phase column?

👺

Gizmo: Relax, there's already a cron job dumping Postgres to a second PVC in the same namespace every night. That's a backup. Ship it. 🤑

🐢

Timmy the Turtle: A copy that dies with the original isn't a backup, it's a second copy of the same risk. If that node or that namespace goes, both PVCs go with it.

🦊

Foxy: Fair — but even with Velero pointed at a real bucket, would a restore of that operator's CRs actually come back clean on a brand-new cluster?

🦫

Benny the Beaver: Only if the CRDs are in the backup too. I'm dropping the namespace filter and explicitly including the operator's CRDs — restore ordering does CRDs first, but only if they were captured at all.

🐘

Ellie the Elephant: Good. Restoring into checkout-drill now, timing the whole thing — that number becomes our actual RTO, not whatever's written in the runbook.

🐢 Timmy's checkpoint

1. What are the two independent things a Velero Backup captures, and why does distinguishing them matter when reading a result? 2. Name one thing an etcd snapshot backs up that Velero never touches, and one thing Velero backs up that an etcd snapshot doesn't. 3. A Backup finishes as Completed with zero volumes snapshotted. Is anything necessarily wrong? 4. You restore an old backup over a still-live namespace expecting a clean rollback. What actually happens instead? 5. Your DR plan is to fail over to a different region, and you rely on plain CSI snapshots with no data-movement flag set. What breaks, and what's the fix? 6. What's mechanically identical between "recover a deleted namespace" and "migrate a workload to a new cluster" using Velero?

Check your answers
  1. API objects (a tarball of what the API server held) and volume data (captured via CSI snapshot or Kopia filesystem backup). They're captured by different mechanisms and fail independently — a backup can report success while one half quietly captured nothing.
  2. An etcd snapshot captures the raw control-plane database — cluster-scoped state, certificates context, everything the API exposes and more — which Velero never touches. Velero captures namespaced API objects and PersistentVolume data, neither of which an etcd snapshot restores on its own.
  3. Almost certainly, yes. Completed only means everything requested succeeded — a namespace filter that matched nothing, or snapshotVolumes: false with no filesystem backup configured, produces a clean-looking backup that protected nothing. Check the resource and volume counts in describe --details.
  4. Not a rollback. Velero adds any objects missing from the live namespace and, with existingResourcePolicy: update, patches existing ones to match the backup — but it never deletes anything created since the backup ran. The result is a merged state, not a point-in-time revert.
  5. The disk snapshots stay in their original region and storage system, so the DR region has nothing to restore from. The fix is snapshotMoveData: true, which uploads the snapshot's contents into the (replicated) object storage bucket, or filesystem backup via Kopia, which is portable by design.
  6. Both are just "install Velero pointed at the same bucket and prefix, wait for the backup history to sync, then restore" — the only difference is intent and timing: one is triggered by an accident and done in the same cluster, the other is planned in advance and done somewhere else entirely. The bucket, not the cluster, is what makes either one possible.