Deplo

Move from Coolify

Beta

A wizard that reads an existing Coolify installation over its API and recreates 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 that reads an existing Coolify installation over its API and recreates it here: projects, applications, one-click services, databases, domains, environment variables, volumes and backup destinations, with the data copied across.

Settings -> System -> Migrations, for instance admins. Paste the panel address and a token, and Deplo works out which product is answering. Each Coolify team lands in a Deplo team of your choosing - one that exists, where you hold create_projects, or a new one named after it.

Deplo going on the same machine?

If Deplo installs on the machine Coolify already runs on, the installer does all of this for you: it brings everything across, takes ports 80 and 443, and removes Coolify when you say so. See Take over your VPS.

Will your apps make it across?

What migrates lists the shapes that come across untouched, the ones that need a look, and how each release is measured against live installations.


Before you start

Three things on the Coolify side, and only one of them is easy to miss.

Turn the API on

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.

Mint a token that can read secrets and stop a service

Coolify: Keys & Tokens -> API tokens. Tick deploy first, then read and read:sensitive, and mint it from an account that is an admin or owner of the team. The order matters: Coolify's dialog clears the list the moment deploy is ticked, so tick it before the other two.

read:sensitive is not optional

Without it Coolify does not blank your secrets, it removes them from the response. Every environment variable, every database password and every one-click stack's compose file would arrive empty, and the migration would look like it worked. Deplo refuses to scan at all rather than let that happen.

deploy is not optional either

The data step stops each service on Coolify before it copies its data, and Coolify allows that only with the deploy permission. Without it every service would arrive with its configuration and none of its data. Deplo checks at Connect and refuses with This token cannot stop a service.

One token reads one team

A Coolify token is bound to the team it was minted in, and Coolify will not even list the other teams to it. If your Coolify has three teams, switch team and mint three tokens, one each - the next step takes them all.

Check the IP allowlist

If Coolify's API is restricted to a list of addresses, add the machine Deplo runs on. The refusal reads You are not allowed to access the API.


How it works

Deplo does not scrape files or ask you for SSH. It talks to Coolify's API for the configuration, and for the data it puts an agent on each Coolify server.

That agent is installed in a deliberately minimal server role: a migration source. It is out of every deploy picker and every build picker, never a backup destination, never swept by cleanup, absent from monitoring and from the fleet count, and granted to one team only. It is also the only server Deplo will uninstall itself from when you are done.

Volumes are copied by the agent standing on the disk that holds them, relayed through the control plane, because agents cannot dial each other.

A Coolify instance often has more than one server. The Install step lists every one behind the panel and gives you a command per machine.


What Deplo touches, and what it leaves alone

Nothing on Coolify is deleted. Volumes, bind-mounted directories and bind-mounted files all stay exactly where they are. Deplo reads them and copies the bytes across; the originals are still on that machine afterwards, so you can run the migration again or go back to Coolify if you change your mind.

It does stop what it copies, and leaves it stopped. A volume read while its container is still writing to it arrives corrupted, so the data step stops the service on Coolify first. That stop is the cutover: Deplo never starts it again on Coolify. If Coolify refuses to stop a service that is running, its data is not copied at all and the report says so, rather than copying something that moved under the read.

On this side, only the paths the app itself mounts are written. Each volume and each bind mount is emptied and rewritten with what came across, so a second run does not merge two copies. Nothing else on the destination server is touched. A managed database is started again and checked; every other app waits for you to press Deploy.

The host is not part of the app. What belongs to the machine rather than to what you are moving - the Docker socket, the kernel's own pseudo-filesystems, and the machine's timezone, hostname and resolver files - is left out of the copy on both sides. The destination server already has its own, and rewriting them would change that machine rather than the app.

The only thing Deplo puts on the machines behind Coolify is its own agent, and Remove from Deplo takes it off again.


Run a migration

The wizard's steps are Connect, Install, Review, People (for an instance admin) and Done. A step opens only when the one before it has finished.

Connect

Open Settings -> System -> Migrations, the Migrate tab, and enter the Panel address and the API token. Deplo reads the panel and puts the team the token covers on the Teams to bring over list, with the Deplo team it lands in beside it: the team of the same name when there is one, otherwise a new team named after it. Change it from the dropdown - any team where you hold create_projects, or New team. A new team is created when its migration starts, not before.

