Move from Coolify
BetaRead an existing Coolify installation over its API and recreate it in Deplo, with the data copied across.
Beta
This feature is currently beta/experimental and subject to change. It is not recommended for production. Try it out and share your feedback on our GitHub.
A wizard reads your Coolify installation over its API and recreates it here: projects, applications, one-click services, databases, domains, variables, volumes and backup destinations, with the data copied across. The configuration comes over the API; the data is read by a Deplo agent installed on each Coolify server and relayed through the control plane. Pick the tab that matches where Deplo is going: onto the machine Coolify already runs on, or onto a machine of your own.
Before you start
Turn the API on and mint a token
Coolify ships with its API switched off on some installs. If it is off, the scan says so in Coolify's own words: API is disabled. Turn it on under Coolify's Settings. If the API is restricted to a list of addresses, add the machine Deplo runs on, or the refusal reads You are not allowed to access the API.
Then: Keys & Tokens, API tokens. Tick root, and mint it from an account that is an
admin or owner of the team. Root is one box and it covers both things a migration needs,
reading your values and stopping a service before its data is copied.
If you would rather not grant root, tick deploy first, then read and
read:sensitive - in that order, because Coolify's dialog clears the list the moment deploy
is ticked. Without read:sensitive Coolify does not blank your values, it removes them from the
response: every variable, every database password and every one-click stack's compose file would
arrive empty. Deplo checks both at Connect and refuses rather than importing blanks.
One run per source team
A Coolify token is bound to the team it was minted in, and Coolify will not even list the other teams to it. For a second team, switch team over there, mint a token, then paste it here and press Add: it joins the Teams to bring over list with the Deplo team it lands in. Two Coolify teams may land in the same Deplo team, or each in its own.
A single walk of the wizard is one run per team, queued by the control plane and taken one after another. The list belongs to the server, not to the browser tab: close the page, reload it, come back tomorrow.
On every source machine
A Coolify instance often has more than one server, and the Install step lists every one behind the panel.
- One shell command, as root, on each machine behind the panel. It installs the Deplo agent, which is what reads the disks. Installing is outbound, so it works behind any firewall.
- Inbound TCP 9443 on each of them. Reading a volume is Deplo dialing that machine back, which its call home never does. A machine that went green can still be out of reach.
- A snapshot of every machine, taken first. It is one click at every provider, and it covers what nothing in the wizard can put back.
- Room on the destination disk. A copy writes a second copy of everything before the old one goes. Deplo warns under 10% free or under 5 GiB.
What the run stops
Every service you tick is stopped on Coolify when its data copy starts, and on a run that finishes it is never started again. That stop is the cutover. What you leave unticked is not touched at all.
Run the migration
Snapshot the server. The cutover removes Coolify in the same confirmed step, so the snapshot is your only way back once you press it.
Run the installer. The same command as any other install:
curl -fsSL https://deplo.build/install.sh | bashIt finds Coolify and asks whether Deplo may replace it. Answer no and nothing is installed.
--yes does not answer this one; to install without being asked, set DEPLO_TAKEOVER=coolify.
Open the address it prints. Until the ports move, Deplo answers over plain http through
Coolify's own proxy on port 80, so there is nothing to open in the firewall. The same address
answers over https from Deplo's proxy after the cutover. If Coolify has no dynamic config
directory to write into, the installer says so and you tunnel instead with
ssh -L 3001:localhost:3001 root@<server-ip>.
Create your account, then choose. Deplo opens the takeover wizard and nothing else. Its first step asks the one question that cannot be undone: Bring your data over, or Start clean, which deletes Coolify and everything it runs here and skips straight to Take over.
Connect, Install, Review. The panel is already filled in. Paste a token per Coolify team, run the printed line on any other machine behind the panel, tick what comes over, and press Start migration. Everything lands on this machine.
Take over the machine. The step opens once every team on the list has landed. It asks you to
tick I have no other teams on this panel, adds a second tick for any service whose data copy
failed, and takes one confirmation you type coolify into.
On the host, deplo-takeover.service then stops Coolify and the apps it ran, copies its
certificates into Deplo's store, moves Traefik onto 80 and 443, removes Coolify's containers,
networks, images and directory, and restarts Docker. Volumes and Coolify's own backups stay on the
disk. The apps go down because two copies of one app cannot both hold a port.
The page follows the dashboard to the https address by itself, once the certificate has issued.
deplo-takeover.service removes itself afterwards.
Deploy your apps. Nothing is deployed for you. Open each app, check it, and press Deploy.
What comes across
What lands as what
| On Coolify | Here |
|---|---|
Application, nixpacks / static / dockerfile | App built from its git repository |
Application, dockerimage | App running that image |
Application, dockercompose | Compose stack |
| Service (one-click) | Compose stack, from the compose its author wrote |
| PostgreSQL, MySQL, MariaDB, MongoDB, Redis, ClickHouse | The same database engine |
| KeyDB, Dragonfly | Nothing. Named in the report |
| Persistent storage | Volume, with its data |
| File storage | Config file under Storage |
| Environment variables | The app's variables, magic SERVICE_* values already resolved |
| Preview-only variables | The app's preview variables |
| Shared variables (team, project, environment, server) | Shared variables at the same level, linked to the apps that referenced them |
| Domains, with their port and path | Domains |
| HTTP basic auth | Access |
| Memory and CPU limits | Resources |
| Health check | Advanced -> Health check |
| Published host ports | Advanced -> Published ports, with the publish-ports permission |
| Scheduled tasks | Cron jobs |
| S3 storages | Backup destinations, tested on arrival |
| Scheduled database backups to an S3 storage | Backup schedules, on the destination that came with them |
What changes on the way
- Throwaway hostnames are re-hosted. A name on
sslip.io,traefik.me,nip.ioorlocalhosthas the old server's IP baked into it, so Deplo mints a generatednip.ioaddress with the same route and the app's Domains section says which became which. Real domains you own come across as they are, and you repoint DNS when you are ready. In a takeover the app lands on the same machine, so the generated name keeps working and stays as it is. - Connection strings are rewritten. A database keeps its data but not its hostname - it
answers as
db-<name>here - and every variable that named the old one is updated. What Deplo cannot reach is what an app stores about itself: a savedtrusted_domainsorsiteurlstill holds the old address, and the report tells you to fix it from the app's Console. - A volume keeps the name you gave it. Coolify stores a persistent storage as
<resource id>-<name>, and that id is its own bookkeeping, so the volume arrives as plain<name>. - A volume the compose file pins by name is not the one that was running. Coolify renames every
volume a compose declares to
<resource id>_<key>and honours neitherexternal: truenor a pinnedname:. Deplo copies what was actually running, and the report names the pinned volume so you can decide what to do with what is in it. - The compose is the one its author wrote, not the copy Coolify renders with its own labels, container names and per-resource network. Deplo writes those itself.
- A published host port comes across, and needs the publish-ports permission. Without it the app still arrives and the report names the ports it left behind. A port under 1024 cannot be republished; a domain is usually the answer.
- A network you created yourself does not come across. It exists only on that machine, and left in place the stack would not start here at all. An Environment owns one network and every app in it shares that one, so two stacks that talked over a network of your own keep talking as long as they land in the same Environment.
- A service name another stack already answers to is renamed. Two one-click stacks in one
Environment both calling their database
dbcannot both have the name; the second arrives as<app>-db, and itsdepends_on, its variables and its domain follow. - A bind comes across with what is in it, a single file included. A one-click stack keeps its state beside its own compose file under Coolify's data directory, and a config file Coolify keeps at a real path outside it travels too, landing in Deplo's own files directory rather than recreating Coolify's path. A bind that names a file rather than a directory copies that file alone.
- A
{{team.KEY}}reference becomes a real link. A value that is exactly one reference to a shared variable of the same name arrives linked to it, not as a second copy. When the names differ a link cannot express it, so the value Coolify resolved arrives instead and the report says so. - A server-level shared variable is offered to the project. Coolify scopes it to a machine, across projects; Deplo has no server scope.
- A variable is a secret here only if Coolify marked it one. Deplo never types a variable secret from its name: a secret is write-only and cannot be turned back, so a guess would lock away a value nobody can check. A variable Coolify marks shown once is the exception - but its API answers with no value for most of them, whatever the token holds, so those land plain and empty and the report names the keys. Fill them in under Variables before you deploy.
- An address Coolify stored without a scheme arrives on plain http. It answered on
:80over there, so it answers on:80here and every link anyone had written keeps working. The report names it; add a certificate under Domains when you want https. - A template address recorded without a port still routes. A one-click template that declares
SERVICE_FQDN_<NAME>and no port has the port read off the service instead, and the report says which one it took. - A
$Coolify would have expanded is named. Unless a variable is marked literal there, Coolify lets compose expand every$in its value, so the running value could differ from the one you typed. It arrives written as it was. - Imported basic-auth credentials are flagged. They never went through Deplo's password policy or the breach check.
- Anything needing a permission you do not hold lands in the report instead, naming what asked for it and who turns it on.
What is left behind
- KeyDB and Dragonfly. There is no engine for either here. Both are named in the report.
- Coolify's own
COOLIFY_*variables. They name the machine and the panel you are leaving. - Deploy keys and connected-source credentials. A public repository clones here with nothing; attach a git connection for a private one, which also turns on auto-deploy.
- Private registry passwords. Coolify never exposes them. Add the registry under Registries before the deploy fails on it.
- A volume's own backup to an S3 storage. Coolify's API writes those schedules but never lists them. Set it again under Backups: one app backup here covers every volume of the app.
- A database backup that saved only to the local disk. There is no destination here to put it on, so the report names it.
- The machine's own files. Coolify files every bind mount under Storages,
/etc/localtimeand/etc/resolv.confincluded. Those belong to the host, and the destination already has its own.
Backing out
Before the data step. Stop and undo removes everything the run created here and takes the agent back off the source. Coolify keeps all of its data.
During or after the data step. Nothing is rolled back. Everything already created here stays as it is, every service whose data did not arrive is marked and refuses to deploy, and what this run stopped on Coolify is started again. The agents stay where they are so the copy can run again.
A takeover, before the ports move. Cancel and remove Deplo starts every service the migration stopped, starts Coolify's own containers with the restart policy each had, removes everything Deplo created here with its volumes and networks, and uninstalls Deplo. It asks for the token again, because the run's own copy is wiped when the run ends.
A takeover, after the ports move. The snapshot is the only way back. If the port move itself fails, the unit puts the ports back and starts Coolify again on its own, and the step offers Try again with the reason - usually a container of yours that grabbed 80 while the proxy was down.
Running the whole thing again is safe: everything already here is kept, its data is copied across again when you tick it, and Install shows the line to paste once more.
Troubleshooting
| Symptom | What to check |
|---|---|
| API is disabled | Turn the API on in Coolify's settings. |
| You are not allowed to access the API | Coolify's IP allowlist does not include this machine. |
| This token cannot read values, or cannot read compose files | The token is not root and has no read:sensitive, or it belongs to an account that is not an admin or owner of the team. |
| This token cannot stop a service | The token is not root and has no deploy. Mint a new one with root ticked. |
| Coolify refused the token | Every token is minted with an expiry. This one ran out, or was revoked. |
| Rate limited | Coolify allows 200 requests a minute. A busy panel can still hit the ceiling; wait and try again. |
| The panel's certificate is not trusted | Deplo cannot be told to accept one. Use Coolify's plain http address. |
| A 502, or a Cloudflare 521 | That comes from what sits in front of the panel, not the panel. Start it, or point Deplo at the panel's own address. |
| Stuck on Waiting for the agent | The install command has not run on that machine, or the machine cannot reach this one. |
| The server agent did not install during a takeover | The data step cannot start without it. Re-run /opt/deplo/install.sh on the machine, or use the command under Settings -> Servers. |
| One source machine will not answer | Its services are named in the report and blocked from deploying; the machines that do answer are still copied. Check inbound 9443 from here with nc -vz <source-ip> 9443. |
| A volume arrives empty | The app refuses to deploy until you decide. One volume has six hours to copy, so a very big volume over a slow link is one to bring over yourself. |
| A shared-variable level is missing from the report's imports | An older panel has no endpoint for one of them. Copy those in by hand. |
| A single-file bind is refused by name | The destination server's agent is too old for it. Update that agent and copy the data again. |
| The data step stops half way | Nothing is rolled back. A second run skips what is already here and copies the data again, or use Copy the data again on the app. |
| Two apps mount the same absolute path | The copy empties its target before it writes, so Deplo refuses and names the other app. Give one of them its own directory. |
| The port move failed | The unit put the ports back and started Coolify again. The step names what is holding the port, next to Try again. |
More in Servers and agents.
Next steps
Did this page help you?