One-shot migration tool that hands a running Coolify v3 install over to
coolifygo on the same host. Reads v3's SQLite database directly, decrypts
its secrets with v3's COOLIFY_SECRET_KEY, inserts equivalent rows into
coolifygo's Postgres, takes over every workload container (apps + DBs),
upgrades the host Docker engine, and finally wipes the v3 stack.
On the v3 host:
curl -fsSL https://raw.githubusercontent.com/annihilatorrrr/coolifyv32Go/main/install.sh | sudo bashThat wrapper does the whole thing end to end:
- Installs Go if missing
go installs this migrater from source- Freezes v3 — stops
coolify+coolify-fluentbit, releasing port 3000 and quiescing SQLite - Upgrades the host's Docker engine (always-on — v3 ships with an old version; safe here because v3 is frozen and coolifygo isn't up yet)
- Installs coolifygo via
gocoolify/install.sh(skipped if already running) — port 3000 is now free and Docker is modern - Runs
--phase=pre-docker— discover, extract SQLite, decrypt, plan, insert into coolifygo's Postgres - Runs
--phase=post-docker— takes over every v3 workload container, then wipes v3 completely
No manual flags required — DSN + encryption key are sourced from /data/coolifygo/.env.
coolfymigrater \
--coolifygo-dsn=postgres://coolifygo:...@coolifygo-postgres:5432/coolifygo \
--coolifygo-key=$(grep DATA_ENCRYPTION_KEY /data/coolifygo/.env | cut -d= -f2)
Both flags fall back to $DATABASE_URL and $DATA_ENCRYPTION_KEY env vars.
--v3-secret-key and --v3-sqlite are auto-detected from the running
coolify container; pass them if discovery fails.
| Flag | Purpose |
|---|---|
--phase |
all (default), pre-docker (stops after insert), or post-docker (resumes from state file). Used by install.sh to bracket the Docker upgrade. |
--state-file |
Where pre-docker writes / post-docker reads the plan JSON (default /var/lib/coolfymigrater/state.json). |
--dry-run |
Print the plan and exit. |
--yes |
Skip confirmation prompts. |
--no-teardown |
Keep v3 alive after data migration. |
Built for the narrow shape the operator described:
- v3 manages Dockerfile-built apps only (other build packs error out).
- v3 manages PostgreSQL + Redis databases only (MySQL / Mongo error out).
- No persistent storage on application containers.
- A single GitHub App git source.
- v3 runs on the same host as coolifygo (local Docker socket).
Everything else (services, FQDN/Traefik/SSL, teams, PR previews, remote destinations, Storages) is intentionally skipped.
- discover — inspect the local
coolifycontainer for the secret key and container metadata; list every workload on thecoolify-infranetwork. - connect coolifygo postgres — verify DSN + key + that a
type=localserver row already exists (created by coolifygo'sEnsureLocalServer). - freeze — stop
coolifyandcoolify-fluentbitso v3's SQLite is in a consistent state. - extract + read — briefly start
coolify,docker cp/app/db/prod.dbto a temp dir, stop again, open it viamodernc.org/sqlite, decrypt every secret column with AES-256-CTR keyed byCOOLIFY_SECRET_KEY. - plan — map v3 entities onto coolifygo row shapes. Validates that buildPacks and DB types are in scope.
- insert — one Postgres transaction writes git_sources → applications → databases. Rollback on any failure leaves coolifygo untouched.
- takeover — apps are recreated as
coolifygo-<slug>-<id8>with the existing image + env + port binding + coolifygo's labels/network. Databases stop, get their data volume copied tocoolifygo-db-<id8>via a one-shot alpine container, then start fresh with coolifygo's canonical image, env, healthcheck, and network. - teardown — stop+remove every v3 stack container (coolify, fluentbit,
coolify-proxy), drop the seven v3 volumes (coolify-db, coolify-logs,
coolify-local-backup, coolify-ssl-certs, two letsencrypt volumes, and the
optional coolify-pgdb), prune
coollabsio/coolify+ghcr.io/coollabsioimages, remove thecoolify-infranetwork, and clean host paths (/data/coolify,/var/lib/coolify,/etc/cron.d/coolify-default,/usr/local/bin/coolify).
| v3 entity | coolifygo target | Notes |
|---|---|---|
GithubApp + GitSource |
git_sources |
github-app auth_type only. PEM, client secret, webhook secret re-encrypted under coolifygo's AES-256-GCM. |
Application + Secret |
applications |
env_vars carries decrypted secrets. Branch defaults to main, base_directory to ./, dockerfile_location to Dockerfile. Status set to whatever the live v3 container reports. Host port carried from Docker's actual port bindings (not v3's internal port field, which was for Traefik routing). Apps with no host-published port get port 0 (no host binding). |
Database |
databases |
Slug regenerated. Volume content copied byte-for-byte. Port + internal_port set to the canonical 5432/6379. Public port carried from Docker's actual host binding (overrides v3 SQLite if they diverge). |
| running container metadata | container_id / image_name on the new row | So the boot reconciler doesn't immediately try to recreate something that's already up. |
- v3 management containers + their volumes + their Docker network.
- v3 host paths (
/data/coolify,/var/lib/coolify, the systemd unit hint binary/usr/local/bin/coolify, the/etc/cron.d/coolify-defaultcron). - v3 Traefik / Let's Encrypt state (SSL is out of scope in coolifygo).
- v3 backup volumes (coolifygo runs its own backup pipeline; old archives are not migrated).
- Port conflict detection at plan time: the migrater checks for duplicate host ports within the migration batch and against resources already in coolifygo's database. Bails with a descriptive error before any change.
- Inserts run inside a single Postgres transaction. Rollback on any failure leaves coolifygo in its pre-migration state.
- Container takeover begins only after the transaction commits. A failed
takeover leaves a partial state in coolifygo but never destroys v3's data
volumes (those are dropped by the teardown phase, gated by a confirm prompt
unless
--yesis passed). --dry-runprints the full plan and exits before any change.
The migrater is idempotent on names: applications, databases, and GitHub Apps
that already exist in coolifygo are skipped. Container takeover is safe to
re-run because new container creation removes any prior coolifygo-… with
the same name first.