Skip to content

Dev copies

A dev copy is a second, separate managed database that carries the same structure as one of your managed databases — the same tables and columns. It is where unfinished work runs: try a schema change, let a half-built workflow write test rows, break something on purpose, without any of it reaching the database your business actually runs on.

A copy is made one of two ways. The default carries structure only — the same tables, none of the rows — and does nothing at all to the production database. Copy with data duplicates the production data as well, which is what you want when a change only makes sense against real rows, and which costs enough that it is a separate button: see Copy with data below.

A dev copy is a real database in every respect. It has its own name, its own connection logins, its own migration history, and it counts toward your organization’s database limit.

Once a database has a dev copy, Ronja sends unfinished work to it for you. Nothing in the work itself changes — a workflow still names the production database, and you never edit it to point somewhere else. Ronja swaps the destination as the work runs.

Three kinds of work count as unfinished:

  • A workflow that is still a draft. Test runs of a workflow Ronja has built but not committed go to the dev copy. The moment the draft is committed — Draft promoted to live — the workflow runs against production again, including every scheduled run of it.
  • An app you have not published yet — if you ask for it. A draft preview carries a Use test data switch, and it starts off: the preview reads and writes the production database until you turn it on. Turn it on and that preview uses the dev copy instead. See Previewing an app on a dev copy below.
  • Anything you ask Ronja to run against the copy. In a chat, say so — “try that migration on the dev copy first”, “count the rows on the dev copy” — and Ronja sends that piece of work to the copy. There is no switch to set: she chooses the copy for the call she is making, and says which database answered.

Ask once and it holds for the work you asked about, not for the rest of the conversation. If you want the next thing on the copy too, say so — “and run that one on the copy as well”. It is worth being explicit for exactly the reason there is no switch: a setting left on quietly decides where later work goes, including work you never had the copy in mind for.

If the database has no dev copy, or the copy is not usable yet, Ronja stops and tells you rather than running against production instead. Create the copy first — or ask her to.

Every result says which database answered. When Ronja runs SQL or applies a migration against a dev copy, the result card in the chat carries a small dev marker; hover it and it names the production database the copy belongs to. A result with no marker came from production — the absence is the signal — so a conversation that mixes the two never leaves you guessing.

You can also work against the copy directly, the way you work against any managed database: through its own entry in the Databases list — where it sits under its parent, labelled Dev copy — or through its own connection logins, which are separate secrets carrying the same name as the production ones with (dev) on the end. A copy is the one kind of database that does not turn up in Search (⌘K) — only production databases do, so a copy can never be mistaken for the real one — which is why you reach it from its parent’s entry in the Databases list. Everything else in Managed databases — the console, the migration history, the API, ronja db — works on a copy exactly as it does on any other database.

While an app is still a draft — one Ronja is building, or one you are editing before publishing — the preview carries a strip above it with a Use test data switch and a line saying which data you are looking at.

The switch starts off every time, for every app. Off, the strip reads Previewing with LIVE data., and the preview reads and writes the production database exactly as the published app would. If the app is allowed to write, the strip says so in as many words — Previewing with LIVE data — this app WRITES. — because from there every button in the preview changes production rows. If the app can run workflows or Saved Agents, the strip adds it can run workflows and agents that may write: those run their own code with their own permissions, so they can change production data even when the app itself cannot.

Turn it on and the strip reads Previewing with test data — the Orders dev copy (with production data)., naming the copy it is using and whether that copy carries production’s data or only its structure. Ronja remembers your choice for that app as the starting position next time, and the strip always states where you actually are, so a remembered setting can never be something you have to guess at.

If nothing the app reaches has a dev copy ready yet, turning the switch on says so rather than pretending: Previewing with test data — but no dev copy is ready yet, so calls needing one will refuse, naming the database. Create a copy (below), wait for it to be Ready, and reload the preview.

Under the switch, a line tells you what test data is there to use: “Test data available for Orders. Customers has no dev copy.” It is there to save you a trip to the Databases page, not to stop you — the switch works either way, and a call that needs a copy that does not exist stops with an error naming the database when it runs. If the app also runs workflows or Saved Agents, the line ends with (approximate — workflows and agents this app calls are not traced): those run their own code and reach their own databases, and this line does not follow them.

  1. Open Databases and click the database you want a copy of.
  2. Find the Dev copy card and click Create dev copy — or Copy with data, which is the same thing plus the data, and which costs more than it looks.

