How do I move from Supabase to a database hosted with Hodi?
AI-assisted development tools (Lovable, Bolt, v0, Cursor, Claude Code…) almost always default to Supabase for the database if you don't give them specific instructions. That's convenient to get started, but it ties you to an external service, hosted outside your jurisdiction, with yet another subscription to manage.
Good news: Supabase is just PostgreSQL. And your Hodi hosting includes PostgreSQL. So you don't have a migration to do, but a move, which changes everything.
The AI that wrote your application can handle it, as long as you steer it properly. Here's how.
Just starting your development?
The simplest option is to never go through Supabase at all. From your very first prompt, ask your AI to use a database on your cPanel directly, PostgreSQL or MySQL. You'll save yourself everything that follows.
I'm developing this application and will host it on Hodi cPanel hosting.
It will be deployed with Hodifly. Do not use Supabase, or any external
database service.
Use directly:
- a PostgreSQL 13 database (or MySQL/MariaDB) hosted on the same server,
accessible only on localhost;
- the pg driver (node-postgres) for PostgreSQL, or mysql2/promise for MySQL,
with a connection pool;
- parameterized queries only, never concatenated SQL;
- a server layer that authenticates the user and checks their permissions on
each route: the database is never called from the browser;
- credentials read from environment variables
(PGHOST, PGDATABASE, PGUSER, PGPASSWORD);
- for uploaded files: a persistent folder whose path comes from the
UPLOAD_DIR variable (ex. /home/myaccount/files/), located outside the web
root and never inside the project folder, which is replaced on every deployment.
Target PostgreSQL 13: do not use any syntax introduced after this version.
The rest of this article is for those whose application already uses Supabase.
The moving prompt
Copy this prompt into your development tool (Claude Code, Cursor…), with your project open. It contains everything: your hosting constraints and the order of operations.
This application uses Supabase. I want to host it on my Hodi cPanel hosting,
with a PostgreSQL database and automatic deployment via Git (Hodifly).
I have neither Supabase nor an external service.
Architecture constraints
- PostgreSQL 13, accessible only from the server (localhost).
Do not use any syntax introduced after version 13.
- The database is never called from the browser: write a server layer (API)
that authenticates the user and checks their permissions on each route.
- pg driver (node-postgres) with a connection pool, parameterized queries only.
- Database credentials via environment variables: PGHOST, PGDATABASE, PGUSER, PGPASSWORD.
- Uploaded files: a persistent folder whose path comes from the UPLOAD_DIR variable
(ex. /home/myaccount/files/), outside the web root, and never inside the project folder,
which is fully replaced on every deployment.
- gen_random_uuid() is available natively, you can keep it.
Proceed in this order, showing me your work at each step:
1. Inventory. List everything that depends on Supabase (file + line): queries, auth,
storage, real time, access policies (RLS), keys. For each access policy,
write the business rule it expresses. Specify what is called from the browser.
2. Plan. Propose the target architecture and explain the trade-offs.
3. Schema. Adapt the schema to PostgreSQL 13. Remove anything Supabase-specific
(internal schemas, anon/authenticated/service_role roles, RLS policies).
Keep types, constraints, indexes, and foreign keys.
4. Data access. Replace all supabase-js calls with the server layer.
5. Auth and authorization. Replace Supabase Auth (passwords hashed with argon2id,
httpOnly cookie sessions). Translate each access policy from step 1 into a
server-side check, centralized in a middleware. Filtering must be
in the SQL query (WHERE user_id = $1), not after the fact. Give me a table:
for each route, who reads, who writes, which check enforces it.
6. Files and finishing touches. Replace Supabase Storage per the UPLOAD_DIR constraint.
Propose a realistic alternative to real time and tell me what's lost.
Remove Supabase dependencies from package.json.
7. Verification. Go back to the inventory from step 1 and check off what's actually been handled.
Give me the tests to run, including: a user cannot read another user's data,
and an uploaded file survives a redeployment.
The rest of this article explains the reasoning behind these constraints, and what you need to do on your end.
What carries over, and what's left to do
Supabase isn't just a database: it's a set of services built around PostgreSQL. The database moves over almost as-is; the rest takes some work.
What Supabase provides | With Hodi |
|---|---|
PostgreSQL database | ✅ Moves over almost identically |
| ✅ Identical |
Constraints, indexes, foreign keys | ✅ Identical |
Auto-generated API / | ⚠️ To be replaced with a server layer |
Auth (accounts, login) | ⚠️ To be reimplemented server-side |
Access rules (RLS policies) | ⚠️ To be translated into checks in your code |
Storage (files) | ⚠️ To be switched to the file system |
Realtime | ⚠️ Rarely transposable as-is |
And in practical terms for you? Your schema, your data, and your types transfer without conversion. Most of the work is focused on the server layer: data access, authentication, and authorization.
Why a server layer?
Many AI-generated applications call Supabase directly from the browser. This is possible because Supabase exposes an HTTP API in front of PostgreSQL, not the database itself.
In your setup, this API no longer exists. A database should never be exposed to the browser. So you need a small API that receives requests, authenticates the user, checks their permissions, and then queries PostgreSQL. It's this layer that now carries your application's security, route by route.
If your application was a static site talking to Supabase, it becomes a Node or Python application, which requires Site Pro hosting. Plan for this before you start.
Creating your database in cPanel
You create your database yourself, in just a few clicks. No need to send us a request.
- In cPanel, under the Databases section, open PostgreSQL Databases (or MySQL Databases).
- Create a database, then a user with a strong password.
- Link the user to the database by granting them privileges.
- Enter the credentials in Hodifly → Environment Variables.
One thing to know: cPanel automatically prefixes names with your account. A database named app becomes myaccount_app, a user api becomes myaccount_api. Use short names, and enter the full names in your configuration.
To import your schema and inspect your data, use phpPgAdmin (PostgreSQL) or phpMyAdmin (MySQL), from cPanel.
Your Hodifly environment variables are stored on your own server, encrypted at rest, and never sent anywhere else.
Where to store files uploaded by your users
This is the point people often discover too late, once the files are already lost.
Hodifly republishes your site on every push, and a restore replays an earlier version. The published folder is therefore replaced, not modified. Anything your application had written there disappears on the next deployment. The problem is sneaky: locally, everything works; in production, the files vanish on the next push.
Files uploaded by your users are data, just like your database. They need to live in a persistent location:
- Create a
filesfolder at the root of your personal space:
/home/myaccount/files/(replace myaccount with your cPanel account name). This location is outside the web root, which is /home/myaccount/public_html/, and outside the folder published by Hodifly. It is therefore never replaced by a deployment, nor directly accessible from the Internet.
- Declare it in Hodifly → Environment Variables:
UPLOAD_DIR=/home/myaccount/files/- In the database, store only the filename and its relative path. The content stays on disk.
- Serve files through an application route that checks permissions, never through a direct link.
- Add this folder to
.gitignore.
What about your backups? Since these files are on your hosting, they follow your plan's backup policy, just like the rest of your account.
The definitive test: upload a file, make an unrelated git push, then check that it's still there.
Moving your existing data
- From Supabase, export the schema and the data separately (
pg_dump --schema-only, thenpg_dump --data-only). - Supabase runs on a version of PostgreSQL newer than 13, and
pg_dumpis designed to move forward, not backward. So have your AI adapt the schema (step 3 of the prompt) before running it. - Then import the data alone. This is the part that transfers best: same types, same formats, no conversion.
- If an import fails, fall back on a per-table CSV export from the Supabase dashboard.
- Check the row counts table by table before switching over.
Mistakes the AI makes
- It declares victory too soon. Ask again: "Go back to the inventory from step 1 and check off what's actually been handled. What's left?"
- It forgets authorization checks. This is the main risk. Demand the route-by-route table, then test it: log in as user A, try to access a piece of data belonging to user B.
- It filters client-side instead of in the query. Data sent to the browser and then hidden from display is still data that was sent. Filtering must be in the
WHEREclause. - It writes files inside the project folder. The costliest mistake: everything works locally, then the files disappear on the next deployment.
- It leaves hardcoded credentials. Ask: "Find any secret or password that's hardcoded and move it to an environment variable."
- It uses syntax that's too recent for PostgreSQL 13.
Checking before going live
- No occurrence of
supabaseremains in the code (grep -ri supabase .). - A logged-in user can only access their own data. Actually test this.
- All sensitive routes reject a user who isn't logged in.
- Passwords are hashed, never stored in plain text.
- Database credentials live only in environment variables.
- Row counts match those in Supabase.
- An uploaded file survives a redeployment.
What if I prefer MySQL?
We also support MySQL/MariaDB, and it's a perfectly legitimate option. It's the most widely used engine in the world: more documentation, more examples, and so often better handled by AI tools. If your assistant seems more comfortable with it, or if it's what you're familiar with, go for it without hesitation. Just adapt the prompt above (MySQL/MariaDB database, mysql2/promise driver).
The trade-off, to be honest: you're leaving PostgreSQL, so it takes a bit more time. You'll need to convert the schema, remap the types (uuid, jsonb, timestamptz, and arrays have no direct equivalent), rewrite the queries, and import the data via CSV.
How to choose? For the fewest changes, stick with PostgreSQL: your Supabase schema works as-is. For a more common engine that's more familiar to your AI as well as your developer, MySQL works very well, with one extra conversion step.
Updated on: 17/07/2026
Thank you!