PVE Cloud Backup
Self-hosted Proxmox VE backup manager for uploads to an existing rclone remote such as OneDrive.
The application is intentionally small:
- FastAPI backend and worker
- Vue 3 + TypeScript + Tailwind frontend, compiled once into
static/ - SQLite for all runtime configuration and state
pveshfor Proxmox API accessrclone copyto,rclone lsjson,rclone deletefile, andrclone rmdirfor exact remote objects and empty per-backup folders- systemd services, no Docker
Layout
All application files live under:
/opt/pve-cloud-backup/
backend/
frontend/
static/
data/
app.db
logs/
scripts/
The install script copies the two systemd service files to /etc/systemd/system/.
Prerequisites
Install these on the Proxmox host:
- Python 3 with
venv - Node.js and npm for the one-time frontend build
rclone- a preconfigured rclone remote, for example OneDrive or a crypt remote backed by OneDrive
- working
pveshaccess as the service user
The app does not configure rclone. If no remote exists, setup fails until you configure rclone yourself.
Install
From the installation directory:
cd /opt/pve-cloud-backup
./scripts/install.sh
The installer:
- creates required subdirectories,
- creates a Python virtual environment,
- installs Python dependencies,
- builds the Vue frontend into
/opt/pve-cloud-backup/static, - initializes SQLite migrations,
- installs and starts systemd services.
Open:
http://<proxmox-host>:8080
Setup
On first launch, complete the setup wizard:
- Proxmox node
- Proxmox backup storage
- local backup directory
- rclone executable path
- rclone remote name
- remote backup path/folder within that remote
- Discord webhook URL
- allowed CORS origins
- default compression
- default backup mode
- default retention policy
- max concurrent backups
All settings are stored in SQLite at /opt/pve-cloud-backup/data/app.db.
Recovery behavior
On worker startup, unfinished records in these states are reconciled:
pve_runninglocal_readyuploadingremote_readylocal_deletingdeleting
The worker does not delete unknown local files or remote objects. Local archives are deleted only after upload finishes and the remote object is verified.
Retention cleanup
Retention cleanup runs in two places:
- immediately after a backup completes for that job;
- periodically from the worker service, once per hour, so changed rules and day-based expiry are enforced even if no new backup runs.
Remote deletion uses exact-object rclone deletefile calls for backup records already known in SQLite, then best-effort rclone rmdir cleanup for the empty per-backup folder.
Testing
Run backend tests:
cd /opt/pve-cloud-backup/backend
./.venv/bin/pytest
Run frontend type check and build:
cd /opt/pve-cloud-backup/frontend
npm run build
Manual integration test against a real Proxmox guest:
INTEGRATION_VMID=<vmid> API_URL=http://127.0.0.1:8080 /opt/pve-cloud-backup/scripts/integration-test.sh
Optional environment variables:
INTEGRATION_GUEST_NAMEto assert the discovered guest name before queueing.INTEGRATION_STORAGEto override the configured Proxmox backup storage.INTEGRATION_LOCAL_BACKUP_DIRto assert the configured local dump directory.INTEGRATION_CRONto choose the created job schedule. Default:0 3 * * *.INTEGRATION_RUN_NOW=0to create the job without immediately queueing it.
The script creates a disabled backup job and, by default, queues it once. Only run it when you intentionally want a real Proxmox backup and rclone upload.
CI
Gitea Actions workflow:
.gitea/workflows/ci.yml
It runs on pushes and pull requests to main and can also be started manually. The workflow checks:
- backend dependency install and pytest;
- frontend dependency install and production build;
- shell script syntax.
Package caches are enabled through the Gitea Actions cache-compatible setup actions:
- Python/pip cache keyed by
backend/requirements.txt; - npm cache keyed by
frontend/package-lock.json.
The Gitea runner must have action cache support configured for cache restore/save to work.
Updates
After pulling new code, apply it with:
cd /opt/pve-cloud-backup
git pull --ff-only
./scripts/apply-update.sh
For a stricter update that runs backend tests before restarting services:
RUN_TESTS=1 ./scripts/apply-update.sh
The update script backs up data/app.db, rebuilds dependencies/assets, runs migrations, installs service units, restarts services, and checks API health.
Uninstall
/opt/pve-cloud-backup/scripts/uninstall.sh
The uninstall script removes and disables systemd services. It leaves /opt/pve-cloud-backup intact so app.db, logs, and any remaining data are not destroyed accidentally.