Backups for Convex you can actually restore.
Scheduled, encrypted, off-site backups for self-hosted Convex and Convex Cloud — see your whole backup estate on one map, spin up a brand-new bucket in under a minute, and restore with confidence instead of hope. Plus a production-ready Convex-on-Coolify template to run the instances themselves.
60-second setup · Deploy anywhere · Why teams use it · Screenshots · How it works · Security · FAQ
Convex's snapshot export is great, but a backup strategy needs more than npx convex export in a cron job:
- Off-site, versioned and immutable copies. A backup on the same server, or one an attacker can delete, isn't a backup.
- Encryption you control, so a leaked bucket doesn't leak your users' data.
- Restores that are rehearsed and safe: into a staging copy, one table at a time, with personal data scrubbed.
- Visibility: know when a backup didn't happen, not just when one failed, and see how everything connects.
- One place for every project and client, with proper access control.
Convex Backup Console does all of that in a single container — and now shows you the whole picture at a glance.
Every instance, job and destination, drawn as a live topology — not three separate lists you have to mentally wire together. Each connection is colored by the health of its last run, running jobs animate, and hovering (or tapping, on mobile) traces one path end to end. ⌘K jumps straight to any node.
No more hand-creating an R2 or S3 bucket, an IAM user, and a least-privilege policy before you can even add a destination. Paste a one-time setup credential and the console provisions the bucket and mints a brand-new credential scoped to only that bucket — the setup credential is never stored, logged or shown again. Progress streams in step by step, and anything created is rolled back if a later step fails.
Every backup shows its export → compress → encrypt → upload → verify steps, each with a real
duration — live while it's running. After every upload, the stored object's size is confirmed
against the file that was sent (S3 HeadObject, pinned to the version; local: stat); a mismatch
fails that destination's copy exactly like an upload failure would.
Full snapshot or selected tables, into the same instance or a different one, with source → target lineage shown above the live log so there's no ambiguity about what's about to happen. Scrub emails, names and phone numbers for a safe staging copy, confirm by typing the target's name, and watch the import stream in real time.
Fan out to Cloudflare R2, AWS S3, Backblaze B2, Wasabi, MinIO, RustFS or a local volume. Turn on S3 Object Lock and a backup can't be deleted or overwritten for N days — not even with stolen credentials. The Storage page shows growth over 90 days, a retention timeline per destination, and an estimated monthly cost.
- + New → Docker Compose Empty → paste
backup-console.coolify.yaml. - Deploy. Coolify generates the domain and the secrets.
- Open the URL and follow the setup wizard: paste the one-time setup code from the service's Logs, create your admin account, add 2FA, create the first workspace (name, logo, timezone) and invite your team.
- Create your encryption key in Workspace → Settings.
Save
SERVICE_PASSWORD_64_MASTERKEYin your password manager. It encrypts every stored key and secret.
Full guide, including OAuth setup: docs/coolify.md.
One click from render.yaml: disk, health check and generated secrets (paid instance for the disk). |
|
Deploy the repo with root directory /app, add a volume at /data and two secrets. |
|
| Fly.io | cd app && fly launch --copy-config with the included fly.toml (steps). |
Not Vercel/Netlify: the console needs an always-on process (for schedules) and a persistent disk (for its
database), which serverless platforms don't provide. See docs/deploy.md for requirements and
every platform.
docker run -d --name convex-backups -p 3000:3000 \
-v convex-backups:/data --read-only --tmpfs /tmp \
-e PUBLIC_URL=https://backups.example.com \
-e BACKUP_MASTER_KEY="$(openssl rand -base64 48)" \
-e BETTER_AUTH_SECRET="$(openssl rand -base64 48)" \
ghcr.io/flyingwebie/convex-backup-console:latest
docker logs convex-backups # shows the setup code; then open your PUBLIC_URLPut it behind HTTPS (Traefik, Caddy, Nginx or a Cloudflare Tunnel; see app/docker-compose.tunnel.yaml).
| Step | Where |
|---|---|
Add a self-hosted instance: API URL + ./generate_admin_key.sh output |
Instances → Add instance |
| Add a Convex Cloud deployment: a production or dev deploy key | Instances → Add instance → Convex Cloud (guide) |
| Add a destination — Create bucket for me, or connect an existing R2/S3/B2… | Destinations |
| Create a job: schedule, retention, encryption | Jobs |
| Choose who gets which email, brand and edit them | Emails |
| Chat/webhook alerts | Notifications |
convex-instance.coolify.yaml deploys self-hosted Convex with Postgres and
RustFS (S3) storage on Coolify. The detailed walkthrough is in docs/convex-on-coolify.md.
A live demo workspace — "Acme Agency," four instances, three destinations, three months of scheduled runs — walked through the redesigned console.
flowchart LR
subgraph Sources
SH[Self-hosted Convex<br/>URL + admin key]
CC[Convex Cloud<br/>deploy key]
end
subgraph Console[Convex Backup Console · one container]
S[Scheduler / API / UI] --> X[Export → compress → encrypt → upload → verify]
R[Restore: filter tables · scrub PII] --> I[Convex import API]
DB[(SQLite · secrets encrypted<br/>with BACKUP_MASTER_KEY)]
end
SH & CC -- snapshot export --> X
X --> D1[(Cloudflare R2)]
X --> D2[(S3 / B2 with Object Lock)]
X --> D3[(Local volume)]
D1 & D2 & D3 --> R
I --> SH & CC
X -. alerts & digests .-> N[Branded email · Slack · Discord · Telegram · Webhook]
- A job asks the instance for a snapshot export through Convex's admin HTTP API, the same API
npx convex exportuses. - The console downloads it, records a manifest (tables, row counts, files, SHA-256) and encrypts it with the workspace's age key.
- The archive is uploaded to every destination (with Object Lock where configured) and verified against the stored object, then retention prunes old copies and the export is removed from the source.
- A restore downloads and decrypts a copy, optionally filters tables or scrubs personal data, and runs Convex's import with your chosen mode.
age -d -i convex-backups-age-key.txt backup.zip.age > backup.zip # key from Workspace → Settings
npx convex import --replace-all backup.zip # into any Convex deployment| Area | How |
|---|---|
| Sign-in | Invite-only. Password (12+ characters) or Google / GitHub / Microsoft; second factor always required (TOTP or passkey), including after social sign-in |
| Sessions | 24h, HttpOnly cookies, per-session factor tracking, list/revoke, sign out everywhere |
| Brute force | 5 sign-in / 2FA attempts per minute per IP (Cloudflare-aware) |
| Secrets at rest | Admin keys, deploy keys, S3 secrets, webhook URLs and age keys are encrypted with AES-256-GCM under BACKUP_MASTER_KEY |
| Backups at rest | age (X25519) per workspace; S3 Object Lock for immutability |
| One-time credentials | "Create bucket for me" setup credentials are held in memory for a single request only — never stored, logged or shown again |
| Isolation | Every query scoped to the active workspace; role checks on every server function and API call |
| Container | Non-root, read-only filesystem, all capabilities dropped |
| Audit | Every change, backup, restore, download and key reveal is logged |
- uses: flyingwebie/convex-backup-console/.github/actions/convex-backup@main
with:
console-url: https://backups.example.com
token: ${{ secrets.BACKUP_CONSOLE_TOKEN }} # Workspace → Settings → API tokens
job-id: <job-id>
- run: npx convex deployThe REST API is documented in docs/github-action.md.
| This console | npx convex export + cron |
Convex Cloud built-in backups | |
|---|---|---|---|
| Self-hosted and Cloud | ✅ | ✅ | Cloud only |
| Off-site copies in your own storage | ✅ | DIY | ❌ |
| Encryption you hold the key to | ✅ | DIY | ❌ |
| Immutable (Object Lock) | ✅ | DIY | ❌ |
| One-click bucket + scoped credential | ✅ | ❌ | — |
| Partial & scrubbed restores | ✅ | ❌ | ❌ |
| Visual topology of instances/jobs/destinations | ✅ | ❌ | ❌ |
| Alerts, digests, status URL | ✅ | DIY | — |
| Multi-project, roles, audit log | ✅ | ❌ | Per-team |
Convex Cloud's own backups are excellent, so keep them on. This adds independent, encrypted copies outside Convex.
Backups
- 🔁 Manual, cron or API-triggered jobs, one instance → many destinations, including file storage.
- ☁️ Self-hosted Convex (URL + admin key) and Convex Cloud (deploy key; URL looked up automatically).
- 🔐 age encryption per workspace: decryptable offline with the standard
ageCLI. - 🗄️ S3-compatible destinations (Cloudflare R2, AWS S3, Backblaze B2, Wasabi, MinIO, RustFS) and local volumes.
- 🧱 S3 Object Lock: backups can't be deleted or overwritten for N days, not even with stolen credentials.
- 🧹 GFS retention (keep-last + daily/weekly/monthly), exports cleaned up on the source automatically.
- 🧾 Manifests: tables, row counts, files and SHA-256 per backup, compared with the previous one.
Restores
- ↺ Into the same or another instance, in four modes (full, replace, append, empty-only).
- ✂️ Partial restores: only the tables you pick; everything else stays untouched.
- 🕶️ Scrubbed staging copies: fake emails, names and phones; redaction; hashing; consistent within a restore.
- ⬇️ Download any backup, decrypted or as the raw
.agefile.
Operations
- 🧭 Topology map on the Overview: instances → jobs → destinations, colored by last-run health.
- ⌘K command palette: jump to any page, instance, job or destination, or run a quick action.
- 📈 Overview & storage dashboard: health per instance, growth chart, monthly cost estimate.
- ✉️ Branded emails built with React Email: workspace logo, accent colour and footer; templates you edit with variables and a live preview (owners can write full HTML); a per-email recipients grid (roles, specific members, outside addresses). Members can opt out; alerts cover failed/partial backups, no recent backup, restores and digests.
- 🔔 Chat alerts: Slack, Discord, Telegram, webhook.
- 🩺 Status URL for Uptime Kuma, Better Stack and UptimeRobot (200 healthy / 503 not).
- 🚀 REST API + GitHub Action: back up right before
npx convex deploy, with a per-workspace switch to turn API access off. - 🧭 Guided product tour on first sign-in (role-aware, replayable from the account menu).
- 🗑️ Trash & deletion policy: restore deleted items for N days, with separate toggles and delays for deleting their backup files from remote (S3/R2/B2) and local storage.
- 📜 Audit log of every action, exportable as CSV.
Teams
- 🏢 Workspaces, e.g. one per client, each with its own logo, timezone, keys, alerts and members.
- 👥 Roles: Owner · Admin · Operator (run and download, no restore) · Viewer (read-only).
- 🔑 Mandatory 2FA (authenticator or passkey), Google / GitHub / Microsoft sign-in, invite-only accounts.
- 🛡️ Session management, sign out everywhere, admin 2FA reset, login rate limiting.
app/ The console: TanStack Start · Better Auth · SQLite · React Email · shadcn/ui
backup-console.coolify.yaml Coolify template: the console (deploy once)
render.yaml Render Blueprint (Deploy to Render button); app/railway.json, app/fly.toml for Railway / Fly.io
convex-instance.coolify.yaml Coolify template: a self-hosted Convex instance (Postgres + RustFS), one per project
docs/ Guides (Coolify, Convex on Coolify, GitHub Action), images
testing/ Local Docker kit, Playwright e2e (45 + 8 onboarding checks), demo data
legacy/ The old all-in-one instance compose, kept for existing deployments
.github/ Image build (amd64 + arm64) and the convex-backup GitHub Action
cd app && pnpm install
BACKUP_MASTER_KEY=dev BETTER_AUTH_SECRET=dev-secret-at-least-32-characters-long \
SETUP_CODE=dev-setup pnpm dev # then open http://localhost:3000/setup
pnpm test # unit tests
pnpm typecheckThe end-to-end suite runs two real Convex stacks (Postgres + RustFS), an S3 stand-in, Mailpit and a
webhook receiver locally. See testing/local/README.md and the results in testing/TESTING.md.
Does it need Cloudflare? No. Cloudflare R2 is a great destination and Cloudflare Tunnel is an optional hardening layer, but neither is required.
What happens if I lose the console?
Your backups are plain (or age-encrypted) Convex snapshot zips in your own buckets. Decrypt them with the age key you saved and npx convex import them. The console is a convenience, not a dependency.
Can I restore into Convex Cloud? Yes, with a deploy key for the target deployment. Restores into a production Cloud deployment show an extra warning.
How big can a backup be? Exports are streamed to disk and uploaded in parts, so size is bounded by the container's free disk space, not memory.
Is Microsoft sign-in work-accounts only?
Set MICROSOFT_TENANT_ID to common (any account), organizations (work/school), or your tenant ID.
Does "Create bucket for me" need standing cloud credentials? No — the setup credential you paste in (an R2 API token or an AWS IAM access key) is used once, in memory, to create the bucket and a brand-new credential scoped to only that bucket. It's never stored, logged or shown again.
- Scheduled restore drills with automatic verification
- Backing up Convex environment variables alongside data
- Self-registration of new instances from the Convex template
- One-click template in Coolify's built-in catalogue (how to submit)
Issues and pull requests are welcome — start with CONTRIBUTING.md.
Found a vulnerability? Please report it privately as described in SECURITY.md.
Code: AGPL-3.0-or-later, from version 1.0.0-beta.3. Versions up to and including 1.0.0-beta.2
were released under the MIT licence and remain available under those terms (see their tags/releases).
- Self-hosting the console unmodified is free for any use, including commercial.
- If you modify it, you don't have to publish your changes to the world. But everyone who uses your modified version over a network (your team, contractors or customers) must be offered its source code (AGPL §13). The sidebar's source link points to this repository, so after modifying the console, point it at your own modified source.
- Commercial licences (to use the code without the AGPL's source-sharing obligations) are available from the
copyright holder — open an issue or see the contact in
SECURITY.md. - Contributions are accepted under the Contributor License Agreement, which keeps relicensing (including dual-licensing under terms other than the AGPL) possible.
Docs: the contents of docs/ and this README (including screenshots) are licensed under
CC BY 4.0. Code samples embedded in the docs remain licensed under the code licence above.
© Davide Del Gatto · Flying Web Solutions. Not affiliated with Convex, Inc. or Coolify.
















