Coolify server-to-server migration
coolify-migrate
Copy a Coolify instance, or just the apps, databases and services you pick, to another server while the old one keeps running. Test the copy, then cut over when you're ready. If anything fails along the way, both servers are rolled back automatically.
██████╗ ██████╗ ██████╗ ██╗ ██╗███████╗██╗ ██╗
██╔════╝██╔═══██╗██╔═══██╗██║ ██║██╔════╝╚██╗ ██╔╝
██║ ██║ ██║██║ ██║██║ ██║█████╗ ╚████╔╝
██║ ██║ ██║██║ ██║██║ ██║██╔══╝ ╚██╔╝
╚██████╗╚██████╔╝╚██████╔╝███████╗██║██║ ██║
╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝╚═╝ ╚═╝
███╗ ███╗██╗ ██████╗ ██████╗ █████╗ ████████╗███████╗
████╗ ████║██║██╔════╝ ██╔══██╗██╔══██╗╚══██╔══╝██╔════╝
██╔████╔██║██║██║ ███╗██████╔╝███████║ ██║ █████╗
██║╚██╔╝██║██║██║ ██║██╔══██╗██╔══██║ ██║ ██╔══╝
██║ ╚═╝ ██║██║╚██████╔╝██║ ██║██║ ██║ ██║ ███████╗
╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚══════╝
Contents
Highlights
Everything in this section ships in the current release.
| Area | What it does |
|---|---|
| Minimal downtime | Copies while the old server stays online. A final incremental sync moves only the changes made since the first copy. |
| Two-phase migration | Separates copy and test from cutover. You decide when production traffic moves. |
| Whole or selective moves | Clones the complete Coolify instance, or moves only the apps, databases and services you choose. |
| Many servers into one | Detects workloads on localhost and every server managed by the old Coolify, then can consolidate all of them onto the new server. |
| Empty or active destination | Installs Coolify on an empty server, or safely adds resources to a server that already runs Coolify. Existing resources are never overwritten. |
| Successful release preservation | On matching CPU architectures, transfers and starts the exact image that last ran successfully. Across architectures, rebuilds the last successful commit for the new CPU instead of building the latest branch by accident. |
| Live, consistent data copy | Performs a resumable warm transfer, then briefly freezes each database container for a consistent pass. A strict timeout always unfreezes the database. |
| Git configuration migration | Preserves repositories, branches, Git sources, provider credentials and deploy keys. |
| Secret-safe import | Keeps the original APP_KEY for a full clone. When importing into an existing Coolify, decrypts secrets on the old server and re-encrypts them with the destination key. |
| Full platform state | A full clone includes users, teams, settings, SSH keys, proxy configuration, TLS certificates, projects, environments and resource data. |
| Duplicate-job protection | Pauses Coolify schedules and backups on the new server until cutover, then restores them. |
| Automatic recovery | Journals every change, rolls back partial failures, provides --undo, and creates a source-server rollback script for cutover. |
| DNS transition bridge | Can forward visitors who still reach the old IP to the new server until DNS propagation finishes. |
| Fast transfers | Uses resumable rsync, zstd compression, a private-network route when supplied and bounded parallel jobs. |
| Safety checks | Checks CPU architecture, disk space, inodes, Coolify versions, SSH access and host fingerprints before copying. |
| Hard capacity gate | Refuses to start unless the destination has Coolify's minimum 2 CPUs/2 GB RAM, enough disk for the calculated incoming data plus a safety reserve, and enough free inodes. |
| Live activity | Shows an animated status, elapsed time and the latest useful output for discovery, connections, imports, builds and every other potentially slow operation. |
| Per-project destination mapping | When importing into an existing Coolify, asks where each selected source project should go. Different source projects can target different destination projects while preserving environment names. |
| Flexible SSH | Supports keys, SSH agents, passwords, sudo users, custom ports, pinned host keys and temporary restricted setup keys. |
| One-line start | `curl -fsSL https://coolify-migrate.grtsnx.com |
| Background operation | Detaches long migrations safely and lets you reconnect with --attach. |
| Automation support | Offers non-interactive flags, saved migration plans, --plan-only, configurable job limits and checksum-pinned installer execution. |
| Hardened input handling | Validates remote values, avoids eval and source, uses bound SQL parameters, protects rsync arguments and cleans up temporary access on every exit. |
Before you start
In this guide, the old server is the server that runs your current Coolify. The new server is the machine you are moving to.
You need:
- Access to Coolify on the old server.
- A new server's public IP address. For a full migration, start with an empty Ubuntu 22.04/24.04 or Debian 12 server. Do not install Coolify on it.
- Access to the new server through your hosting provider's web console. You will paste one command there.
- Access to your DNS provider, such as Cloudflare, Namecheap or Route 53.
- Enough free disk space on the new server for the old server's data.
If the old Coolify manages additional servers, the new server needs enough disk, memory and CPU for their combined workloads. Each managed server must be reachable from the old Coolify with its saved SSH key and passwordless sudo, and must be able to reach the new server's SSH port directly.
In the new server's cloud firewall or security group, allow:
- TCP port 22 from the old server's IP, for the transfer.
- TCP ports 80 and 443 from the internet, for your sites.
- TCP port 8000 from your own IP if you want to open the new Coolify dashboard before changing DNS.
Take a fresh backup or server snapshot before you begin. Keep the old server until the new one has worked correctly for several days.
Step-by-step migration
You do not need to know Linux. Follow these steps in order.
1. Open the old server's terminal
Sign in to your current Coolify dashboard. Open:
Servers → localhost → Terminal
If your Coolify version has no web terminal, connect to the old server with SSH instead.
2. Download and start the tool
Paste this one-line command into that terminal and press Enter:
curl -fsSL https://coolify-migrate.grtsnx.com | bash
The tool saves itself as ~/coolify-migrate.sh, then asks questions before it changes either server. A checksum-verified alternative is in .
3. Answer the prompts
Use these answers for a normal full migration:
| Prompt | What to choose or enter |
|---|---|
| Migrate from this server? | Choose Yes. |
| New server | Enter root@NEW_SERVER_IP. Replace NEW_SERVER_IP with the address from your hosting provider. If root login is blocked, use ubuntu@..., admin@... or the username your provider gives you. |
| Trust this server? | Check that the displayed fingerprint matches the fingerprint in your provider's console, then choose Yes. |
| How should I log in? | Choose Create a new key for me. The temporary public key defaults to 24 hours; enter a different lifetime when asked if needed. |
| Copy this whole line... | Copy the green command. Open the new server's web console, paste the command there, run it once, then return to the old server and press Enter. |
| What do you want to copy? | Choose Everything for a complete move. Choose Pick apps / databases / services only if you want part of the old Coolify. |
| What should happen to managed-server workloads? | Choose Move them onto the new server to combine localhost and all additional servers into one. Choose Keep them only if those machines will continue running. This prompt appears only when additional servers are detected. |
| Options | Keep the defaults selected and press Enter. |
| Start copying? | Review the OLD and NEW addresses, then choose Yes. |
The first phase only creates a copy. Your old server continues to serve your sites.
4. Let the copy finish
You may close the browser tab after the background worker starts. To watch it again, open the old server's terminal and run:
bash ~/coolify-migrate.sh --attach
Do not continue until the tool prints Copy complete. If it reports an error, it automatically removes its partial changes from the new server.
5. Test the new server
Open http://NEW_SERVER_IP:8000 in your browser and sign in with your normal Coolify email and password.
Check each important app and database. To test a domain without changing public DNS, temporarily point that domain to the new IP in your computer's hosts file. USAGE.md gives instructions for Windows, macOS and Linux.
The tool pauses Coolify schedules and backups on the new server during testing. Jobs built into your own app may still run twice, so keep this testing period short.
If the copy is wrong or you no longer want it, run this on the old server:
bash ~/coolify-migrate.sh --undo
6. Switch to the new server
Choose a quiet time. In the old server's terminal, run:
bash ~/coolify-migrate.sh --cutover
Confirm the OLD and NEW addresses again. The tool stops the migrated resources on the old server, copies the final changes, and starts them on the new server. This final sync is the only period when your apps may be unavailable.
For a full migration, the web terminal will disconnect when the old Coolify stops. This is expected; the migration continues in the background. Connect to the old server with SSH and run bash ~/coolify-migrate.sh --attach to watch it.
Do not update DNS until the tool prints Cutover complete.
7. Change DNS
The completion report lists every hostname to change and the new IP. At your DNS provider:
- Find each listed A record.
- Replace the old server's IP with the new server's IP.
- Save the record. Leave its other settings unchanged.
DNS changes may take time to reach every visitor. For a full migration, the tool can forward traffic that still reaches the old IP to the new server during this period.
8. Finish safely
Open your sites from a phone and a computer, submit a real request, and check recent database data. Keep the old server for several days. Delete it only after every site, scheduled task, backup and database works on the new server.
If you must return to the old server after cutover, run the rollback command printed by the tool:
bash /var/lib/coolify-migrate/rollback.sh
For hosts-file instructions and common failures, read the full walkthrough.
Command-line example
Operators can run the same flow without the wizard. This example runs on the old server and uses an SSH key already stored there:
bash ~/coolify-migrate.sh \ --src local \ --dst root@203.0.113.20 \ --dst-key /root/.ssh/id_ed25519 \ --dst-host-key SHA256:REPLACE_WITH_SERVER_FINGERPRINT \ --all \ --no-detach bash ~/coolify-migrate.sh --cutover
Run bash ~/coolify-migrate.sh --help for every flag. Test with --plan-only before unattended use.
To consolidate localhost plus all servers managed by the old Coolify onto one empty new server, add --consolidate:
bash ~/coolify-migrate.sh --src local --dst root@203.0.113.20 \ --dst-key /root/.ssh/id_ed25519 \ --dst-host-key SHA256:REPLACE_WITH_SERVER_FINGERPRINT \ --all --consolidate --no-detach
How it works
| Phase | What happens | Old server |
|---|---|---|
| Copy | Coolify is installed on the new server (if it doesn't have it yet), then data is copied in two passes. The first is a warm, resumable copy. The second is a consistent pass in which each database container is frozen for at most 30 seconds (docker pause), never stopped. Managed servers send their data directly to the destination over pinned SSH; the controller does not become a data bottleneck. If the limit is reached, the database is immediately unfrozen and the copy aborts safely. | Running. Nothing is stopped. |
| Test | Open the new dashboard on its IP, or point a hostname at the new IP in your hosts file. Scheduled tasks and backups stay paused on the new server, so nothing runs twice. | Serving all traffic |
Cutover (--cutover) | Stops the migrated resources on the old server and syncs only what changed since the copy, including the latest database rows. Then it starts everything on the new server and turns scheduled tasks back on. For a full migration it can also forward the old IP's traffic to the new server until DNS switches; the forward removes itself once DNS has changed. | Stopped by you, recorded in rollback.sh |
| DNS | Point the records it lists at the new IP. | Delete it once you're happy |
What you can migrate
| New server is empty | New server already runs Coolify | |
|---|---|---|
| Everything | Full clone: users, teams, settings, secrets (APP_KEY), SSH keys, proxy config and TLS certificates, every app, database and service with its data. You log in with the same account. | Every resource is added to the existing Coolify. Its own users, projects and settings aren't changed. |
| Selected apps / databases / services | A fresh Coolify is installed and your admin account is copied over (same email and password), then your selection is imported. | Your selection is added to the existing Coolify. |
For an empty destination, Everything can also consolidate a multi-server installation. The tool inventories each managed server, copies its volumes, bind mounts and last successful images, maps every workload to the new localhost, and removes retired server records from the new dashboard after a successful cutover. The original dashboard and source servers remain available for rollback.
When copying into a Coolify that already exists, secrets are decrypted on the old server and re-encrypted with the new server's own APP_KEY, all through Coolify's own models. Projects and environments are matched by name, or created if missing. Git sources, provider credentials and deploy keys come along; each app remains connected to the same repository and branch. If the dashboard hostname changes, verify the Git provider's webhook or app callback after migration. A resource that already exists on the new server (same UUID) is skipped, never overwritten.
Builds and CPU architecture
The migrator starts the exact release that was running successfully on the old server:
- On the same CPU architecture, it transfers that built image and asks Coolify to restart from it. Coolify builds only if the expected image is missing.
- When CPU architectures differ, such as x86_64 to ARM64, the old image cannot run on the new CPU. The migrator pins the last successful commit and builds a native image on the new server while the old server remains live. It never silently builds a newer branch
HEADwhen a successful commit is known.
Automatic rollback
Every change on the new server is written to a journal (/var/lib/coolify-migrate/journal). If a run fails, or you stop it with kill, the tool rolls back without being asked:
- The new server was empty: it's wiped back to empty. That removes containers, volumes,
/data/coolify, the keys we added, and Docker itself if the migration installed it. - The new server already ran Coolify: exactly the rows, containers, volumes and folders that were added are removed. Nothing else is touched.
- The old server during the copy phase: nothing was stopped there, so nothing needs undoing.
- The old server during cutover: everything is restarted from
rollback.shwith the original restart policies. The copy on the new server is kept, so you can just run--cutoveragain.
Use --no-auto-rollback to keep a failed copy for inspection. --undo performs the same journaled removal on demand.
Options
Phases (default) copy | --cutover | --undo | --cleanup-orphans | --attach
Servers --src local | user@host[:port] --dst user@host[:port]
--src-key/--dst-key PATH --src-password/--dst-password --src-agent/--dst-agent
--src-host-key/--dst-host-key SHA256:... --accept-new-host-key
--dst-reach HOST[:PORT] new server's address as seen from the old one (private network = faster)
sudo password if needed: prompted, or $SRC_SUDO_PASSWORD / $DST_SUDO_PASSWORD
What --all | --resources UUID,UUID
--target-project UUID place selected resources in an existing destination project
--consolidate move localhost + all managed servers onto one empty destination
Behaviour --no-volumes --no-binds --no-images --no-start --no-pause-tasks --no-ip-rewrite
--no-compress --no-bridge --no-auto-rollback --relay --remote-fix
--jobs N --freeze-timeout SECONDS --installer-sha256 HEX
--key-ttl-hours HOURS (default 24; range 1-720)
--coolify-version X --detach/--no-detach --plan-only -y/--yes
The update check makes one request to coolify-migrate.grtsnx.com at startup. Turn it off with COOLIFY_MIGRATE_NO_UPDATE_CHECK=1.
Security
The tool was audited line by line, and every finding is fixed in v2. The main protections:
- Everything a server reports back is treated as untrusted. Values are validated against strict patterns before they reach bash arithmetic, a shell, SQL,
rsyncor your terminal. Remote output is emitted on single lines, and labels are base64-encoded. SQL uses bound parameters (psql -v). Remote paths are quoted, andrsyncruns with--protect-args. - Saved migration plans are data, not shell scripts. Versioned, base64-encoded fields pass an allowlist and validation before use. Legacy plans use a restricted parser; neither format is executed with
sourceoreval. - Unsafe paths are refused. Bind mounts are only copied from data locations; system paths such as
/etc,/root,/var/liband/home/are never copied. Mirroring with--deleteis only used on volumes and/data/coolify. Empty or root destinations are refused outright. - Keys:
- The server-to-server transfer key is created for each run, marked
restrictand expires after 24 hours by default. Change it with--key-ttl-hours HOURS. The new server's host key is pinned, fetched over the already-authenticated connection, so trust is never blind. - On first contact with a server, its fingerprint is shown and you're asked to confirm it.
- Unattended runs refuse unknown host keys unless you pin
--src-host-key/--dst-host-key, or explicitly choose--accept-new-host-key. - The setup key you paste on the new server uses the same 24-hour default and is removed when the run ends.
- The server-to-server transfer key is created for each run, marked
- Temporary access is removed on every exit, including failures, Ctrl-C and kill. That covers the transfer key, a temporary sudoers drop-in (checked with
visudo), the password-login sshd setting, database dumps and export files.- A sudoers file left behind by an earlier run that was killed is detected and removed.
- A pasted private key is deleted after cutover.
- Secrets stay private.
- Passwords are never written to disk or logs, or put on a command line.
- Dumps and exports live in
/var/lib/coolify-migrate, owned by root with mode 700. - Files handed to Coolify's PHP are owned by its user with mode 600.
- Logs are mode 600.
- No third-party images. The traffic bridge reuses the Traefik image your Coolify already runs. Its self-removal runs from cron, so no container is given
docker.sock. - Managed-server checks keep a dedicated known-hosts file. Automatic firewall changes are off by default.
--remote-fixonly adds an allow rule for the new server's IP on the SSH port, taggedcoolify-migrate. - Installer pinning is available. In controlled environments, pass the expected official installer digest with
--installer-sha256 HEXorCOOLIFY_INSTALL_SHA256; a mismatch stops the run before execution.
Verify the script before running it as root:
curl -fsSL https://coolify-migrate.grtsnx.com -o coolify-migrate.sh curl -fsSL https://gist.githubusercontent.com/grtsnx/e73980ff9ecc011e7ea843863ebe1cac/raw/coolify-migrate.sh.sha256 -o coolify-migrate.sh.sha256 sha256sum -c coolify-migrate.sh.sha256
Requirements and limits
- Users: root, or a user with sudo. Password sudo works too; you're asked once.
- New server: a Coolify-supported OS.
rsync,zstdandcurlare installed automatically. - Coolify versions: both servers need Coolify v4. When copying into an existing Coolify, use the same or a newer version than the old server. Columns the new server doesn't have are skipped.
- CPU architecture: matching architectures reuse copied successful images. If architectures differ (for example x86_64 → ARM), a native rebuild is unavoidable; it uses the last successful commit while the old server remains live. Database volumes are copied as files; check your databases after cutover.
- Multi-server consolidation: supported for
--allonto an empty destination. Every managed source needs working SSH through the key already saved in Coolify, passwordless sudo for non-root users, and direct SSH access to the destination.--relayis intentionally unavailable for consolidation because it cannot provide the same bounded live-database consistency. Identical volume names or overlapping bind paths on different source hosts are rejected before copying so their data can never be merged accidentally. - Copying into an existing Coolify:
- Resources land in its root team.
- Remote servers managed by the old Coolify aren't imported.
- S3 backup destinations need to be re-selected.
- Traffic bridge: while active, the new server sees bridged visitors as coming from the old server's IP. It's only offered for full migrations.
Roadmap
These items are planned or being explored; they are not part of the current release. Priorities may change as real migrations expose better opportunities.
Planned next
- Build readiness gate: wait for destination builds and health checks, then show one clear success or failure report before cutover.
- Controlled cross-architecture build queue: limit concurrent native builds by available CPU and memory so a large migration cannot overload the new server.
- Multi-architecture registry reuse: detect a matching platform image in the registry and pull it instead of rebuilding when one exists.
- Pre-cutover drift report: show repository, configuration and data changes made after the copy phase.
- Machine-readable reports: produce optional JSON summaries for automation, auditing and support.
- Signed releases: add cryptographic release signatures or attestations alongside the existing SHA-256 verification.
Exploring
- DNS provider integrations: optionally switch and roll back supported DNS records after explicit confirmation.
- Encrypted migration bundles: export to encrypted storage when the old and new servers cannot connect directly.
- Completion notifications: send success, failure and action-required notices through email, Slack or Discord.
- Managed registry and build-cache handoff: carry reusable cache layers between servers to reduce unavoidable build time.
- Optional web interface: provide a visual migration view without replacing the script or command-line workflow.
Ideas and real-world migration reports are welcome in the public gist comments.
Tested
Every flow below was tested end to end on real Coolify 4.3.18: the official installer, the real database schema, real deployments and real data.
| Test | Result |
|---|---|
| Full clone, copy phase, running from the old server | Same APP_KEY, data checksum identical, old server never stopped |
Rows written after the copy, then --cutover | All 5,100 rows on the new server, checksum identical |
| Cutover failure | Old server restarted automatically from rollback.sh within 3 s |
Selected app + database into an existing Coolify (different APP_KEY) | Secrets re-encrypted and readable, existing admin and projects untouched, data identical |
--undo on that Coolify | Exactly the imported rows, containers, volumes and folders removed |
The current script was also revalidated on real Coolify 4.3.23 using two isolated Ubuntu 24.04 hosts. The full-copy test preserved APP_KEY, a PostgreSQL marker table, a managed Docker volume and its image; post-copy database and volume changes arrived during --cutover; and the generated rollback script restored the source containers with their original restart policy. This run also found and fixed an empty localhost SSH user after clone restore, which had prevented the destination proxy from starting.
The v2.3.0 many-to-one path is covered by the regression suite: managed-server SQL discovery (including Swarm apps), plan persistence, local/remote storage collision rejection, destination remapping, resumable direct rsync, bounded database freezes, temporary-key cleanup and UUID-based cutover rollback. It has not yet been exercised in a production three-host migration, so keep snapshots and verify the copy before cutover as described above.
Testing found 12 bugs that a mocked test would have missed. One matters for anyone running a migration tool next to Coolify: Coolify's installer deletes every authorized_keys line containing "coolify". That's why this tool's key comments never include the word.
Run the local regression and syntax checks with:
/bin/bash tests/test.sh shellcheck -S error coolify-migrate.sh tests/test.sh
Changelog
- 2.5.0:
- Adds independent destination selection for every selected source project.
- Uses source project UUIDs for unambiguous mappings and persists them safely in the migration plan.
- Fixes the existing
--target-projectvalue being lost at the Coolify container boundary.
- 2.4.1:
- Fixes
--cleanup-orphansso active queue rows with a nullapplication_idare detected and cancelled after an older undo. - Limits repair cleanup to orphaned Queued and In progress records; valid applications and completed history remain untouched.
- Fixes
- 2.4.0:
- Adds animated elapsed-time activity for slow discovery, connection, import, route, volume and start operations.
- Makes destination CPU, RAM, disk-space safety reserve and inode checks a hard pre-migration gate.
- Lets selective imports target an existing destination project while preserving source environment names.
- Changes temporary setup and transfer public keys to a 24-hour default with
--key-ttl-hoursconfiguration. - Undo now cancels imported deployment queues, helper containers and recorded build processes before deleting rows.
- Adds
--cleanup-orphansto repair stale queue entries left by older undo versions without touching valid deployments.
- 2.3.0:
- Consolidates
localhostand all Coolify-managed workload servers onto one empty destination. - Discovers standalone and Swarm applications, services, databases, volumes, bind mounts, images, running releases and public domains on their actual source hosts.
- Transfers managed-server data directly to the destination with resumable rsync and pinned, one-day SSH credentials.
- Resolves current workload containers at cutover, bounds every database freeze, and automatically restarts every source host on failure.
- Rejects volume-name and overlapping bind-path collisions before data moves.
- Bridges traffic from every retired source IP during DNS propagation and removes retired server records after a successful cutover.
- Consolidates
- 2.2.0:
- One-line installation saves a reusable
~/coolify-migrate.shfor later cutover and undo. - Apps are pinned to the running or recorded last successful commit.
- Same-architecture migrations use Coolify's restart-only path and rebuild only if an image is missing.
- Cross-architecture migrations clearly report the required native builds instead of claiming image reuse.
- Image/start choices now survive into the saved cutover plan.
- One-line installation saves a reusable
- 2.1.0:
- Saved plans are strictly parsed data and can no longer execute shell code.
- Unattended SSH requires pinned fingerprints or explicit TOFU consent.
- Managed-server host keys are remembered; firewall mutation is opt-in.
- Warm transfers resume partial files, negotiate zstd and run with bounded concurrency.
- Database freezes have a hard 30-second default limit and always unpause on timeout.
- Optional SHA-256 pinning protects Coolify installer execution.
- 2.0.0:
- New default: copy while the old server keeps running, then
--cutover. - Migrate selected apps, databases or services, onto an empty server or into an existing Coolify.
- Journaled automatic rollback, plus
--undo. - Every finding from the security audit fixed.
- Databases copied consistently while live.
- Scheduled tasks paused on the new server until cutover.
- Traefik-based bridge with no third-party images.
- Fixed the installer deleting the transfer keys.
- New default: copy while the old server keeps running, then
- 1.1.0: live progress bars, update check.
- 1.0.0: first release.
Built by grtsnx. If this saved your weekend, a ⭐ or a follow is appreciated.
| #!/usr/bin/env bash | |
| # Support the Coolify-style one-line command while keeping a local copy for | |
| # later --cutover, --undo, and --attach commands. | |
| if [[ -z ${BASH_SOURCE[0]:-} || ! -f ${BASH_SOURCE[0]:-} ]]; then | |
| _cm_url="${COOLIFY_MIGRATE_URL:-https://coolify-migrate.grtsnx.com}" | |
| _cm_dst="${COOLIFY_MIGRATE_SCRIPT:-$HOME/coolify-migrate.sh}" | |
| _cm_tmp="${_cm_dst}.tmp.$" | |
| mkdir -p "$(dirname "$_cm_dst")" || exit 1 | |
| curl -fsSL "$_cm_url" -o "$_cm_tmp" || { rm -f "$_cm_tmp"; exit 1; } | |
| /bin/bash -n "$_cm_tmp" || { rm -f "$_cm_tmp"; exit 1; } | |
| chmod 700 "$_cm_tmp" && mv -f "$_cm_tmp" "$_cm_dst" || { rm -f "$_cm_tmp"; exit 1; } | |
| # Let the first curl finish cleanly before replacing this stdin-driven shell. | |
| cat >/dev/null | |
| exec /bin/bash "$_cm_dst" "$@" | |
| fi | |
| # ============================================================================= | |
| # coolify-migrate — copy Coolify (or selected apps / databases / services) to | |
| # another server while the old one keeps running, then cut over when ready. | |
| # | |
| # * whole instance onto an empty server (users, settings, secrets, certs, data) | |
| # * selected resources onto an empty server or INTO an existing Coolify | |
| # * databases copied consistently while live (frozen for seconds, not stopped) | |
| # * every change on the new server is journaled: failures roll back automatically | |
| # | |
| # Run it ON the old server (e.g. from Coolify's own web terminal) or from any | |
| # machine with SSH access to both. Works with SSH keys, passwords, ssh-agent, | |
| # root or sudo (with or without password). | |
| # | |
| # curl -fsSL https://coolify-migrate.grtsnx.com | bash | |
| # | |
| # Verified download alternative: | |
| # curl -fsSL https://coolify-migrate.grtsnx.com -o coolify-migrate.sh | |
| # curl -fsSL https://gist.githubusercontent.com/grtsnx/e73980ff9ecc011e7ea843863ebe1cac/raw/coolify-migrate.sh.sha256 -o coolify-migrate.sh.sha256 | |
| # sha256sum -c coolify-migrate.sh.sha256 && bash coolify-migrate.sh | |
| # ./coolify-migrate.sh --src root@1.2.3.4 --dst root@5.6.7.8 --dst-key ~/.ssh/id_ed25519 | |
| # | |
| # Compatible with bash 3.2+ (macOS default) on the operator side. | |
| # ============================================================================= | |
| set -o pipefail | |
| TOOL_VERSION="2.5.0" | |
| TOOL_URL="https://coolify-migrate.grtsnx.com" | |
| SCRIPT_PATH=${BASH_SOURCE[0]} | |
| [[ $SCRIPT_PATH == /* ]] || SCRIPT_PATH="$PWD/$SCRIPT_PATH" | |
| # ---------------------------------------------------------------- defaults --- | |
| STATE_DIR="${COOLIFY_MIGRATE_HOME:-$HOME/.coolify-migrate}" | |
| RUN_ID="$(date +%Y%m%d-%H%M%S)" | |
| LOG="$STATE_DIR/logs/$RUN_ID.log" | |
| RWD="/var/lib/coolify-migrate" # work dir on both servers | |
| INSTALL_URL="${COOLIFY_INSTALL_URL:-https://cdn.coollabs.io/coolify/install.sh}" | |
| INSTALL_SHA256="${COOLIFY_INSTALL_SHA256:-}" | |
| # Key comments must never contain "coolify": Coolify's installer runs sed -i "/coolify/d" ~/.ssh/authorized_keys | |
| KEY_MARKER="cmig-ephemeral" | |
| SETUP_MARKER="cmig-setup" | |
| SUDOERS_FILE="/etc/sudoers.d/zz-coolify-migrate" | |
| PWCONF="/etc/ssh/sshd_config.d/00-coolify-migrate.conf" | |
| SETUP_KEY="$HOME/.ssh/coolify_migrate_ed25519" | |
| LIVE=0 INTERACTIVE=1 ASSUME_YES=0 PLAN_ONLY=0 FORCE_RELAY=0 ACCEPT_NEW_HOST_KEY=0 CONSOLIDATE=0 | |
| OPT_VOLUMES=1 OPT_BINDS=1 OPT_IMAGES=1 OPT_START=1 OPT_PAUSE_TASKS=1 OPT_IPREWRITE=1 OPT_COMPRESS=1 OPT_REMOTES=0 OPT_BRIDGE=1 | |
| CROSS_ARCH=0 | |
| AUTO_ROLLBACK=1 CUTOVER=0 UNDO=0 CLEANUP_ORPHANS=0 WHAT="" ENGINE="" TARGET_KIND="" SEL_UUIDS="" PHASE="" EXECUTING=0 OVERWRITE=0 | |
| SRC_LOCAL=0 DETACH=auto ATTACH=0 HANDED_OFF=0 WORKER_RC_FILE="" | |
| SUDO_GRANT_SRC=0 SUDO_GRANT_DST=0 PWGUIDE_SRC=0 PWGUIDE_DST=0 | |
| COOLIFY_VERSION_OVERRIDE="" DST_REACH="" | |
| SRC_HOST="" SRC_USER="" SRC_PORT="" SRC_AUTH="" SRC_KEY="" SRC_PASS="" SRC_SUDO="" | |
| DST_HOST="" DST_USER="" DST_PORT="" DST_AUTH="" DST_KEY="" DST_PASS="" DST_SUDO="" | |
| SRC_HOST_KEY="" DST_HOST_KEY="" FREEZE_TIMEOUT=30 TRANSFER_JOBS=2 KEY_TTL_HOURS=${COOLIFY_MIGRATE_KEY_TTL_HOURS:-24} | |
| TARGET_PROJECT_UUID="" | |
| # discovered facts (all validated on the way in) | |
| S_VERSION="" S_ARCH="" S_OS="" S_IP="" S_DATA=0 S_DBU=coolify S_DBN=coolify S_MID="" | |
| S_LOCAL_IP="" S_LOCAL_USER=root S_LOCAL_PUB="" S_COUNTS="" S_DOCKERCFG="" N_REMOTE=0 | |
| D_ARCH="" D_OS="" D_IP="" D_FREE=0 D_INODES=0 D_CPU=0 D_MEM_TOTAL=0 D_COOLIFY="" D_USERS=0 D_DATA="" D_MID="" D_HASDOCKER=0 D_JOURNAL=0 | |
| VOLS=() BINDS=() SKIPPED_BINDS=() IMGS=() RUNNING=() FQDNS=() DMOUNTS=() RES=() | |
| DPROJECTS=() | |
| PROJECT_MAPS=() | |
| # Workloads discovered on servers managed by the source Coolify. Records keep | |
| # their source server so data is always read from the host that owns it. | |
| MSERVERS=() MRES=() MVOLS=() MBINDS=() MIMGS=() MRUNNING=() | |
| MODE="direct" COMP="gzip -1 -c" DECOMP="gzip -dc" DSSH="" DT="" TRANSFER_REACH="" TRANSFER_PORT=22 | |
| # ---------------------------------------------------------------------- UI --- | |
| if [[ -t 1 && -z ${NO_COLOR:-} ]]; then | |
| B= |