A private address - the same machine (http://172.17.0.1:8000) or another box on your network - works too, but only for an instance admin.

One token reads one team

A Coolify token is bound to the team it was minted in. For a second team, mint a token there, paste it and press Add. Each one joins the Teams to bring over list with its own landing team: two Coolify teams may land in the same Deplo team, or each in its own.

Install

Deplo needs its agent on each Coolify server, to read the disks. The step prints one line per machine: sign in to that machine as root, paste it, and wait here. It shows Waiting for the agent until the machine calls home, and Connected once it has. Deplo takes the agent back off when the migration is done.

The machine's own address, not the panel's

Deplo's first guess for a machine is the address you gave the panel. Behind a proxy or a CDN that address is the proxy, so nothing answers on 9443 and the wizard shows Cannot connect with a box to type the machine's real address into. That address is remembered for the next attempt.

A machine that already answers has a Change address link too: a live agent on the wrong box reads as connected, and every volume of the services on it would then arrive empty.

Review

Deplo shows the tree: every project, environment, application, service and database it found. Tick what comes over. Per item you can set Expose publicly and a Host port where that applies, and which server it lands on; Place everything on sets the whole tree at once. A row that cannot come over says why in a badge, and the badge's tooltip carries the reason.

Then press Start migration. The run happens on the server: the step shows the progress, the log is one click away under Show log, and you can close the tab. Stop and undo takes the run back out - see below for what that means once the data step has begun.

The chip at the top of every page shows the same progress to the whole team. When the run ends it turns green and says Migration finished, and it stays there until somebody closes the report: a run that ends while you are on another page is not missed.

One migration runs at a time per team. Starting a second one while the first is still going is refused by name, so a double click cannot create the same app twice.

With several teams on the list, the review shows one section per team, each headed by the Deplo team it lands in, and Migrate N teams runs them one after another on its own: a new team is created as its turn comes, and Deplo leaves its agents on the source machines until the last team is over, so only the first team pays for the install. A team with nothing ticked is skipped.

At the end the report is on the same step, one line per team. It names everything that did not come across cleanly, and you acknowledge it to move on.

People

For an instance admin: the step offers single-use registration links for the members of the Coolify team, so you can hand them out. Coolify does not report anybody's role over its API, so everyone arrives as a plain member and you promote whoever should be more.

Done

Nothing is deployed yet. Open each app, check it over, and press Deploy when you want the traffic. The report of every past run stays under the History tab, with its log.

Stop during the data step

Before the data step, Stop and undo removes everything the run created and takes the agent off the source. Once data has started moving, a stop keeps what came across, marks every service whose data is not here yet so it refuses to deploy on empty storage, starts again what it stopped on Coolify, and leaves the agents where they are so the copy can run again.


What comes across

On CoolifyHere
Application, nixpacks / static / dockerfileApp built from its git repository
Application, dockerimageApp running that image
Application, dockercomposeCompose stack
Service (one-click)Compose stack, from the compose its author wrote
PostgreSQL, MySQL, MariaDB, MongoDB, Redis, ClickHouseThe same database engine
KeyDB, DragonflyNothing - named in the report, see below
Persistent storageVolume, with its data
File storageConfig file under Storage
Environment variablesThe app's variables, magic SERVICE_* values already resolved
Preview-only variablesThe 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 pathDomains
HTTP basic authAccess
Memory and CPU limitsResources
Health checkAdvanced -> Health check
Published host portsAdvanced -> Published ports, with the publish-ports permission
Scheduled tasksCron jobs
S3 storagesBackup destinations, tested on arrival
Scheduled database backups that save to an S3 storageBackup schedules, on the destination that came with them

What changes on the way across

  • A published host port comes across. An app that bound 16379:6379 on the source binds it here too, under Settings -> Advanced -> Published ports. It 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 - the report says so, and a domain is usually the answer.
  • The same app name in two environments keeps both. production/wiki and staging/wiki are two apps and both stay called wiki. Only the internal name has to differ, so the second one is filed under /<team>/apps/wiki-1 - that name is what its container and its volumes take. The report says which one it took.
  • Throwaway hostnames get re-hosted. A Coolify app served on a generated *.sslip.io name has the old server's IP baked into it, so that name would keep pointing at the old machine. Deplo mints its own equivalent, keeps the same route, and the app's Domains section tells you which address became which. Real domains you own come across as they are, and you repoint DNS when you are ready. On a takeover the generated name points at the same machine, so it stays as it is.
  • Connection strings are rewritten to the new names. 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 on the way across. Same for an address that had to be re-hosted. The report names every variable it changed. What Deplo cannot reach is what an app stores about itself: a saved trusted_domains or siteurl still 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 here as plain <name> - what you read in Storage, and what Deplo names it on disk. The data is the same either way.
  • The compose is the one its author wrote. Deplo imports Coolify's raw compose, not the copy Coolify renders with its own labels, container names and per-resource network - Deplo writes those itself.
  • A volume the file pins by name is not the one that was running. Coolify renames every volume a compose declares to its own <resource id>_<key>, and honours neither external: true nor a pinned name:. So the volume the file names is one the stack never mounted. 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.
  • A database lands in the same Environment as its apps. An Environment owns a Docker network here, so a database placed anywhere else would not answer to db-<name> from the apps that name it. It arrives beside them, and its Networking card moves it later if you want it somewhere else.
  • A network you created yourself does not come across. A stack attached to a network made on the source machine with docker network create arrives without it. That network exists only on that machine, and left in place the stack would not start here at all. You rarely lose anything by it: 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. The report names each network it dropped.
  • A service name another stack already answers to is renamed. Every stack in one Environment shares that network, so two one-click apps both calling their database db cannot both have the name. The second one arrives as <app>-db rather than being refused, and its depends_on, its variables and its domain all follow the new name. The report says which names changed.
  • 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 - a ./content directory, a ./nginx.conf - under Coolify's data directory. Those are copied into the app's files directory in the data step. A bind that names a host path outside the stack works the same way, and one that names a file rather than a directory copies that file alone, never the directory around it. Nothing else in that directory travels.
  • The machine's own files stay on the machine. Coolify files every bind mount under Storages, /etc/localtime and /etc/resolv.conf included. Those belong to the host, not to the app - the server you are moving to already has its own - so Deplo never carries them, and the report does not mention them.
  • A file that is not text arrives with the data. A config file comes across as a File under Settings -> Storage, which holds text. A binary one - an SQLite database mounted at a path, say - and a whole mounted directory are copied in the data step instead, byte for byte. The report says which files took that route.
  • A file Coolify keeps at a real path on the host travels too. A stack's config file stored outside Coolify's own data directory - /srv/app/nginx.conf rather than /data/coolify/services/<id>/nginx.conf - is carried by the data step, and the copy lands in Deplo's own files directory rather than recreating Coolify's path on the new machine. It used to arrive mounted and empty, with no line in the report.
  • An address a template recorded without a port still routes. A one-click template that declares SERVICE_FQDN_<NAME> and no SERVICE_FQDN_<NAME>_<PORT> names its service and no port. Deplo reads the port off that service - what it publishes, what its healthcheck dials, else the usual web port - and the report says which one it took.
  • Coolify's own COOLIFY_* variables stay behind. They name the machine and the panel you are leaving, so they are dropped from every shared level rather than becoming team variables nobody can act on.
  • A git application arrives without a credential when Coolify cloned it through a connected source. A public repository clones here exactly as it cloned there and needs nothing. Attach a git connection for a private one; that also turns on auto-deploy.
  • A {{team.KEY}} reference becomes a real link. A variable whose value is exactly one reference to a shared variable of the same name arrives as a link to that shared variable, not as a second copy: change the shared value once and the app follows. When the names differ (SHARED={{team.SMTP_HOST}}) a link cannot express it, so the variable arrives with the value Coolify resolved and the report says so. Only the apps that referenced a shared variable are linked to it - the others still see it offered under Variables, one click away.
  • A server-level shared variable is offered to the project. Coolify scopes it to a machine, across projects. Deplo has no server scope, so it arrives as a shared variable offered to the project being imported, and the report says that is what happened.
  • A shared-variable level Coolify will not answer for is in the report. An older panel has no endpoint for one of them. Nothing is imported from that level and the report names it, so you copy those in by hand instead of discovering them missing later.
  • Preview-only variables come across as preview variables. Deplo's pull-request previews inherit the app's variables and add their own on top, so a variable Coolify kept for previews only lands there - never in production. The report lists them by key.
  • A backup schedule follows its destination. A database that saved to an S3 storage arrives with the same schedule on the destination Deplo created from that storage. One that saved only to the local disk is named in the report, because there is no destination here to put it on.
  • A volume's own backup to an S3 storage does not come across. Coolify's API writes those schedules but never lists them, so there is nothing to read. Set it again under Backups: one app backup here covers every volume of the app.
  • KeyDB and Dragonfly have no engine here. They are named in the report rather than quietly skipped, so you can decide what to do with them.
  • An address Coolify stored without http:// or https:// arrives on plain http. It answered on :80 over there, so it answers on :80 here and every link anyone had written keeps working. The report names it; add a certificate under Domains when you want https.
  • 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 here written as it was, and the report names the variable so you can check it.
  • 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 can never be edited or turned back, so a guess would lock away a value nobody can check against Coolify. MYSQL_ROOT_PASSWORD arrives as readable as PORT, and you mark what you want Secret under Variables afterwards. A variable Coolify marks shown once is the exception: when its API hands the value over, it lands as a secret, and the report names it.
  • A shown-once variable usually arrives empty. Coolify's API answers with no value for most of them, whatever the token holds, so Deplo has nothing to import. It lands plain and empty - an empty secret could never be filled in - and the report names those keys: fill them in under Variables before you deploy, then mark them Secret if you want.
  • 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 the report tells you

Besides what did and did not come across, the report carries a few checks that are cheaper to read now than to find out later.

  • A copy that would erase another app is refused. Two apps mounting the same absolute path on the same server - /opt/data on both is enough - is all it takes, because the data copy empties its target before it writes. Deplo names the other app and writes nothing: on the review screen before the run starts, and again in the report. Give one of the two its own directory, then copy the data again.
  • Free disk on the machine receiving the data, when it is under 10% or under 5 GiB. A copy writes a second copy of everything before the old one goes, so free some room first.
  • A project that is empty on Coolify is named, rather than left out silently and read as one that vanished between the scan and the report.
  • An API key belongs to one organization, so an import covers that one only. Anything under another organization needs a second import with a key from it.
  • The registries a stack pulls its images from. Coolify never exposes the password, so add a private one under Registries before the deploy fails on it.

After it lands

  • Stop and undo, while a run is moving, deletes what the migration created here, if you want to start again.
  • Remove from Deplo takes the migration source out of the fleet. Deplo uninstalls its own agent from that machine first, and warns you with Agent still installed if it could not.

The History tab keeps every past run with its report and the mark of the panel it read.

An app whose data did not come across refuses to deploy. The report names it, its page says why, and it stays that way on purpose: starting it would run it on empty storage. Bring that data over yourself, or press Deploy anyway on the app to accept starting without it.

Limits and gotchas

  • Cut DNS over yourself, when you are ready. The migration does not touch your registrar.
  • Copying data takes as long as the data is big. Volumes stream host to host through the control plane.
  • Running it again is safe. Everything already here is kept, its data is copied across again when you tick it, and a backup schedule the first run could not set because its destination was not here yet is added. Deplo took its agent off the source machine when the first run finished, so Install shows the line to paste once more.
  • Check the report, always. A migration that says "done" with twelve lines in the report has twelve things waiting for you.
  • Check that the source is gone afterwards. Deplo takes its agent off the machine when the run finishes; if it could not, Agent still installed says so, with the command to run there.

If it does not work

  • 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.
  • The panel's certificate is not trusted: Deplo cannot be told to accept one, so use Coolify's plain http address instead.
  • This token cannot read values or This token cannot read compose files: the token has no read:sensitive, or it belongs to an account that is not an admin or owner of the team. Both are the same scope.
  • This token cannot stop a service: the token has no deploy. Mint a new one with deploy ticked first, then read and read:sensitive.
  • Coolify refused the token: every token is minted with an expiry, and this one has run out or was revoked. Mint a new one under Keys & Tokens.
  • Rate limited: Coolify allows 200 requests a minute and Deplo stays under it, but a busy panel can still hit the ceiling. Wait and try again.
  • Stuck on Waiting for the agent: the install command has not run on that machine, or it cannot reach this one.
  • A volume arrives empty: the report says so, and the app it belongs to refuses to deploy until you decide what to do about it. A single volume has six hours to copy - past that it fails and the report marks it empty, so a very big volume over a slow link is one to bring over yourself.
  • One source machine will not answer: the services on it are named in the report and blocked from deploying, and the machines that do answer are still copied. Only when nothing answers at all does the step stop before touching anything. Copying dials that machine on inbound 9443, which its call home never uses, so a source that went green can still be out of reach. Check it from this machine with nc -vz <source-ip> 9443.
  • A file the target agent is too old to take: copying a bind that names a single file needs a recent agent on the DESTINATION server. An older one refuses the file by name rather than putting an empty directory in its place - update that server's agent and run the copy again.
  • The data step stops half way: nothing is rolled back. Everything already created here stays exactly as it is, and every service whose data did not arrive is named in the report and blocked from deploying. A second run skips everything already created and copies the data again, or bring the missing data over yourself with Copy the data again on the app.

See Servers and agents for more.


See also

Did this page help you?

On this page