Clicking the button starts the copy — it does not wait for it. Ronja provisions a new database and re-applies the original’s migrations to it, one by one, so both databases end up with an identical schema history. The card shows Provisioning while that happens and updates itself when the copy is Ready; a copy with data can take a while. The copy appears in the Databases list indented under its parent and labelled Dev copy, and the card links straight to it.

You can also ask Ronja for the copy in a Build mode chat — “Make me a dev copy of the leads database”, or “…with the production data in it”. The result in the chat names the copy and its state, so a copy still Provisioning is not mistaken for one you can use yet. It is the same copy the Dev copy card shows, made the same way.

Which of those two you ask for decides whether Ronja stops first. A structure-only copy — the plain “make me a dev copy” — is created straight away, with no prompt: it does nothing to production, so there is nothing to weigh up. A copy with the production data always stops and asks you to Approve or Reject before anything is created, because taking one disconnects the production database while it runs.

A database can have one dev copy at a time. Clicking Create dev copy again — or asking Ronja again — simply returns the copy you already have.

If something goes wrong the card shows Error and says what happened. A copy with production data is not retried on its own — remaking it would disconnect the production database again, which is not something to do without you asking — so use Refresh when you want another attempt.

What a structure-only copy carries — and what it does not

Section titled “What a structure-only copy carries — and what it does not”
Carried over Not carried over
Every table and column created by a migration Any row of data
The full migration history, step for step Tables created outside migrations
The same set of connection logins (read / write / admin) The logins’ passwords — new ones are minted
Custom logins, which cover named tables only

The second row of the right-hand column is the one to watch. Ronja creates tables by applying migrations, and those are what a dev copy replays. If somebody created a table by connecting with an admin login and running CREATE TABLE by hand, that table is not in the migration history and will not appear in the copy.

A custom login — one that covers only the tables you named — is not copied either, because what it grants is whatever it was created with and there is nothing to derive that from. Ask Ronja for one against the copy if you need it. Draft work that uses a custom login stops with an error rather than reaching production.

Replication is not available on a dev copy: a copy can be thrown away and rebuilt at any time, and a replication stream cannot survive that. Mirror the production database instead.

Copy with data makes the copy from the production database itself rather than from its migration history, so the copy comes out holding everything production holds: every table — including any created by hand outside a migration — and every row, as of the moment you clicked.

That makes it the right choice when a change only means anything against real data: a migration that rewrites existing rows, a query whose plan depends on real volumes, a workflow whose logic you cannot judge against an empty table.

Ronja states which kind each copy is. The Dev copy card and the copy’s own page banner label it With production data or Structure only; the Databases list shows With production data on a copy that has it, and nothing on a structure-only one.

Refreshing keeps the kind: refreshing a copy with data makes another copy with data — and disconnects production again — while refreshing a structure-only copy leaves production alone. To change the kind, discard the copy and create the other one.

When a schema change works on the copy, Promote to production moves it onto the production database. The button sits on the Dev copy card, next to Refresh, and appears once the copy is Ready — not while it is still being created, in error, or sitting in the trash.

  1. Click Promote to production. Nothing is applied yet.
  2. Read the plan. Ronja lists every migration the copy has that the production database does not, in the order they will run, each marked Will apply — a promote plan holds nothing else, because it is by definition the migrations production has never seen. If the two histories no longer line up you get a message in place of the plan; see When the two histories have drifted apart below.
  3. Click Promote.

The migrations run against the production database in one transaction — all of them, or none. Each is written into that database’s own migration history, so the console’s Migrations timeline shows them exactly as it would if you had applied them there in the first place.

You can promote from a chat instead: ask Ronja to promote the dev copy and she can show you the plan first — the same migrations, in the same order — before asking you to approve the promote itself. Nothing reaches production until you do.

Promoting twice is safe. A second promote finds nothing left to move and says so plainly: the copy has nothing production does not already have.

