Your app has real users now. Their accounts, their saved work, maybe their payments all live in one database. And the same AI agent that builds your features can also run commands against that database.
One bad command, like a migration that drops a table or a "reset" pointed at the wrong project, can erase data you cannot get back. Git can undo code mistakes. It cannot undo lost user data.
This guide fixes that in an afternoon: a backup you control, proof that it restores, a separate database for the AI to experiment on, and settings that keep your agent away from production. The examples use Supabase, which many Lovable, Bolt, v0, Cursor and Claude Code apps run on, with notes for Lovable Cloud and Replit.
Quick glossary
- Production (prod) database: the one your live app and real users use.
- Dev database: a separate copy for building and testing. If it breaks, nobody loses anything.
- Restore: loading a backup back into a database. A backup you have never restored is a guess.
-
Migration: a small SQL file that changes your database structure, like "add a
plancolumn toprofiles". -
Destructive change: anything that deletes data or structure, like
drop tableortruncate.
Step 1: Find out what safety net you already have
Here is what the official docs say, as of October 2026:
-
Supabase Free plan: no daily backups you can rely on. Supabase's backup docs tell free projects to "regularly export their data using the Supabase CLI
db dumpcommand and maintain off-site backups." - Supabase Pro / Team / Enterprise: automatic daily backups, kept for 7 / 14 / up to 30 days, under Database > Backups. Point-in-Time Recovery (restore to an exact minute) is a paid add-on.
- Lovable Cloud: one backup per day, about 14 days kept, under More > Database > Manage > Backups. You can also export the whole database (once every 24 hours).
- Replit: separate development and production databases. Replit's docs say Agent "can't touch" the production database, and the development database can be rolled back to a checkpoint.
Three gotchas apply almost everywhere:
- Files are separate. Supabase and Lovable both say database backups do not include files in Storage (uploads, avatars, PDFs).
- Deleting the project deletes the backups. Supabase says deleting a project permanently removes its data, "including any backups."
- Restores roll back everything. You lose every change made after the backup, and Supabase notes the project is unavailable during the restore.
Ask yourself: "If my database disappeared right now, what is the newest copy I could get back?" If the answer is "none" or "not sure", do Step 2 today.
Step 2: Take a backup you control, today
On Supabase, use the CLI commands from Supabase's own backup-and-restore guide. You need the Supabase CLI and Docker Desktop installed (the CLI runs the dump inside Docker). Get the connection string from the Connect button on your project dashboard. Supabase recommends the Session pooler string by default.
Save this as backup.sh in your project folder:
#!/usr/bin/env bash
# Backs up roles, schema and data into a dated folder.
# Usage: DB_URL="your-session-pooler-connection-string" ./backup.sh
set -e
STAMP=$(date +%Y-%m-%d_%H%M)
mkdir -p "backups/$STAMP"
supabase db dump --db-url "$DB_URL" -f "backups/$STAMP/roles.sql" --role-only
supabase db dump --db-url "$DB_URL" -f "backups/$STAMP/schema.sql"
supabase db dump --db-url "$DB_URL" -f "backups/$STAMP/data.sql" --use-copy --data-only -x "storage.buckets_vectors" -x "storage.vector_indexes"
echo "Backup saved in backups/$STAMP"
Then keep it out of Git, because these files contain your users' data:
chmod +x backup.sh
echo "backups/" >> .gitignore
The connection string contains your database password. Never paste it into a public repo, a screenshot, or an AI chat.
On Lovable Cloud, go to More > Database > Manage > Settings, click Export data, then Export and Start export. Lovable emails you when it is ready and saves it to your project's Storage. Download it and keep it somewhere outside Lovable, since its docs warn that removing the database deletes the export too. Download important Storage files separately from More > Storage.
Step 3: Prove the backup works with a test restore
On Supabase, create a new empty project to restore into. The Free plan allows 2 active projects, so this can be free. Install psql (the Supabase guide has instructions for Mac and Windows), then run the restore command from that guide, using the new project's connection string:
psql \
--single-transaction \
--variable ON_ERROR_STOP=1 \
--file roles.sql \
--file schema.sql \
--command 'SET session_replication_role = replica' \
--file data.sql \
--dbname "PASTE-THE-NEW-PROJECT-CONNECTION-STRING"
Run it from inside the dated backup folder. Then compare row counts for your most important tables in both projects, using the SQL editor. Replace the table names with your own (profiles and projects are just examples):
select 'profiles' as table_name, count(*) from public.profiles
union all
select 'projects', count(*) from public.projects;
If the numbers match, you have a real backup. If the restore errors out, the Supabase guide has a troubleshooting section (for example, for errors mentioning supabase_admin), plus an extra key-copy step if you use Supabase Vault.
Now delete this test project, because it holds a full copy of real user data. Create a fresh one and restore only roles.sql and schema.sql (leave out the --command and data.sql lines). That empty copy becomes your dev database in Step 4.
Step 4: Give the AI its own database to break
The safest rule in this whole guide: the AI builds against a dev database, never against production.
-
Supabase (with Cursor, Claude Code, Bolt, v0): use two projects, for example
myapp-devandmyapp-prod. Your local.envpoints at dev. Production keys live only in your hosting dashboard (Vercel, Netlify, and so on). Supabase also offers a local database (supabase start) and paid Branching. - Replit: already split for you. One catch from Replit's docs: when you publish, structure changes Agent made to the development database, including deleted columns or tables, are applied to production. So check what changed before you hit Publish.
- Lovable Cloud: since October 5, 2026, Lovable runs every Cloud project on a single database. Its migration guide puts it plainly: "The data you see while building is the data your published app uses." Drafts preview code changes, not a second database. So lean on Steps 2, 3 and 7, test with obviously fake accounts, and keep the Step 5 setting. For a true dev/prod split, Lovable lets you connect your own Supabase project instead.
Habit: before anything touches data, check which project ref is in your connection string or .env.
Step 5: Make every database change a reviewed migration file
Raw SQL fired off by an AI leaves no record. Migration files give you something to read before anything runs. With the Supabase CLI:
# 1. Create a new, empty migration file
supabase migration new add_plan_to_profiles
# 2. Let the AI (or you) write the SQL into that file, then test it locally.
# "db reset" here rebuilds your LOCAL database only.
supabase db reset
# 3. Compare local and remote migration history
supabase migration list
# 4. See exactly what would be applied, without applying it
supabase db push --dry-run
# 5. Only after a fresh backup (Step 2), apply it
supabase db push
Before step 5, read the migration file. You are not checking every line, just scanning for red-flag words:
drop table drop column truncate
delete from ... (with no where) alter column ... type
rename db reset --linked db push --include-all
migration repair
Two deserve a special warning. Supabase's CLI docs say db reset with --linked or --db-url runs a script that drops all user-created entities in the remote database. And if migration list shows lots of local migrations with no remote match, stop: a push may try to apply old files you did not expect. One r/Supabase post from April 2026 describes exactly that, an agent running db push that replayed old migrations, including DROP TABLE, against a live project.
For destructive changes, prefer an additive version: add the new column, copy the data, switch the app over, and remove the old column later, after a backup.
On Lovable Cloud, its docs say Lovable refuses changes like dropping or renaming a table or column, or changing a column's type, and adds to the database instead. That behavior is controlled by the agent permission Execute backward incompatible database migrations. Leave it at the default, or set it to Ask each time so you see the SQL first. Avoid Always allow.
Step 6: Take away the AI's keys to production
Prompt rules shape what the AI tries. Settings decide what it can do. Use both.
Supabase MCP server. Supabase's docs offer two URL options: project_ref (limit it to one project) and read_only=true (run queries as a read-only Postgres user). Scope it to dev:
https://mcp.supabase.com/mcp?project_ref=YOUR_DEV_PROJECT_REF
If the AI must look at production, use a separate read-only connection:
https://mcp.supabase.com/mcp?project_ref=YOUR_PROD_PROJECT_REF&read_only=true
Claude Code. Add deny rules to .claude/settings.json in your project. Claude Code enforces these itself, whatever the model decides:
{
"permissions": {
"deny": [
"Bash(supabase db push *)",
"Bash(supabase db reset *)",
"Bash(supabase link *)",
"Bash(npx supabase db push *)",
"Bash(npx supabase db reset *)",
"Bash(psql *)"
]
}
}
The npx lines matter: Claude Code's docs explain that a rule only matches the command as written. The same docs say deny rules are not a full security boundary, so the dev project and read-only MCP still matter.
Cursor. In Settings > Agents > Approvals & Execution, avoid Run Everything, which runs every command without approval. Auto-review or Allowlist are safer, but Cursor's docs say neither is a security boundary.
Everyone. Add these rules to your AGENTS.md, CLAUDE.md, or Cursor rules file:
Database safety rules:
- Never run commands against the production database.
- Never run: supabase db push, supabase db reset --linked, migration repair,
or any DROP, TRUNCATE, or DELETE without a WHERE clause.
- All schema changes go in a new file in supabase/migrations/. Stop after
writing it and tell me what it does in plain English.
- Flag anything that deletes data, drops or renames columns, or changes types.
- Never ask me to paste database passwords or connection strings into chat.
Step 7: Automate backups and schedule a restore drill
-
Supabase Free: Supabase has an official guide, "Automated backups using GitHub Actions", with a workflow that runs the same three
db dumpcommands every day at midnight (UTC, since GitHub schedules use UTC). It reads the connection string from a GitHub secret namedSUPABASE_DB_URLand commits the dump files. Supabase's guide warns: "Never backup your data to a public repository." Use a private repo dedicated to backups. - Supabase Pro: daily backups are automatic. Still export weekly, because deleting the project deletes its backups.
- Lovable Cloud: daily backups are automatic. Export before any big change, and download it.
Then add a monthly calendar reminder, "Restore drill": restore the latest backup into a throwaway project (Step 3), compare row counts, then delete it. On the Free plan's 2-project limit, pause your dev project first. It is the only way to know your backups still work.
To set up the workflow, paste this into your AI tool:
Read Supabase's guide "Automated backups using GitHub Actions"
(supabase.com/docs/guides/deployment/ci/backups). Create the periodic backup
workflow from that guide in .github/workflows/backup.yml, but trigger it only
on schedule (daily) and workflow_dispatch, not on push or pull_request.
Do not put any connection string in the file. Tell me exactly which GitHub
secret to create and confirm the repo must be private. Do not run anything.
Common mistakes
- Trusting a backup you never restored. Do the Step 3 test once and the monthly drill after that.
- Forgetting Storage files. Database backups do not include uploads. Download them separately.
-
Running
db resetwhile linked to production. Locally it is harmless. With--linkedit wipes the remote database. - Saving backups in a public repo. Backups contain emails and personal data.
- Restoring to an old backup without exporting first. A restore erases everything after that point, so export the current state before you roll back.
Your action summary
Today:
- Answer the Step 1 question: what is the newest copy of my database I could get back?
- Run
backup.sh(or Lovable's export) and addbackups/to.gitignore. - Restore that backup into a second project and compare row counts.
This week:
- Point your AI tools at the dev project only.
- Make database changes through migration files, read them, run
db push --dry-run, and back up before every push. - Add the Claude Code deny rules, a scoped MCP URL, safe Cursor settings, and the rules block above.
- Automate nightly backups in a private repo and put a monthly restore drill on your calendar.
None of this slows you down. It just means that the next time an AI agent gets too confident with your database, you lose a few minutes instead of your users' data.
Top comments (1)
I'd put
auth.usersin the Step 3 row count. Your users come over in data.sql, so that's the count that says people can still log in after a restore, and profiles can match while it's short.The other reason to count every table, not just two: a cut-off data file restores without an error. We cut a plain data dump exactly between two tables and replayed it with this same psql command, single transaction and
ON_ERROR_STOP. Exit code 0. The first table was complete and the second was empty, because to psql a file that ends after a table looks like a file that's finished. Cutting it in the middle of a row also exited 0, with about half the rows. So matching counts on the tables you picked don't prove the rest made it.