Backing up Azure DevOps with a bespoke Databricks app
A small app running on Databricks workspace
CLOUDAZURE
Jack Jalali
10/31/20265 min read
Why I built my own
I was looking to find a way to back up my Azure DevOps. The tools I looked at either couldn't do it or cost more than the job justified, so I built a backup app of my own that is currently running on Databrick's free tier as a POC.
Azure DevOps holds my code, my backlog and the history of every decision in it. Microsoft keeps the service running, but recovering from my own mistakes is on me: a deleted repository, a bulk edit gone wrong, a project removed by accident. Without a backup, that history is simply gone.
I started with the obvious options:
Veeam, our existing backup platform, couldn't back up Azure DevOps at all. I've joined a dedicated Veeam Forum where a lot of users have requested this feature to be added.
Rubrik and dedicated SaaS tools such as BackRightUp could, but their licence costs were hard to justify for what we needed.
That left building something. Running it on Databricks meant I could control both the costs and where the data lives.
Requirements
Complete protection. Back up the assets that matter in one run: git repositories with every branch and tag, the board structure (area paths, iterations, teams) and work items with their fields, full history, comments, links and attachments.
Fast incremental runs. After the first full backup, each run should capture only what changed.
Disaster-recovery ready. Restore a whole project or just selected parts, into the original project, a new one or a different organization.
Storage control. Keep backups on infrastructure we control and pay for directly.
User friendly. Someone who isn't a developer should be able to run a backup or a restore without a manual.
Application flow on Databricks
The app is only the control panel; serverless jobs do the actual work:
The Databricks App is where you add Azure DevOps connections, start backups, set schedules and run the restore wizard. Every backup or restore runs as a runner job on serverless compute, so the app never does heavy work and doesn't need to be running for backups to happen. An daily scheduler job starts any backup that is scheduled to run.
Run history, schedules and the snapshot index live in Delta tables. Backups go to a Unity Catalog volume, and the Azure DevOps access tokens are kept in a Databricks secret scope, never shown in the browser. Users sign in with their Databricks account, so there are no separate passwords to manage.
How a backup works
Every backup is a complete snapshot of a project, but it stores only what is new. A second backup with nothing changed writes zero bytes.
Each snapshot is a small manifest that lists every item and points at its stored copy. The stored copies are the clever part:
Content-addressed storage. Every work item, attachment and settings document is saved under the SHA-256 hash of its contents. Identical content is stored once, so an unchanged work item or a 20 MB attachment costs nothing the second time.
Git as bundle chains. The first backup of a repository is a full git bundle. Later backups add a small bundle with only the new commits. A restore replays the chain, then sets every branch and tag to exactly what was recorded.
Work items with their history. Each work item is saved with every revision, its comments, links and attachments.
If one part fails, for example a repository that can't be reached, the others carry on. The snapshot then keeps the last good copy of the failed part and is marked partial, so it can still be restored.
How restore works
A restore is a four-step wizard that shows exactly what will happen before anything changes:
Backup. Pick the organisation, the project and the snapshot to restore from.
Contents. Choose repositories, board structure and work items: all of them, or specific IDs such as 12, 15-18.
Destination. Pick the target organisation and project. A project that doesn't exist yet is created with the same process as the original.
Review. A dry run lists every repository action, how many work items will be created and anything to watch out for. Only then does Start restore run it.
Restored work items always get new IDs, because Azure DevOps doesn't let you choose them. The app records which original became which copy, so links between items are rewired correctly and a re-run skips anything already restored. Each item's revisions are replayed in order with their original authors and dates, so its history still reads like the original.
Scheduled backups
Backups run automatically on a schedule you set per project: every 1 to 12 hours, daily at a chosen hour, or weekly on a chosen day. Times are in your own time zone and stay correct across daylight-saving changes.
A small scheduler job wakes up every hour on Databricks and starts any backup that is due. Because it is a job, schedules keep running even when the app itself is stopped. If the previous backup of a project is still running, the next one is skipped rather than piling up, and a missed slot runs once at the next check.
App's limitations
1- backups currently sit on a Databricks-managed volume rather than my own storage. Connecting my own S3 bucket failed because the AWS account was locked to a different region, and the workspace is on a free plan that limits this. With a standard AWS account and a paid workspace it is a two-line configuration change, and earlier snapshots stay restorable after the move.
2- The warehouse auto-stop didn’t work. Databricks refused with “Editing warehouse is not available to free users. Set up a paid account.”
this means while on a free tier, a cronjob starts the app 5 minutes before the scheduled job and based on job history the same cronjob shuts down the app.
After the upgrade, it’s a 30-second change in SQL Warehouses → Serverless Starter Warehouse → Edit → Auto stop.
The above two changes can reduce the running costs:
Run the app only when you use it. Backups and schedules are jobs and don't need the app, so a one-line command starts it when needed (one to three minutes) and stops it afterwards.
Keep the jobs off the SQL warehouse. The jobs now read and write their state through their own Spark session. I verified this on Databricks: a scheduled backup ran end to end while the warehouse stayed stopped.
That is the point of building it, the backups themselves cost a few pounds a month, and the rest scales with how much the app is used.
If you're facing the same gap, the approach transfers to any Databricks workspace: a small app as the control panel, jobs to do the work, and storage you choose.
