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.

Air-Gapped Environments

This guide assumes your MOJO host can reach a container image registry (download.metify.io or your organization's internal registry). If your host has no outbound registry access, contact support@metify.io for air-gapped migration instructions.


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. Setting up compose support
  3. Updating the MOJO configuration to use Podman
  4. Configuring SELinux and database permissions
  5. Stopping Docker and starting MOJO under Podman (images are pulled fresh from the registry)
  6. Verifying all services are running correctly
  7. (Optional) Removing Docker

Since your host can reach the container registry, there is no need to manually transfer images from Docker to Podman. Podman will pull fresh images directly when MOJO starts.


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
Registry access Host can reach download.metify.io or your organization's internal container registry

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.

Active Directory / LDAP Users

If you have AD/LDAP integration enabled and previously placed CA certificates directly inside the Docker container (e.g., at /etc/ldap/certs/), those files will be lost when Podman pulls fresh images. See AD/LDAP Certificate Persistence below for the proper fix.


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 2 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: 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 3: 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.

Internal Registry

If your organization mirrors MOJO images to an internal registry (e.g., Artifactory, Harbor), verify that your image URLs in configs/mojo.env point to the correct registry. Log in to your registry with Podman before starting MOJO:

sudo podman login <your-registry-url>

Step 4: 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 5: 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 6: 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

Use the --update flag so the launcher pulls fresh images into Podman from the registry:

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

This will authenticate with the configured registry, pull all MOJO images into Podman's image store, and start the platform. The initial pull may take several minutes depending on your connection speed.

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. Pull all MOJO container images from the registry
  7. 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 7: 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}}'

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.

AD/LDAP Certificate Persistence

Symptom: After migrating to Podman, AD/LDAP login fails. The coordinator log (volumes/app/logs/uwsgi.log) shows:

ignoring invalid AD cert dir /etc/ldap/certs/cert.pem

or AD logins simply fail while local admin logins still work.

Cause: If your AD CA certificate was manually placed inside the Docker container's filesystem (e.g., by running docker cp cert.pem mojo-app:/etc/ldap/certs/), that file only existed in the container's writable layer. When Podman pulls a fresh image, the container starts from scratch and the certificate is gone.

Fix: Place the certificate on the host in the persistent volume directory, which is mounted into the container automatically:

cd /opt/mojo

# Create the LDAP certs directory (if it doesn't already exist)
sudo mkdir -p volumes/app/ldap-certs

# Copy your AD CA certificate into the host directory
sudo cp /path/to/your/ca-cert.pem volumes/app/ldap-certs/

Then set the environment variable in configs/mojo.env to point to the directory (not the file) inside the container:

# Edit configs/mojo.env
MOJO_LDAP_CERT_DIR="/etc/ldap/certs"

Directory, Not File

MOJO_LDAP_CERT_DIR must point to a directory containing your certificate(s), not to a specific file. Setting it to /etc/ldap/certs/cert.pem (a file path) will be rejected by the coordinator.

Restart MOJO to apply:

sudo ./mojo-launcher restart mojo

Verify by checking the logs:

sudo podman logs --tail 50 mojo-app 2>&1 | grep -i "cert dir"
# Should show: "using AD cert dir /etc/ldap/certs"

The volumes/app/ldap-certs/ directory on the host is mounted into the container at /etc/ldap/certs/, so your certificates will persist across container restarts, image updates, and future migrations.

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-6 above on each node, then additionally:

  1. Verify the HA image is available after pulling:

    sudo podman images | grep mojo-ha
    
  2. 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).

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

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

    sudo ./mojo-launcher start mojo --update
    

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

  5. 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 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: