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

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
- Migrating from Coolify, Dokploy or CapRover
- Importing apps from a repo, image or compose file
- The full comparison with Coolify, Dokploy, CapRover, Dokku and Kamal
- New to Levelrail? Start with the five minute install
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.
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.