VPS Snaps

Backing up a Kubernetes cluster

Connect a cluster with a service account token — no in-cluster agent to install.

6 min readBackup Types

VPS Snaps connects to your cluster's Kubernetes API using a service account token. If Velero is already running in your cluster, we detect it and drive it directly for a fuller backup. Otherwise, a short-lived Job reads the namespace manifests inside the cluster and uploads them straight to your own storage, and we best-effort snapshot persistent volumes where your cluster's storage driver supports it.

Step 1 — Create a scoped service account

A service account with read access to the kinds VPS Snaps backs up, plus create and delete on volume snapshots for the volume half. Applied as YAML so it works whether or not your cluster has the CSI snapshot CRDs — a rule for an API group the cluster lacks simply grants nothing.

kubectl apply -f - <<'EOF'
apiVersion: v1
kind: ServiceAccount
metadata: { name: vpssnaps-backup, namespace: default }
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata: { name: vpssnaps-backup-role }
rules:
- apiGroups: [""]
  resources: [namespaces, configmaps, secrets, services, persistentvolumeclaims]
  verbs: [get, list, watch]
- apiGroups: [apps]
  resources: [deployments, statefulsets, daemonsets]
  verbs: [get, list, watch]
- apiGroups: [batch]
  resources: [jobs, cronjobs]
  verbs: [get, list, watch]
- apiGroups: [networking.k8s.io]
  resources: [ingresses]
  verbs: [get, list, watch]
- apiGroups: [storage.k8s.io]
  resources: [storageclasses]
  verbs: [get, list, watch]
- apiGroups: [snapshot.storage.k8s.io]
  resources: [volumesnapshotclasses]
  verbs: [get, list, watch]
- apiGroups: [snapshot.storage.k8s.io]
  resources: [volumesnapshots]
  verbs: [get, list, create, delete]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata: { name: vpssnaps-backup-binding }
roleRef: { apiGroup: rbac.authorization.k8s.io, kind: ClusterRole, name: vpssnaps-backup-role }
subjects: [{ kind: ServiceAccount, name: vpssnaps-backup, namespace: default }]
EOF

Step 2 — Let backups run inside the cluster

Lets each backup run as a short-lived Job in the account's own namespace, as that same read-only account, so the manifests — Secrets included — go from the cluster to your storage without passing through VPS Snaps. The Job is deleted when it finishes and removes itself ten minutes later regardless. Skip this and our worker reads the manifests over the API instead; the run's log says which happened. The pod pulls alpine:3.24 from Docker Hub and installs curl and jq, so it needs outbound internet access.

kubectl apply -f - <<'EOF'
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { name: vpssnaps-backup-jobs, namespace: default }
rules:
- apiGroups: [batch]
  resources: [jobs]
  verbs: [create, get, delete]
- apiGroups: [""]
  resources: [pods, pods/log]
  verbs: [get, list]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: vpssnaps-backup-jobs, namespace: default }
roleRef: { apiGroup: rbac.authorization.k8s.io, kind: Role, name: vpssnaps-backup-jobs }
subjects: [{ kind: ServiceAccount, name: vpssnaps-backup, namespace: default }]
EOF

Only with Velero installed

Lets VPS Snaps find Velero and ask it for backups and restores. Without it a cluster running Velero is backed up as one without.

kubectl apply -f - <<'EOF'
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata: { name: vpssnaps-backup-velero }
rules:
- apiGroups: [apiextensions.k8s.io]
  resources: [customresourcedefinitions]
  resourceNames: [backups.velero.io]
  verbs: [get]
- apiGroups: [velero.io]
  resources: [backups, restores]
  verbs: [get, list, create]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata: { name: vpssnaps-backup-velero }
roleRef: { apiGroup: rbac.authorization.k8s.io, kind: ClusterRole, name: vpssnaps-backup-velero }
subjects: [{ kind: ServiceAccount, name: vpssnaps-backup, namespace: default }]
EOF

Step 3 — Get the token and CA certificate

Modern Kubernetes doesn't auto-generate long-lived service account tokens, so request one explicitly:

kubectl create token vpssnaps-backup -n default --duration=8760h
kubectl config view --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d

The token above lasts a year. Rotate it before it expires by creating a new one and updating the cluster connection in VPS Snaps.

Step 4 — Connect the cluster

In VPS Snaps, go to Kubernetes and add a cluster with your API server URL (from kubectl config view), the token, and the CA certificate.

Without a CA certificate, we can't verify the cluster's identity over TLS. Only skip it if your API server uses a publicly-trusted certificate — most managed clusters don't.

What gets backed up without Velero

  • ConfigMaps, Secrets, Services, PersistentVolumeClaims
  • Deployments, StatefulSets, DaemonSets
  • Jobs, CronJobs, Ingresses
  • Persistent volume data — only for PVCs whose StorageClass has a matching VolumeSnapshotClass

This is a well-known set of workload resource kinds, not full dynamic API discovery — it covers what matters for restoring a typical application, not every custom resource in your cluster.

What gets backed up with Velero

If Velero is already installed (in the default 'velero' namespace), we create a Backup custom resource and let Velero do what it's configured to do — including its own volume data mover if you've set one up. The backup lands in Velero's own configured storage location, not the storage destination you pick in VPS Snaps.

Still stuck?

Open the failed run under Backup History and copy the error text out of the log — pasting that into your first message is usually the difference between one reply and four.