A Kubernetes operator for KubeVirt that enables file-level restore operations for virtual machines from various backup sources.
The VM File Restore Operator provides a declarative way to restore individual files and directories to KubeVirt VirtualMachines from multiple source types:
- PersistentVolumeClaims (PVCs): Restore from backup PVCs
- VolumeSnapshots: Restore from Kubernetes VolumeSnapshots
- Remote Storage: Restore from remote storage via rclone (S3, GCS, etc.)
This operator simplifies disaster recovery and file-level backup scenarios for virtualized workloads running on KubeVirt, enabling granular restore operations without needing to restore entire VM disk images.
- Declarative File Restore: Use Kubernetes CRs to restore files to running VMs
- Multiple Source Types: Restore from PVCs, VolumeSnapshots, or remote storage via rclone
- Automatic and Manual Modes: Automatic restore with
sourcePath, or manual mode (omitsourcePath) for interactive restore - Hot-plug Technology: No VM restart required - volumes are hot-plugged at runtime
- Guest OS Auto-Detection: Automatically detects Linux/Windows and adjusts mount paths
- SSH-Based Execution: Secure SSH access for executing restore commands in guest OS
- Robust Error Handling: Automatic retries, timeouts, and detailed error reporting
- Idempotent Operations: Safe to retry, handles partial failures gracefully
- HCO Integration: Can be managed by HyperConverged Cluster Operator with TLS security profiles
The operator uses a 10-phase state machine:
New → Init → Hotplugging → WaitingForAttachment → SSHConnecting →
Restoring → Cleanup → Succeeded
↓
Failed
How it works:
- Init: Validates target VM is running and source exists
- Hotplugging: Modifies VM spec to add restore volume (hot-plug)
- WaitingForAttachment: Waits for KubeVirt to attach volume to VMI
- SSHConnecting: Establishes SSH connection to VM guest OS
- Restoring: Executes helper script to mount and restore files
- Cleanup: Unplugs volume from VM, deletes temporary PVCs
- Succeeded/Failed: Terminal state with completion time
Special Mode:
- VolumeReady: Manual restore mode - volume stays attached until CR deletion
- go version v1.26.0+
- docker version 17.03+
- kubectl version v1.11.3+
- Access to a Kubernetes v1.11.3+ cluster with KubeVirt installed
Build and push your image to the location specified by IMG:
make docker-build docker-push IMG=<some-registry>/file-restore-operator:tagNOTE: This image ought to be published in the personal registry you specified. And it is required to have access to pull the image from the working environment. Make sure you have the proper permission to the registry if the above commands don’t work.
Install the CRDs into the cluster:
make installDeploy the Manager to the cluster with the image specified by IMG:
make deploy IMG=<some-registry>/file-restore-operator:tagNOTE: If you encounter RBAC errors, you may need to grant yourself cluster-admin privileges or be logged in as admin.
Create instances of your solution You can apply the samples (examples) from the config/sample:
kubectl apply -k config/samples/NOTE: Ensure that the samples has default values to test it out.
The operator requires SSH access to VMs to execute restore operations. The operator connects as user filerestore on both Linux and Windows VMs.
The operator stores everything needed for VM setup in its ConfigMap (vm-file-restore-operator-ssh):
data.ssh-publickey— the operator's SSH public keybinaryData.linux-helpers.tar— uncompressed tar withsetup.shandfilerestore.shbinaryData.windows-helpers.tar— uncompressed tar withsetup.batandfilerestore.bat
Extract the scripts from the ConfigMap and run them on the VM — no internet access required in the guest.
For Linux VMs:
PUB_KEY=$(kubectl get configmap vm-file-restore-operator-ssh -n file-restore \
-o jsonpath='{.data.ssh-publickey}')
mkdir -p /tmp/linux-helpers
kubectl get configmap vm-file-restore-operator-ssh -n file-restore \
-o jsonpath="{.binaryData['linux-helpers\.tar']}" \
| base64 -d | tar -xf - -C /tmp/linux-helpers/
virtctl scp --recursive /tmp/linux-helpers/ root@vmi/fedora:/tmp/
virtctl ssh root@vmi/fedora -c "bash /tmp/linux-helpers/setup.sh '$PUB_KEY'"For Windows VMs:
PUB_KEY=$(kubectl get configmap vm-file-restore-operator-ssh -n file-restore \
-o jsonpath='{.data.ssh-publickey}')
mkdir -p /tmp/win-helpers
kubectl get configmap vm-file-restore-operator-ssh -n file-restore \
-o jsonpath="{.binaryData['windows-helpers\.tar']}" \
| base64 -d | tar -xf - -C /tmp/win-helpers/
virtctl scp --recursive /tmp/win-helpers/ Administrator@vmi/win11:win-helpers
virtctl ssh Administrator@vmi/win11 -c "win-helpers\setup.bat \"$PUB_KEY\""What the setup scripts do:
- Create the
filerestoreuser with appropriate permissions - Configure SSH key authentication (command-restricted for security)
- Set up passwordless sudo (Linux only, restricted to restore script)
- Install the helper script (
filerestore.shorfilerestore.bat) from the same directory - Verify the installation
Manual setup: If you prefer manual configuration or need to troubleshoot, see guest-helpers/linux/setup.sh and guest-helpers/windows/setup.bat for the detailed steps.
Once SSH is configured and helpers are installed, create a restore:
kubectl apply -f config/samples/restore_v1alpha1_virtualmachinefilerestore.yamlMonitor progress:
kubectl get vmfr -w
kubectl describe vmfr <restore-name>The VirtualMachineFileRestore CRD allows you to specify:
- target: Reference to the target VirtualMachine (apiGroup, kind, name)
- source: One of
pvc,snapshot, orremote - sourcePath: File or directory path to restore from the backup (omit for manual mode)
apiVersion: filerestore.kubevirt.io/v1alpha1
kind: VirtualMachineFileRestore
metadata:
name: restore-from-pvc
spec:
target:
apiGroup: kubevirt.io
kind: VirtualMachine
name: my-vm
source:
pvc:
name: backup-pvc
sourcePath: /home/user/dataapiVersion: filerestore.kubevirt.io/v1alpha1
kind: VirtualMachineFileRestore
metadata:
name: restore-from-snapshot
spec:
target:
apiGroup: kubevirt.io
kind: VirtualMachine
name: my-vm
source:
snapshot:
name: vm-snapshot-20260415
sourcePath: /etc/database/db.confapiVersion: filerestore.kubevirt.io/v1alpha1
kind: VirtualMachineFileRestore
metadata:
name: restore-from-remote
spec:
target:
apiGroup: kubevirt.io
kind: VirtualMachine
name: my-vm
source:
remote:
name: s3_backup
bucket: my-bucket
sourcePath: /home/user/dataNote: Remote sources require rclone to be configured in the guest VM with the named remote.
Omit sourcePath to hotplug the volume without automatic restore. The volume stays attached in VolumeReady phase until you delete the CR:
apiVersion: filerestore.kubevirt.io/v1alpha1
kind: VirtualMachineFileRestore
metadata:
name: manual-restore
spec:
target:
apiGroup: kubevirt.io
kind: VirtualMachine
name: fedora
source:
snapshot:
name: snap1In manual mode:
- Volume is hotplugged and mounted at
/backup(Linux) orC:\backup(Windows) - CR stays in
VolumeReadyphase - SSH into VM and manually copy files
- Delete CR to unplug volume and clean up
kubectl get vmfr
kubectl describe vmfr restore-from-pvcThe status shows the current phase and progress:
Phases:
New- CR created, not yet startedInit- Validating target VM and sourceHotplugging- Attaching restore volume to VMWaitingForAttachment- Waiting for volume to attach (max 5 minutes)SSHConnecting- Establishing SSH connection (max 2 minutes with retry)Restoring- Executing file restore commandVolumeReady- Manual restore mode (sourcePath empty), volume is mountedCleanup- Unplugging volume and cleaning upSucceeded- Restore completed successfullyFailed- Restore failed (see errorMessage for details)
Status Fields:
phase- Current phase of the restorestartTime- When the restore startedcompletionTime- When the restore completedrestoredFilesCount- Number of files restoredmountPath- Where the volume is mounted in guest OSerrorMessage- Details if restore failedconditions- Additional status information
Timeouts and Retries:
- Volume attachment: 5-minute timeout with exponential backoff
- SSH connection: 2-minute timeout with retry
- All operations are idempotent and safe to retry
Delete the instances (CRs) from the cluster:
kubectl delete -k config/samples/Delete the APIs(CRDs) from the cluster:
make uninstallUnDeploy the controller from the cluster:
make undeployFollowing the options to release and provide this solution to the users.
- Build the installer for the image built and published in the registry:
make build-installer IMG=<some-registry>/file-restore-operator:tagNOTE: The makefile target mentioned above generates an 'install.yaml' file in the dist directory. This file contains all the resources built with Kustomize, which are necessary to install this project without its dependencies.
- Using the installer
Users can just run 'kubectl apply -f ' to install the project, i.e.:
kubectl apply -f https://raw.githubusercontent.com/<org>/file-restore-operator/<tag or branch>/dist/install.yaml- Build the chart using the optional helm plugin
operator-sdk edit --plugins=helm/v1-alpha- See that a chart was generated under 'dist/chart', and users can obtain this solution from there.
NOTE: If you change the project, you need to update the Helm Chart using the same command above to sync the latest changes. Furthermore, if you create webhooks, you need to use the above command with the '--force' flag and manually ensure that any custom configuration previously added to 'dist/chart/values.yaml' or 'dist/chart/manager/manager.yaml' is manually re-applied afterwards.
Runs Go unit tests and containerized guest-helper script tests (requires docker or podman):
make testE2E tests exercise the operator against a real KubeVirt cluster created by
kubevirtci. They require a running
cluster, a deployed operator, and virtctl on PATH (guest verification uses
virtctl ssh). hack/test-e2e.sh installs virtctl from the KubeVirt release
matching KUBEVIRT_VERSION when it is not already available.
| Target | What it does |
|---|---|
make cluster-functest |
Full workflow: tear down any existing cluster, bring up kubevirtci, build and deploy the operator, run e2e tests (hack/test-e2e.sh). |
make test-e2e |
Run the e2e test suite only. Cluster must already be up and the operator deployed. |
make cluster-up |
Create the kubevirtci cluster (KubeVirt, CDI, storage). |
make cluster-sync |
Build the operator image, push it to the kubevirtci registry, apply manifests, restart the deployment. |
make cluster-down |
Tear down the kubevirtci cluster. |
make cluster-image-env |
Print export PUSH_IMG=... and export IMG=... for the current kubevirtci registry. |
Run make help for the full target list.
E2E does not deploy a pre-built image from Quay. cluster-sync builds the
operator from the current working tree, tags it with the current git commit
(dev-<short-sha> by default), and uses kubevirtci's embedded registry:
| Variable | Used for | Typical value |
|---|---|---|
PUSH_IMG |
docker build / docker push from the host |
localhost:<port>/kubevirt/vm-file-restore-operator:dev-<sha> |
IMG |
Image reference written into dist/install.yaml |
registry:5000/kubevirt/vm-file-restore-operator:dev-<sha> |
The host and in-cluster URLs differ because kubevirtci exposes the registry on
localhost:<port> outside the cluster and as registry:5000 inside it. Values
are read from kubevirtci/_ci-configs/<provider>/config-provider-*.sh after
cluster-up; see hack/kubevirtci-image-env.sh.
Set both to the same registry when pushing and pulling from outside kubevirtci (the host and cluster use the same URL):
PUSH_IMG=registry.example.com/kubevirt/vm-file-restore-operator:example-tag \
IMG=registry.example.com/kubevirt/vm-file-restore-operator:example-tag \
make cluster-syncmake cluster-functestEquivalent to:
./hack/test-e2e.shRequires docker (or podman), kubectl, and sufficient resources for a
2-node cluster with rook-ceph storage. virtctl is installed automatically by
hack/test-e2e.sh if missing. Defaults match the prow job (KUBEVIRT_NUM_NODES=2,
KUBEVIRT_STORAGE=rook-ceph-default).
Use this when the cluster is already up and you want to redeploy or re-run tests without recreating the cluster.
make cluster-up
kubeconfig="$(source hack/config.sh && ./kubevirtci/cluster-up/kubeconfig.sh)"
export KUBECONFIG="${kubeconfig}"
kubectl get nodes
make cluster-sync
source hack/install-virtctl.sh # skip if virtctl is already on PATH
make test-e2eRebuild the manifest and restart the deployment without rebuilding the image
(useful when only test/e2e/ changed):
SKIP_IMAGE_BUILD=true make cluster-sync
make test-e2eRun a subset of tests:
E2E_TEST_CMD='go test ./test/e2e/ -v -ginkgo.v -timeout=90m -ginkgo.focus="manual restore"' make test-e2eDefault test timeout is 90m (E2E_TIMEOUT).
Prow job pull-vm-file-restore-operator-e2e runs ./hack/test-e2e.sh on bare-metal
workers (quay.io/kubevirtci/golang). The image does not include virtctl; the
script downloads the linux/amd64 release binary at runtime (prow and kubevirtci
e2e target). Install virtctl yourself on other platforms. The job is optional
and not run on every PR; trigger it with:
/test pull-vm-file-restore-operator-e2e
KUBEVIRT_VERSION=v1.9.0 make cluster-up # cluster + virtctl version (default: v1.8.4)
KUBEVIRTCI_TAG=<tag> make cluster-up # kubevirtci gocli version
KUBEVIRT_PROVIDER=k8s-1.37 make cluster-up # Kubernetes version (default: k8s-1.36)
KUBEVIRT_NUM_NODES=3 make cluster-up # cluster size (default: 2)
KUBEVIRT_WAIT_TIMEOUT=15m make cluster-up # KubeVirt/CDI readiness (default: 10m)make cluster-downRun golangci-lint:
make lintFix linting issues automatically:
make lint-fix// TODO(user): Add detailed information on how you would like others to contribute to this project
NOTE: Run make help for more information on all potential make targets
More information can be found via the Kubebuilder Documentation
Copyright 2026.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.