Back to writing
Original post by Gagan Deep Singh, canonical on thegdsks.comAlso on Dev.toDiscuss on Dev.to

Migrate from Coolify, Dokploy or CapRover Without Touching the Old Server

Migrate from Coolify, Dokploy or CapRover Without Touching the Old Server Migrations go...

5 min read

Migrate from Coolify, Dokploy or CapRover Without Touching the Old Server

Migrations go wrong when the tool changes the thing you are leaving. The importer in Levelrail follows the opposite rule: it only reads from your old platform. The source keeps running the whole time, so a bad import costs you nothing but a cleanup.

This guide walks the whole path, including the parts that do not migrate. The list is long, and learning that in week two costs more than learning it now.

TL;DR

Step What happens Touches the old server?
1 Make a read token on the old platform Read only
2 Run a dry run Read only
3 Apply the import Read only
4 Move data by hand Reads dumps
5 Switch DNS when the new app is healthy No

How the importer behaves

It reads a live Coolify, Dokploy or CapRover instance through that platform's own HTTP API. It never stops, edits or deletes anything there. A test asserts it sends nothing but GET requests, apart from CapRover's single login POST.

You can run it from the dashboard under Settings, Import from another platform, from the CLI, or over the API. All three use the same two routes.

Step 1: get a token

Platform Credential Notes
Coolify API token Use read:sensitive to get real env values
Dokploy API key Sent as an x-api-key header
CapRover Login password Exchanged for a session token

The token travels to the Levelrail control plane in the request body. The control plane holds it in memory only. It never stores it, logs it or writes it to the audit log, and it scrubs the token from error text. Put it in an environment variable instead of the command line so it stays out of your shell history:

export APP_IMPORT_SOURCE_TOKEN=your-token

Step 2: always dry run first

levelrail-cli import platform coolify --url YOUR_COOLIFY_URL --dry-run

Swap coolify for dokploy or caprover as needed. The dry run reads the source and prints one row per item with a status. It creates nothing.

Status What it means
Ready Maps cleanly, nothing for you to do
Needs attention Gets created, with a caveat and a suggested manual step
Not supported Cannot import, with the reason and what to do instead

Read every Needs attention and Not supported row. That list is your real migration plan.

The control plane refuses private addresses by default, because an import URL is an outbound request an operator controls. Allowing it takes two opt-ins together. The operator sets APP_IMPORT_ALLOW_PRIVATE_NETWORKS=true on the control plane, and the request adds --allow-private. Either one alone gets a 400.

Cloud metadata and link-local addresses stay blocked even with both opt-ins. No legitimate use points an importer at an instance metadata service.

Step 3: apply it

Import everything, or a subset:

levelrail-cli import platform coolify --url YOUR_COOLIFY_URL --only web,api

Apply is safe to repeat. Every imported app carries a label with its source id, and a re-run skips apps it already made. If it fails halfway, run the same command again and it retries only the unfinished items. The command exits non-zero when anything failed, so a script can retry.

If a name is already taken, the default adds a numeric suffix. Pass --collision skip to leave it alone instead.

What moves and what does not

This table is the honest core of the post.

Moves Arrives as
Apps An app with image or git source, port, build method
Env vars Plain env, or encrypted secrets when the name looks sensitive
Domains Declared on the app
Volumes Created empty
Databases Created empty (Postgres, MySQL, MariaDB, MongoDB, Redis from Coolify and Dokploy)
Does not move What to do
Database contents Dump the source, restore with the backup tools
Volume contents Copy the data across before starting the app
Git-built apps Arrive with a pending image, so you connect the repo and run a build
CapRover images CapRover builds locally, so push to a registry or connect a repo
Compose stacks and one-click services Reported unsupported, deploy the compose file separately
Certificates, DNS, history, logs, metrics Re-point DNS only after the new app is healthy
Cron jobs The report lists schedule and command so you can recreate them

Secret-looking names (password, token, key, credential, DSN and similar) go through the secrets manager. Coolify redacts values unless the token has read:sensitive, and a secret that comes back empty is skipped and flagged.

Step 4: finish by hand

The importer lists what remains. A checklist beats memory:

  • Read every Needs attention and Not supported row
  • Restore database dumps and copy volume data
  • Connect repositories and trigger builds
  • Confirm secrets landed on each app
  • Check domains
  • Re-add health checks and cron jobs
  • Switch DNS last

Keep DNS pointing at the old server until the imported app is healthy.

Undoing an import

Nothing on the source changed, so nothing needs undoing there. To remove what the import created, list the apps and databases from the apply report, then delete them:

levelrail-cli apps delete your-app

Delete the databases the report listed and any now-empty projects.

What to check before trusting it

Levelrail is beta. The importer covers apps, databases, env vars, domains and volumes, but it cannot read inside your data. Treat the dry run as a plan, rehearse a database restore, and keep the old platform alive through a full business cycle.

Live Dokku import over SSH does not exist. Code can map a Compose file and a Dokku environment dump, but the API, CLI and dashboard do not expose those two yet.

What would stop you from migrating today: the empty database step, the stack of manual items, or the beta label?

Go deeper

Try it, and tell me what breaks

Levelrail is open source under Apache 2.0. It is young, so every bug report changes what gets built next.

glincker/levelrail

If this post saved you time, a star on the repo helps other self-hosters find it. Bugs and feature requests go in the issue tracker.


GDS K S · thegdsks.com · building Glincker · follow on X @thegdsks

The safest migration is one where the old server never knows it happened.

This article is published here first and cross-posted to Dev.to. This page is the canonical version.
Contact