What promote moves — and what it does not

Section titled “What promote moves — and what it does not”

Promote replays your schema changes, not your data. Rows you inserted into the copy while testing stay on the copy, and a table somebody created by hand with an admin login is not in the migration history, so it is not promoted — the same limit that applies when a copy is first created.

If the production database has gained migrations of its own since the copy was made — someone applied a change straight to production — the two histories no longer line up, and Ronja stops instead of guessing. In place of the plan you get a message naming the migration where they part company, and the fix beside it: Refresh dev copy.

Refreshing rebuilds the copy from the production database’s current migrations, so the two line up again. It deletes the current copy immediately, along with everything in it, so re-apply your changes to the fresh copy and promote from there. See Refresh a dev copy below for what refreshing costs.

A migration being promoted cannot reuse a name the production database’s history already carries, or a name another migration in the same promote uses. Ronja stops and names the one to rename. Migration names are how a database recognises what it has already applied, so two migrations sharing one name would make later checks report the wrong file as changed. Rename it on the copy and promote again.

When the production database has moved on and you want the copy to match it again, click Refresh on the Dev copy card — or ask Ronja to refresh it in a chat, where she asks you to approve it first. Both do the same thing, including the part below.

Ronja then builds a fresh copy the same way the current one was made — from the production database’s current migrations, or, for a copy with data, from production itself, which disconnects it again (what that costs). Building it takes a while, and the card shows Provisioning until it is ready.

There is a brief moment during a refresh when the old copy is gone and the new one does not exist yet. In that moment, unfinished work behaves exactly as it does for a database with no copy at all: it runs against production, unless Require a dev copy for draft work is on for that database, in which case it stops with an error. Once the new copy exists and is still Provisioning, that work stops with an error until it is Ready.

Click Discard on the Dev copy card to move the copy to the trash. It stays recoverable from Governance › Trash until it is permanently removed.

While a discarded copy sits in the trash, the Dev copy card says so — and unfinished work that would have used the copy will not run. Restore it, or create a new copy — the discarded one stays in the trash until it is purged.

That is deliberate, and it applies to a copy that is still being created or that failed to build too. A copy that is halfway to existing is exactly the moment you are actively testing, so Ronja stops the run with an explicit error naming the copy’s state rather than quietly sending the work to production instead.

A dev copy also goes when its production database does. Deleting the production database leaves the copy alone at first — but when the production database is permanently removed, its copy is dropped with it. The Delete database confirmation names the copy so you know what goes.

The Dev copy card carries one setting: Require a dev copy for draft work.

Leave it off and unfinished work simply uses the production database when no dev copy is available. Turn it on and that work fails instead — an explicit error rather than a quiet write to production. Turn it on for the databases where an accidental write would matter.

The same loop runs from the CLI, which is what lets a dev copy sit in a pipeline rather than only in the browser: making a structure-only copy, refreshing it, using it, and promoting it all work from a script. Only a copy with the production data is browser-or-chat work — making one or rebuilding one — because taking one disconnects production. Discarding stays on the Databases page. Add --env dev to send a command to the copy:

Terminal window
ronja db migrate status --env dev
ronja db migrate push --env dev
ronja db sql <database ID> "SELECT count(*) FROM leads" --env dev

You always name the production database. You never pass the copy’s own ID — Ronja resolves the copy for you — and there is no second migration folder: the same .sql files apply to the copy and to production, which is the whole point of trying them on the copy first.

If the database has no dev copy, --env dev fails and says so, rather than quietly running against production. Create the copy first. Whenever a command does reach the copy, it says which database answered on the error stream, above its own output, so a result from the copy is never read as a result from production.

When the copy is where you want it, promote:

Terminal window
ronja db promote <database ID> --dry-run
ronja db promote <database ID> --yes

--dry-run reports what would be promoted — the same plan the console shows — and changes nothing. The real promote asks you to confirm before anything is applied; --yes skips that prompt, which is what an automated job needs. Either way the migrations the copy has past production are applied to production in one transaction. ronja db promote exits with a non-zero status when the two histories have drifted apart, so a pipeline stops there instead of carrying on — refresh the copy and re-apply your changes, as above.