Skip to content

Migrating from Docker to Podman

This guide walks you through migrating an existing MOJO Platform installation from Docker to Podman on a RHEL-based system (RHEL 8/9, Rocky Linux, AlmaLinux).

Why Podman?

Podman is daemonless, rootless-capable, and included in the base RHEL repositories. It is the preferred container runtime on Red Hat Enterprise Linux and its derivatives.


Overview

The MOJO installer natively supports Docker, Podman, and nerdctl as container runtimes. Migrating from Docker to Podman involves:

  1. Installing Podman and enabling its socket
  2. Transferring container images from Docker to Podman
  3. Updating the MOJO configuration to use Podman
  4. Restarting MOJO under Podman
  5. Verifying all services are running correctly
  6. (Optional) Removing Docker

Prerequisites

Requirement Details
Existing MOJO installation Running on Docker (typically at /opt/mojo)
Operating system RHEL 8/9, Rocky Linux 8/9, or AlmaLinux 8/9
Access Root or sudo
Disk space Sufficient for temporarily holding both Docker and Podman images (~2x current image storage)

Pre-Migration Checklist

Before starting the migration, verify and record the following:

# Confirm your MOJO installation location
MOJO_DIR="/opt/mojo"   # Change to your actual MOJO path
ls ${MOJO_DIR}/mojo-launcher

# Verify Docker is the current runtime
sudo docker ps

# Record running container names and images
sudo docker ps --format '{{.Names}}\t{{.Image}}'

# Check current MOJO configuration
sudo grep CONTAINER_RUNTIME ${MOJO_DIR}/configs/mojo.env

# Backup the entire MOJO configuration
sudo cp -a ${MOJO_DIR}/configs ${MOJO_DIR}/configs.backup.$(date +%Y%m%d)

Back Up First

Always back up your configs/ directory before making changes. This backup is also used by the rollback procedure at the end of this guide.


Step 1: Install Podman

Podman is available in the default RHEL repositories.

sudo dnf install -y podman podman-compose
sudo dnf install -y podman
# Note: podman-compose may need to be installed separately on RHEL 8
# See Step 3 for compose setup

Verify the installation:

podman --version
# Should show podman version 4.x or higher (v4+ is required by MOJO)

Enable the Podman Socket

MOJO requires a container socket for certain services (e.g., Traefik proxy auto-discovery). Enable the rootful Podman socket:

sudo systemctl enable --now podman.socket

Verify the socket is active:

sudo systemctl status podman.socket
ls -la /run/podman/podman.sock

The socket should exist at /run/podman/podman.sock. This is the path MOJO will use automatically.


Step 2: Transfer Docker Images to Podman

All MOJO container images currently stored in Docker must be transferred to Podman using docker save and podman load.

List Current MOJO Images

sudo docker images | grep -E "metify|mojo|local/"

You should see images like:

download.metify.io/mojo/coordinator          latest     ...
download.metify.io/mojo/yoda-gui             latest     ...
download.metify.io/mojo/postgres             16.3       ...
download.metify.io/mojo/redis                7.2.4      ...
download.metify.io/mojo/consoler             1.0        ...
download.metify.io/mojo/tor/traefik          3.6.7      ...
download.metify.io/mojo/tor/nginx            1.25.3     ...
download.metify.io/mojo/tor/dhcpd            1.0        ...
download.metify.io/mojo/tor/tftpd            1.0        ...
download.metify.io/mojo/tor/pxe-helper       1.0        ...
download.metify.io/mojo/tor/grub-builder     1.0        ...
download.metify.io/mojo/tor/ipxe-builder     c1834f-1.0 ...
local/mojo-proxy                             latest     ...

Note

Replace all image names and tags below with the exact values from your docker images output. If you are using a release channel other than stable (e.g., rc or develop), your coordinator and GUI tags will differ.

Transfer All Images

# Create a temporary directory for image tarballs
mkdir -p /tmp/mojo-migration

# Save all MOJO images from Docker (this may take several minutes)
echo "Saving MOJO images from Docker..."
sudo docker save \
  download.metify.io/mojo/coordinator:latest \
  download.metify.io/mojo/yoda-gui:latest \
  download.metify.io/mojo/postgres:16.3 \
  download.metify.io/mojo/redis:7.2.4 \
  download.metify.io/mojo/consoler:1.0 \
  download.metify.io/mojo/tor/traefik:3.6.7 \
  download.metify.io/mojo/tor/nginx:1.25.3 \
  download.metify.io/mojo/tor/dhcpd:1.0 \
  download.metify.io/mojo/tor/tftpd:1.0 \
  download.metify.io/mojo/tor/pxe-helper:1.0 \
  download.metify.io/mojo/tor/grub-builder:1.0 \
  download.metify.io/mojo/tor/ipxe-builder:c1834f-1.0 \
  -o /tmp/mojo-migration/mojo-images.tar

