You already run something. Make it back up.
A pipeline, a cron entry, a deploy hook, a script nobody has touched in a year. Whatever already has the credentials and already runs on a schedule can push a backup with one command, or one API call. No agent, no inbound access, no second scheduler. What you are missing is somewhere safe for the output to land.
$ pg_dump "$DATABASE_URL" | saved backup submit prod-db -No card required. USD 50 credit on every new workspace.
Most pipeline backups end in a bucket nobody looks at
The step itself is easy, and almost every team has written it. What is missing is everything after it: whether the file is complete, whether it is reachable by whatever just compromised the database, whether anyone would notice the night it stopped running, and whether anyone can still open it in a year.
A year of nightly backups. The band in spring is the workflow that silently stopped being scheduled. A directory listing shows you the squares on either side of it and never the hole.
The file that is 20 bytes
pg_dump fails, gzip succeeds compressing nothing, and the pipeline exits zero. Every run records a size, so a backup that produced 4 KB instead of 4 GB is visible the next morning rather than in a year.
The copy in the same account
A bucket in the account that runs the database shares its blast radius. Whoever gets production access gets the backups, which is precisely the scenario they existed for.
The archive nobody can open
Artifacts record their own format, compression and encryption. Unpacking one by hand uses tools already on your machine, and the procedure is documented rather than implied.
The four things a platform team says next
“We are not installing an agent.”
You are not. The CLI is a static binary the job downloads and throws away with the runner. Nothing persists between builds, nothing listens, and there is no inbound access to grant.
“Our runner cannot reach the database.”
Then this is the wrong path and a worker is the right one. It runs next to the database on your own hardware and we drive the schedule. Same artifact, same encryption, same retention.
“We do not want another secret in CI.”
It is one API key, scoped to a single workspace with only the permissions you grant it. It cannot read your artifacts, because nothing can: they are encrypted to a key that never enters the runner.
“What stops this silently breaking?”
The job fails, and a red pipeline is a signal you already watch. That is also the honest limit of this path: if the workflow stops being scheduled at all, we have nothing to notice on your behalf.
The same step, wherever it runs
The CLI reads stdin, so its shape does not change between callers. Underneath, all four are the same three calls, and the raw API is there for the cases a shell pipeline cannot express. There is no plugin to install and no marketplace action to trust with your database credentials.
GitHub Actions
.github/workflows/backup.yml
on:
schedule: [{ cron: "0 2 * * *" }]
jobs:
backup:
runs-on: ubuntu-latest
steps:
- run: |
pg_dump "$DATABASE_URL" \
| saved backup submit prod-db -
env:
SAVED_API_KEY: ${{ secrets.SAVED_API_KEY }}GitLab CI
.gitlab-ci.yml
nightly-backup:
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
script:
- pg_dump "$DATABASE_URL"
| saved backup submit prod-db -cron, or any shell
crontab, Jenkins, a Makefile
# it reads stdin, so the shape # never changes between callers pg_dump "$DATABASE_URL" \ | saved backup submit prod-db -
Anything that speaks HTTP
REST API
# ask for a URL, then PUT to it curl -X POST $API/v1/.../runs curl -X PUT "$UPLOAD_URL" --data-binary @dump.sql curl -X POST $API/v1/.../confirm
$ pg_dump $DATABASE_URL | saved backup submit prod-db -reading stdinencrypting to 9F2A…C410 (your key)uploading 4.21 GB ████████████████████ 100%sealing sha256:7c1e…8b03✓ run_01JQ8F3K2M sealed, locked until 2026-11-02$ saved run list --backup prod-db --limit 303:12 ✓ 4.21 GB 00:03:1202:00 ✓ 4.19 GB 00:03:0401:00 ✓ 4.19 GB 00:03:09
No agent. No inbound port. Ten lines of output you can read.
Ciphertext, and nothing else
Encryption happens in the job, with your public key, before anything crosses the network. That is what makes an API key in CI a small thing to grant: it can create runs, and it cannot read a single byte of what those runs produced.
The private half never enters CI
You upload a public key. There is no configuration in which the runner, or we, hold the half that decrypts.
A held artifact stays held
Set a hold and it is stamped onto every artifact written from then on. It can be lengthened and never shortened, so a compromised key cannot loosen the policy and then delete.
Bulk data skips us
The artifact goes straight to object storage against a short-lived URL. Only metadata passes through our orchestration.
Three people have to agree to this
The engineer adding it
How long is this going to take?
- One step, and it reads stdin so it fits whatever you already produce
- No machine to provision and nothing to keep running between backups
- A red build if it breaks, which is a signal you already watch
The reviewer on the PR
What are we granting, exactly?
- One workspace-scoped API key, with only the permissions you give it
- Outbound HTTPS only, so no firewall change and no inbound port
- The key cannot read artifacts, because encryption happens before upload
Whoever owns the budget
What does it cost to run?
- No seats, and no charge for the pipeline minutes we never used
- Almost no computation, because the run happened on your infrastructure
- USD 50 of credit, no card, and nothing auto-charges when it runs out
Push until handing over the schedule buys you something
Two things this path cannot do, and both are reasons to add a worker rather than to start with one. You can adopt it later without changing anything you already wrote.
Nobody notices a run that never happened
Your pipeline owns the schedule, so a workflow that stops firing produces no signal on our side. A worker gives us an expected run to miss, which is the difference between a schedule and a cron entry that stopped in March.
The runner has to reach the source
A database behind a VPN, on-premise, or with no route from hosted CI is not reachable from a runner. A worker sits next to it and connects locally.
The cheapest of the three paths
The run happened on your infrastructure, on your time, so this shape generates almost no computation and no working storage on our side. A bill is archive plus the transit to get the bytes here, which means retention is the lever rather than how often you run.
What you need
- A runner that can already reach the source
- An API key stored as a repository secret
- A GPG public key uploaded to the workspace
That is the whole list. No machine to provision, no network change to request, and nothing left running between backups.
Add the line, watch one run, then decide
If it does not show up somewhere you like, you have lost a line of YAML. That is the whole risk.