You own the server, the code and the data. Here is what the first ten minutes look like.
Why the default path gets annoying
You rent a backend and it works. The annoyance shows up later: on app number two, when you see what a second project costs, and on the day you want to leave and find out what you can take with you.
The database is yours in theory. In practice the export is a job you write yourself, the users sit in someone else's auth format, and the files live in a bucket you do not control.
That is a trade, not a scandal, and it is worth seeing what the other side of it looks like. This post walks through one: a backend you create on your own machine, deploy to a server you rent, and can leave with one command. I build CrescoDB, so that is the tool in the examples, and I will say where it stops.
Create the project
You need Node 20 or newer.
node --version Create the project:
npm create crescodb@latest my-app This writes the project into my-app, installs the CLI inside it, starts it and opens the dashboard. The dashboard asks you to sign in to a CrescoDB account. The account is free and needs no card.
Now add auth. Move into the folder and run the generator:
cd my-app npx cresco create auth That command writes the auth code into your project as plain files: register.js, login.js, middleware.js and jwt.js, wired to the project database. Passwords are hashed with scrypt and sessions use JWT. Next to them sit schema.cresco, which describes your tables, and cresco.db, the SQLite file.
It is a normal Node project. Open it in an editor and read the login route. Nothing in it is hidden behind a package you cannot open.
Run it
npm run dev The API starts on port 3000 and the dashboard is at http://localhost:3000/studio. The database is SQLite, so there is nothing to install and no Docker.
For the store starter, the project's own page shows this output:
✓ schema.cresco · cresco.db · store starter ✓ shop is ready. REST API on :3000 Dashboard opened, connected to shop Your app is served too. cresco dev serves your project folder at http://localhost:3000/app and reloads the page when you save, so the frontend sits next to its API.
The schema
Everything comes from one file. A model looks like this:
{ "name": "Order", "fields": [ { "name": "id", "type": "uuid", "primaryKey": true }, { "name": "total", "type": "number", "required": true } ], "access": { "read": ["authenticated"] } } From that, every model gets create, read, update and delete over REST, with paging and filtering:
GET /orders POST /orders PATCH /orders/:id DELETE /orders/:id The same schema drives the rest. Auth has four roles: guest, user, mod and admin. A model can declare a rowOwner column so users only see and change their own rows. Files go through bucket storage at /storage, and row changes stream to connected clients over server-sent events.
Tables are private by default. A model with no access block needs a signed-in user to read it. Public has to be something you type on purpose.
The engine underneath is SQLite by default. Point the project at PostgreSQL or MySQL and schema.cresco and the API stay the same, so there is no rewrite between SQLite on your laptop and Postgres on the server. One caution: the first switch off SQLite carries your schema over, but moving data between engines later is an export and an import.
Before anything ships, run the audit:
npx cresco audit It reads the schema in your repo and answers one question: who can read each table. A public table holding an email column is critical. A public price list is a note. It exits non-zero on a critical finding, so it also works in CI.
Deploy
You need a server. Any Ubuntu or Debian VPS works. Hetzner, DigitalOcean and Vultr all rent a small one for about $5 a month.
In the dashboard, go to Servers & Apps and connect a server. Give it a name like web-1. The dashboard shows one command with your token and key filled in. Paste it into the server:
curl -fsSL .../connect.sh | sudo bash -s -- --token ... --url ... --pubkey '...' It installs Docker if the box has none, and creates a separate cresco user with no sudo access. It never asks for your root password. The token works once and stops working when the server checks in.
Then, from your project folder:
npx cresco login npx cresco deploy --server web-1 cresco deploy runs the access audit first and refuses to continue on a critical finding. If it passes, the project is sent to the server, built there, started and health-checked. The app gets a free address under crescoafrica.com with HTTPS. For your own domain, the dashboard shows the DNS record to add and checks it for you. If the record sits behind Cloudflare, turn the orange cloud off so it is set to "DNS only", or certificate issuing fails. HTTPS comes from Caddy and Let's Encrypt, and it renews on its own.
Rollback is built in. The previous version stays on the server. If a new build fails, the old one keeps serving. If it builds but fails its health check, you are told why, and Roll back in the dashboard puts the previous build back.
What you still own after deploying
Run this in the project folder:
npx cresco eject It writes a folder that runs your project without CrescoDB:
myapp-ejected/ schema.cresco data.sql vendor/ the engine, packaged into the folder Dockerfile docker-compose.yml README.md data.sql holds every table and every row as plain SQL, and psql, mysql and sqlite3 all load it. To run it:
cd myapp-ejected docker compose up --build Two limits are worth stating. The bundle is not air-gapped: Docker still pulls a Node base image and npm still fetches ordinary packages, like any Docker build. And two things need changing before you put it anywhere real: the database password in docker-compose.yml is a placeholder, and CRESCO_AUTH_SECRET must be set if the project uses auth. If the data export fails, eject writes nothing and tells you why, so you never end up holding a bundle with an empty data.sql.
What this is not
The server is yours, so you are the operator. You pick it, you pay for it, and its disk, its updates and its uptime are your responsibility. CrescoDB reads CPU, memory and disk from the box and alerts you, but it does not turn a rented VPS into a managed service.
It also does not mean none of your data touches CrescoDB. Project state and backups sit on CrescoDB infrastructure. Your live app and its database run on your server. What leaves it is the backup: each automatic snapshot is encrypted on your server with AES-256 before upload, then stored in Cloudflare R2, with the key held encrypted on our side. A restore check loads the latest backup into a scratch database on your server and counts what came out before the copy is marked verified. Automatic backups and restore checks are part of the paid plans. The free plan has none, and you can run a restore by hand.
If you stop using the service, the app keeps running and your database and code stay with you. You lose the backups and the alerting.
One thing you can do in the browser
If you only want to check one thing before installing anything, run the free access audit on the CrescoDB site. It needs no account. It reads your table rules and tells you which tables anyone can read. I put the link in the first comment.
If you try any of this, I would like to hear where it broke, because that is the part I cannot see from my own machine.