echo "Loading MOJO images into Podman..."
sudo podman load -i /tmp/mojo-migration/mojo-images.tar

Transfer the Local Proxy Image

The local/mojo-proxy image is built locally by mojo-launcher and must also be transferred:

sudo docker save local/mojo-proxy:latest -o /tmp/mojo-migration/mojo-proxy.tar
sudo podman load -i /tmp/mojo-migration/mojo-proxy.tar

Handle the docker.io/ Prefix

Image Tag Mismatch

When Podman loads images saved from Docker, it may prefix the repository name with docker.io/. For example, local/mojo-proxy:latest may appear as docker.io/local/mojo-proxy:latest in Podman.

Verify and fix image tags:

# Check how images were tagged after loading
sudo podman images | grep -E "metify|mojo|local/"

# If local images have the docker.io/ prefix, re-tag them:
sudo podman tag docker.io/local/mojo-proxy:latest localhost/local/mojo-proxy:latest

Images from fully qualified registries (e.g., download.metify.io/mojo/*) should retain their correct tags.

Transfer HA Image (If Applicable)

If your installation uses High Availability, also transfer the HA image:

# Check if the HA image exists
sudo docker images | grep mojo-ha

# If it exists, transfer it
sudo docker save local/mojo-ha:latest -o /tmp/mojo-migration/mojo-ha.tar
sudo podman load -i /tmp/mojo-migration/mojo-ha.tar

# Fix tag if needed
sudo podman tag docker.io/local/mojo-ha:latest localhost/local/mojo-ha:latest

Verify All Images

sudo podman images | grep -E "metify|mojo|local/"

Ensure all images listed in your original docker images output are now present in Podman.


Step 3: Set Up Compose Support

MOJO uses Docker Compose syntax for orchestrating its services. Podman needs compose support configured.

Podman 4.7+ includes built-in podman compose support. Verify:

sudo podman compose version

If this works, no additional setup is needed.

Option B: Install docker-compose as Fallback

If podman compose is not available, install the standalone docker-compose binary. MOJO's launcher will automatically detect and use it with the Podman socket:

# Download docker-compose v2
curl -L "https://github.com/docker/compose/releases/download/v2.21.0/docker-compose-$(uname -s)-$(uname -m)" \
  -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

# Verify
/usr/local/bin/docker-compose version

When using docker-compose with Podman, mojo-launcher automatically sets DOCKER_HOST to point to the Podman socket.


Step 4: Update MOJO Configuration

Edit configs/mojo.env

Update the container runtime setting:

cd /opt/mojo

# Update CONTAINER_RUNTIME from "docker" (or "auto") to "podman"
sudo sed -i 's/^CONTAINER_RUNTIME=.*/CONTAINER_RUNTIME="podman"/' configs/mojo.env

# Clear any hardcoded socket path (let MOJO auto-detect)
sudo sed -i 's/^CONTAINER_SOCKET_PATH=.*/CONTAINER_SOCKET_PATH=""/' configs/mojo.env

Verify the changes:

sudo grep -E "CONTAINER_RUNTIME|CONTAINER_SOCKET_PATH" configs/mojo.env

Expected output:

CONTAINER_RUNTIME="podman"
CONTAINER_SOCKET_PATH=""

Auto-Detection

When CONTAINER_SOCKET_PATH is empty, mojo-launcher auto-detects the correct socket. For rootful Podman, this will be /run/podman/podman.sock.


Step 5: Configure SELinux

RHEL systems typically have SELinux enabled. MOJO includes an SELinux setup script that configures the necessary policies for Podman.

cd /opt/mojo

# Check SELinux status
getenforce

# If SELinux is Enforcing or Permissive, run the SELinux setup script
sudo bash src/setup_selinux.sh

This script:

  • Enables container_manage_cgroup SELinux boolean
  • Sets container_file_t context on all MOJO volume directories (volumes/db, volumes/app, volumes/osimages, etc.)
  • Sets the correct context on the Podman socket

If the SELinux script is not present in your installation, manually apply the essential settings:

# Enable container cgroup management
sudo setsebool -P container_manage_cgroup on

# Set SELinux context on volume directories
for dir in volumes/db volumes/app volumes/consoler volumes/osimages \
           volumes/customizations volumes/firmwares volumes/tftpd \
           volumes/dhcpd configs bootstraps; do
    if [ -d "$dir" ]; then
        sudo semanage fcontext -a -t container_file_t "$(pwd)/${dir}(/.*)?" 2>/dev/null || true
        sudo restorecon -R "$dir" 2>/dev/null || true
    fi
done

# Set context for Podman socket
sudo chcon -t container_file_t /run/podman/podman.sock 2>/dev/null || true

Step 6: Fix Database Directory Permissions

Podman handles user namespaces differently than Docker. The PostgreSQL data directory requires specific ownership:

cd /opt/mojo

# For rootful Podman, standard chown works
sudo chown 999:999 volumes/db

Rootless Podman

If running rootless Podman (not typical for MOJO), use:

podman unshare chown 999:999 volumes/db


Step 7: Stop Docker and Start with Podman

Stop the Docker-based MOJO

cd /opt/mojo

# Stop MOJO using the current Docker runtime
sudo ./mojo-launcher stop mojo

Verify all Docker containers are stopped:

sudo docker ps | grep mojo
# Should return nothing

Disable Docker

To prevent conflicts, disable the Docker daemon. Do not uninstall Docker yet in case a rollback is needed.

sudo systemctl stop docker docker.socket
sudo systemctl disable docker docker.socket

Start MOJO with Podman

cd /opt/mojo
sudo ./mojo-launcher start mojo

The launcher will:

  1. Detect CONTAINER_RUNTIME="podman" from configs/mojo.env
  2. Use the podman binary and /run/podman/podman.sock socket
  3. Set up the compose command (podman compose or docker-compose with Podman socket)
  4. Verify Podman v4+ is installed
  5. Run the SELinux setup if SELinux is detected
  6. Launch all MOJO services

Verify Services

# List all running MOJO containers
sudo podman ps

# Check MOJO status through the launcher
sudo ./mojo-launcher status mojo

You should see all MOJO containers running:

  • mojo-app (coordinator)
  • mojo-gui (web UI)
  • mojo-db (PostgreSQL)
  • mojo-redis (Redis)
  • mojo-proxy (Traefik)
  • mojo-dhcpd (DHCP server)
  • mojo-tftpd (TFTP server)
  • mojo-pxe-helper (PXE configuration)
  • mojo-static-server-* (static file servers)
  • mojo-consoler (remote console)

Verify Web Access

Open a browser and navigate to https://<MOJO_HOSTNAME>. Confirm the MOJO web interface loads and you can log in.


Step 8: Configure Autostart (Optional)

If MOJO was configured to start on boot with Docker, update the systemd service:

cd /opt/mojo

# Re-enable autostart (creates a new systemd unit with podman.socket dependency)
sudo ./mojo-launcher enable-autostart

This creates a systemd service that depends on podman.socket instead of docker.service.


Known Issues and Workarounds

mojo-manage --env-file Error

Symptom: Running mojo-manage commands fails with:

Error: unknown flag: --env-file

Cause: Podman Compose does not support the --env-file flag the same way Docker Compose does.

Workaround: Use direct podman exec commands instead of mojo-manage for troubleshooting:

# Instead of: sudo ./mojo-manage --logs
sudo podman logs mojo-app

# Instead of: sudo ./mojo-manage --logs --tail 100 app
sudo podman logs --tail 100 mojo-app

# Instead of: sudo ./mojo-manage --app-command "..."
sudo podman exec -it mojo-app <command>

Podman Flag Ordering

Podman requires options like --tail to be placed before the container name:

# Correct:
sudo podman logs --tail 100 mojo-app

# Incorrect (will fail with "no container with name or ID '--tail' found"):
sudo podman logs mojo-app --tail 100

Container Naming Differences

Container names may vary between installations. On some Podman installations, containers use names without a -1 suffix (e.g., mojo-app), while others may use the suffix (e.g., mojo-app-1).

Always verify actual container names:

sudo podman ps --format '{{.Names}}'

Image Registry Prefix

When transferring images from Docker to Podman, images may get prefixed with docker.io/. If a service fails to start because it cannot find an image:

# Check the actual image name in Podman
sudo podman images | grep <image-name>

# Re-tag if necessary
sudo podman tag docker.io/local/mojo-proxy:latest local/mojo-proxy:latest

Celery Worker PermissionError (SemLock)

Symptom: After migrating to Podman, the mojo-app container logs show Celery worker crashes with:

PermissionError: [Errno 13] Permission denied

in the billiard/SemLock stack trace.

Cause: Podman's default IPC namespace isolation prevents Celery's multiprocessing from accessing shared memory semaphores.

Fix: Add ipc: host to the mojo-app service in mojo.yml:

cd /opt/mojo
sudo sed -i '/container_name: mojo-app/a\    ipc: host' mojo.yml
sudo ./mojo-launcher restart mojo

Verify with:

sudo podman logs --tail 100 mojo-app
# Should show clean Celery worker startup without PermissionError

Note

This manual mojo.yml edit will be overwritten on the next mojo-setup run. A permanent fix has been added to the installer for future releases.

Container DNS Resolution

If containers cannot resolve external hostnames but the host machine can, configure Podman's DNS servers in /etc/containers/containers.conf:

[containers]
dns_servers = ["8.8.8.8", "8.8.4.4"]

Replace the DNS servers above with your organization's internal DNS servers if applicable. Restart MOJO after making this change.

Log Extraction

To bulk-copy coordinator logs out of the container for analysis:

# Copy all log files at once (much faster than grep/tail through podman logs)
sudo podman cp mojo-app:/var/log/coordinator /tmp/coordinator-logs
ls /tmp/coordinator-logs/

HA-Specific Migration Steps

If your MOJO installation uses High Availability (HA), additional steps are required.

Important: One Node at a Time

  1. Migrate the secondary node first
  2. Verify replication is working
  3. Then migrate the primary node

On Each Node

Follow Steps 1-7 above on each node, then additionally:

  1. Transfer the mojo-ha image (see Step 2 above)

  2. Verify the HA image is available:

    sudo podman images | grep mojo-ha
    
  3. Rebuild the HA image if needed:

    cd /opt/mojo
    sudo bash ha/scripts/build_mojo_ha.sh
    

    The build script auto-detects the available runtime (Docker or Podman).

  4. Configure firewalld for HA (if not already done):

    sudo firewall-cmd --add-protocol=vrrp --permanent
    sudo firewall-cmd --reload
    
  5. Start MOJO with HA:

    sudo ./mojo-launcher start mojo
    

    The launcher will automatically start the mojo-ha container if HA_ENABLED=y is set in configs/mojo.env.

  6. Verify HA status:

    sudo ./mojo-launcher ha-status
    # or
    sudo bash /opt/mojo/ha/scripts/ha_status.sh
    

Rollback Procedure

If the migration encounters issues, you can roll back to Docker.

Revert Configuration

cd /opt/mojo

# Stop Podman MOJO
sudo ./mojo-launcher stop mojo

# Restore the backed-up configuration
sudo cp configs.backup.*/mojo.env configs/mojo.env

# Or manually revert the runtime setting
sudo sed -i 's/^CONTAINER_RUNTIME=.*/CONTAINER_RUNTIME="docker"/' configs/mojo.env

# Re-enable Docker
sudo systemctl enable --now docker docker.socket

# Start MOJO with Docker
sudo ./mojo-launcher start mojo

Verify Rollback

sudo docker ps | grep mojo

All MOJO containers should be running under Docker again.


Post-Migration Cleanup

After confirming MOJO runs correctly on Podman for at least 24-48 hours:

Remove Temporary Migration Files

rm -rf /tmp/mojo-migration

Remove Docker (Optional)

Only remove Docker after confirming the migration is fully successful:

sudo dnf remove docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo rm -rf /var/lib/docker

Remove Configuration Backup

sudo rm -rf /opt/mojo/configs.backup.*

Quick Reference: Docker vs Podman Commands

Task Docker Podman
List containers sudo docker ps sudo podman ps
View logs sudo docker logs mojo-app sudo podman logs mojo-app
Tail logs sudo docker logs --tail 100 mojo-app sudo podman logs --tail 100 mojo-app
Exec into container sudo docker exec -it mojo-app bash sudo podman exec -it mojo-app bash
List images sudo docker images sudo podman images
Pull image sudo docker pull <image> sudo podman pull <image>
Stop a container sudo docker stop mojo-app sudo podman stop mojo-app
Copy files out sudo docker cp mojo-app:/path /local sudo podman cp mojo-app:/path /local
Registry login sudo docker login <registry> sudo podman login <registry>
Start MOJO sudo ./mojo-launcher start mojo sudo ./mojo-launcher start mojo
Stop MOJO sudo ./mojo-launcher stop mojo sudo ./mojo-launcher stop mojo
Update MOJO sudo ./mojo-launcher start mojo --update sudo ./mojo-launcher start mojo --update

Info

All MOJO launcher commands (mojo-launcher, mojo-setup) remain the same regardless of the container runtime. Only direct container CLI commands change from docker to podman.


Support

If you have any questions or concerns about migrating to Podman, we're here to help: