Convert & Import
Turn an iocage or Bastille jail into an application, migrate a recognised application to its native catalog image, import a Linux workload, or export an application for a Linux host, in FreeCORE 15.1 (experimental).
Convert & Import is experimental. It moves a workload into Applications, or an application out to a Linux host, and never changes the source:
- Convert a jail as it is. An iocage or Bastille jail becomes an application: its software is lifted into an image, its state moves to datasets of its own, and its rc(8) services start inside the container the way they started in the jail. No knowledge of the software is needed.
- Migrate to the native catalog image. When a recipe recognises the software in a jail or in an exported Linux workload, the application runs the catalog's native FreeBSD image with the workload's data carried across and follows the catalog for updates.
- Import a Linux workload as it is. An exported Linux workload runs under the Linux binary compatibility setting.
- Export for Linux. An installed application is written out as a Docker Compose bundle for a Linux Docker or Compose host.
Nothing is decided for you: the converter writes a plan, you read and confirm it, and only then is it applied. A jail is read through a ZFS snapshot taken when the plan is written; every write lands on the Applications pool. A jail is never created, started or changed by the converter, and it stays exactly as it was after a completed conversion.
Enable It
Open Applications Settings and select Convert & Import (experimental). Planning, converting and exporting are refused while it is off. A migration that needs a Linux image on the way, or an import of a Linux workload as it is, also needs Linux binary compatibility (experimental), which is described in Applications. Converting a jail as it is does not: the result is a native FreeBSD application. Both settings are off by default.
Convert a Jail
Open Applications, choose Convert jail… on the Catalog tab, and pick the jail. The Jails page offers the same for an iocage jail through the row action Convert to application. The panel reads the jail — Release, Root, Packages, and the Recipe that recognises it, if any — and proposes the ports to publish: what the jail listens on when it is running, or the catalog entry's ports when a package matches one. A host port left empty is not published.
Choose the target. As it is is the default whenever the jail can be lifted. Migrate to the native catalog image is offered only when a recipe recognised the jail. Prepare plan writes the plan, grouped as Refused, Carried, Rewritten, Migration step, Notes and Not carried. Convert runs it after you confirm you have read it. The result is listed on Installed, where its Source reads Converted; the jail is still there and still starts.
A running jail can be converted as it is: its snapshot is crash-consistent, like a backup, and the plan says so. Stop the jail first for a clean copy of any database. A migration to the native image needs the jail stopped when the plan is applied.
The as-is conversion is refused for a jail whose root is not a ZFS dataset, an iocage template, a jail whose base release is not present on the system, and a cloned jail whose origin snapshot is not mounted. Such a jail cannot be selected in the panel.
What the as-is conversion produces:
| Part | Where it goes |
|---|---|
| The software | A thick jail becomes one image layer. A cloned, base or thin jail becomes a layer of what it added to its release, on a base layer shared by every conversion from that release. |
/var, /usr/local/etc, /root and the home directories |
Datasets owned by the application, mounted over the image. The contents of /var/run, /var/tmp and /var/cache/pkg are not carried. Delete can keep these datasets. |
| Datasets the jail mounted through its fstab | Bound at the same paths, read-only or read-write as the jail had them. Only nullfs mounts of datasets carry; any other mount stops the plan. |
allow.raw_sockets, allow.mlock, System V IPC |
Carried as container annotations. Mount and device allowances are not granted to applications and are listed as not carried. A devfs ruleset other than the default is noted and not carried. |
The jail's network identity in rc.conf |
Removed: the ifconfig_*, defaultrouter, ipv6_defaultrouter, hostname and dhclient_* lines. The application lives on the Applications bridge and answers on the host's address and the published ports. If a configuration file under /usr/local/etc names the jail's old address, the plan says which. |
| Ports | Published as you chose them. |
The application runs /etc/rc when it starts and /etc/rc.shutdown when it
stops, exactly as jail(8) did, so cron, syslog and every enabled service
behave as they did in the jail. A jail that starts with another command gets
a note. Application logs show what rc(8) printed; the services' own logs are
in /var/log on the var dataset. A jail on an older release, such as 13.x,
converts as it is: its own userland is in the image and runs under the
FreeCORE kernel.
Deleting a converted application removes its image. The shared base layer of the release stays for the next conversion.
Recipes
A recipe holds what the converter knows about one application: where the software keeps its data and configuration in a jail and in the Linux images it names, which settings are carried into the native image and which are owned by that image and therefore left behind, what must be refused, and what a container cannot keep from a jail. FreeCORE 15.1-RC6 ships 30 recipes.
| Status | Recipes | Sources |
|---|---|---|
| Proven | Grafana, Plex, Sonarr, Syncthing, Vaultwarden | Jail or Linux export |
| Draft | Bazarr, Caddy, Emby, HAProxy, Homepage, Jellyfin, Lidarr, Navidrome, Prowlarr, qBittorrent, Radarr, Redis, SABnzbd, Tailscale, Tautulli, Transmission | Jail or Linux export |
| Draft | AdGuard Home, Audiobookshelf, code-server, Heimdall, Home Assistant, Mealie, n8n, NocoDB, Uptime Kuma | Linux export |
A draft recipe's paths were reviewed, but the migration has not been verified by FreeCORE; the panel and the plan say so. A jail that no recipe recognises is converted as it is; a Linux workload that no recipe recognises can be imported as it is.
Each recipe accepts only the Linux images whose data layout it knows. The LinuxServer and hotio images of Jellyfin and qBittorrent, and the hotio image of Plex, keep their data in a different layout, so no recipe recognises them: they cannot migrate to the native image, and with Linux binary compatibility on they can be imported as they are.
Two rules apply to every recipe. A migration never downgrades: if the native image ships an older version than the source, the plan refuses, and when the source version cannot be read the plan says so. The plan lists everything it leaves behind, such as logs, cron entries, packages installed beside the application, sidecar services and TLS certificate files.
Import a Linux Workload
In 15.1-RC6 the web interface converts jails. A Linux workload is converted
through the API (see below) from an export bundle: a directory below /mnt
on the system, holding the compose file of one service and the data it
mounted.
compose.yaml the service definition (compose.yml, docker-compose.yaml
and docker-compose.yml are accepted)
./<bind directory>/ every bind-mounted host directory, relative to the compose file
./<volume>.tar every named volume, one archive each
On the source host, copy each bind-mounted directory beside the compose file, and export each named volume with one command:
docker run --rm -v <volume>:/from -v "$PWD":/to alpine tar -C /from -cf /to/<volume>.tar .
Resolve any env_file into the environment section before exporting; the
bundle must be self-contained. Place the directory on a dataset, for example
/mnt/tank/exports/grafana. One service per bundle; a compose file with
several services is refused with the list of services. A compressed archive
of the bundle is refused; extract it first.
A recipe's data directory may sit inside a bind-mounted directory, such as
Plex's data under /config; it is read from there. Inside a named-volume
archive it is not, and the plan refuses with that reason. The source version
is taken from the image tag, or, for :latest or an untagged image, from the
image's version label; without either, the plan notes that the version is
unknown.
The Import image… form on the Applications page can also be filled from a compose file; that installs the image with empty storage and carries no data. See Applications.
Export for Linux
Export for Linux in an installed application's actions, shown while Convert & Import is on, writes the application as a Docker Compose bundle for a Linux Docker or Compose host. The panel first shows the plan, grouped as Refused, Carried, Rewritten, Notes and Not carried, and names its kind:
| Kind | Meaning |
|---|---|
| state carried | A recipe knows where the application keeps its state and which Linux image reads it. The state is copied into that image's layout. The image tag is the installed version where the recipe knows the tag, otherwise latest; an image older than the installed version is refused, and when the version cannot be read the plan says so. |
| same Linux image | An application running a Linux image under Linux binary compatibility. The bundle names the same image and carries the application's own volumes. |
| fresh install, no state | No recipe target. The bundle names the Linux image and the settings and carries no state. |
A jail converted as it is cannot be exported: its software is a FreeBSD userland with no Linux counterpart. The bundle publishes the application's ports; a LAN attachment, jail properties and the Applications health probe are not carried. External datasets are named at the same container paths and are not copied. Ownership is expressed in the compose file, or as a command to run on the Linux host.
| Field | Description |
|---|---|
| Folder | An existing folder inside a dataset, such as /mnt/pool/exports; not a pool root and not inside the Applications, iocage or Bastille stores. A folder named after the application is created inside it; nothing is overwritten. |
| Mode | Bundle: copy the state into the folder (default), or In place: point at the datasets (moving the whole pool), which copies nothing and names the application's own dataset paths in the compose file. |
| Folder on the Linux host (optional) | Bundle mode only. Where the folder will live on the Linux host; the compose file then uses full paths under it. |
| Copy while the application keeps running | Off by default: the application is stopped for the moment a snapshot is taken, then started again. With it on, the copy is crash-consistent. |
Confirm that you reviewed the plan and click Export. The job checks the
free space, copies the state from the snapshot, and writes compose.yaml and
README.export, which lists what was carried, dropped and rewritten and any
command to run on the Linux host first. Start the bundle on the Linux host with
docker compose up -d in its folder. The snapshot is destroyed afterwards, a
failed export removes the folder it started, and the application itself is
not changed. Never run the FreeCORE application and the Linux service against
the same data at once.
The API
Every step is a call to applications.convert: sources lists the jails of
both engines; detect reads one source and proposes its ports; plan writes
the plan for a source ({"kind": "jail", "jail": "grafana", "engine": "iocage"},
{"kind": "linux-export", "path": "/mnt/tank/exports/grafana"} or
{"kind": "linux-image", "image": "<reference>"}) with the options name, mode (lift, native or
compat) and ports; plans lists the written plans; apply runs a
confirmed plan as a job; discard forgets a plan; lookup names the
native application and recipe a Linux image corresponds to; recipes lists the
recipes; read_compose reads a compose file into an import form;
export_plan, export_plans and export plan and write an export. The plan
is a document of typed entries, each with its own explanation:
| Entry | Meaning |
|---|---|
| carry | A file, directory, dataset or setting that goes into the application. |
| rewrite | Something that changes on the way, such as file ownership, a translated configuration, or the network lines of rc.conf. |
| drop | Something the application will not have, listed so nothing disappears silently. |
| note | Something to know before applying, such as a running source or an address the configuration still names. |
| refuse | A condition that stops the conversion. A plan with a refusal cannot be applied. |
| hop | An intermediate step the converter runs once against the staged data, for example a version upgrade the native image can no longer perform itself. |
For a jail, planning takes a ZFS snapshot of the jail's root dataset; that snapshot is what a later apply reads, so the jail cannot change underneath the plan. Applying or discarding the plan destroys the snapshot. For an export bundle, the plan records a fingerprint of the bundle and refuses to apply if the bundle changed. A failed apply keeps the plan, so it can be retried after the cause is fixed, and removes the storage and image it had created.
Grafana
The Grafana recipe migrates a jail running the FreeBSD grafana package, or
an export of the official grafana/grafana or grafana/grafana-oss image,
into the catalog's Grafana application. Grafana Enterprise is refused. The
database, rendered images and provisioning files are carried; frontend-only
plugins are carried and backend plugins are listed as dropped because they are
binaries for another system. The configuration is carried through an allow
list: the encryption key, server URL and domain, mail, authentication and user
settings come across, while paths, alerting, logging and port settings are
owned by the image and left behind. With an external database, the settings
are carried and the data stays where it is.
Grafana 11 and later cannot migrate the legacy dashboard alerts of Grafana 8 and 9. When the source still has them, the plan adds a hop through the official Grafana 10.4 Linux image, which is the last version able to migrate them; the hop needs the Linux binary compatibility setting. Dashboards that use AngularJS panels are listed in the plan: Grafana migrates its own legacy panels on first load, third-party AngularJS plugins do not render any more.
After the application starts, sign in and check your dashboards, data sources and alert rules before removing the source jail or export.