Migrating from CloudLinux
tenantctl-migrate converts a CloudLinux host to tenantd. It reads your existing LVE caps and CageFS layout, generates matching tenantd package profiles, enrolls every caged user, swaps the PHP-FPM master, and unmounts CageFS.
Target is under two minutes on a host with around 100 users. Cron and SSH stay up throughout. There is a rollback.
Before you start
- Install tenantd first (see the quickstart). The migration tool is part of the main package.
- Keep your CloudLinux license active until the migration is verified. Cancel afterwards, not before.
- Run as root.
1. Snapshot
Running the tool with no arguments is the safe default. It captures a snapshot of your current CloudLinux state and exits without changing anything:
tenantctl-migrate
The snapshot lands at /var/lib/tenantd/migration-snapshot.json and records, per user, their LVE caps (speed, pmem, vmem, ep, nproc, io, iops), whether they are caged, and their PHP version. It also stores the raw lvectl list and cagefsctl --list-enabled output so nothing is lost in translation.
2. Preview the plan
tenantctl-migrate --dry-run
This prints exactly what the conversion will do and changes nothing. Read it. You will see each user marked enroll or skip, the generated cl-<signature> profiles with their translated systemd limits, and the FPM changes.
Users with identical LVE caps share one generated profile, so a 100-user host typically produces a handful of profiles rather than 100.
Uncaged users are listed as skip. They were not isolated under CloudLinux and the migration does not silently start isolating them.
3. Convert
tenantctl-migrate --commit
tenantctl-migrate apply is accepted as an alias for the same thing in releases after v0.5.3. On v0.5.3 it is rejected, so use --commit, which works on every release.
The conversion runs in tracked phases:
snapshot -> packages_written -> users_enrolled -> fpm_swapped -> cagefs_disabled -> completed
Progress is written to /var/lib/tenantd/migration-state.json, so if the run is interrupted you can re-invoke it and it resumes from the last completed phase rather than starting over or double-applying.
A full log is written to /var/log/tenantd/migrate.log.
4. Verify
tenantctl show <user> systemd-cgls /tenantd.slice
Confirm for a sample of users that the slice is active, the limits match what the dry run promised, and the namespace pin is live. Check that sites still serve and that SSH logins land in the right namespace.
5. Roll back if needed
tenantctl-migrate --rollback
This reverses a completed or partially completed conversion using the state file. It works from any phase, including a run that died in the middle.
6. Cancel CloudLinux
Once you are satisfied, cancel the license. tenantd needs no license key and no registration, so there is nothing to activate on this side.
Options
| Option | Effect |
|---|---|
| (no arguments) | Capture a snapshot and exit. The safe default. |
--snapshot | Explicit alias for the above. |
--dry-run | Detect CloudLinux, print the full plan, change nothing. |
--commit | Execute the conversion end to end. |
apply | Alias for --commit, in releases after v0.5.3. |
--rollback | Reverse a completed or partial conversion. |
--snapshot-file=PATH | Use a specific snapshot file instead of the default. |
--fpm-default=VERSION | Override the default ea-phpXX, which is otherwise auto-detected. |
--help | Print usage. |
What does not carry over
LVE's io and iops caps translate into IO_WEIGHT, which is a relative weight rather than a hard cap. For per-tenant filesystem IO this behaves well. For per-thread IO throttling inside a shared mysqld it is not equivalent, because that requires a kernel patch we refuse to ship.
If specific tenants were IO-heavy enough that you relied on LVE's hard IO caps for their databases, promote them onto a dedicated per-tenant mysqld after the migration:
tenantctl mysql-promote-suggest # find the candidates tenantctl mysql-promote <user> # move one onto a dedicated mariadb@ instance
Promotion is free. Full detail in the MySQL governor.