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:
- Installing Podman and enabling its socket
- Transferring container images from Docker to Podman
- Updating the MOJO configuration to use Podman
- Restarting MOJO under Podman
- Verifying all services are running correctly
- (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.
Verify the installation:
Enable the Podman Socket¶
MOJO requires a container socket for certain services (e.g., Traefik proxy auto-discovery). Enable the rootful Podman socket:
Verify the socket is active:
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¶
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¶
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.
Option A: podman compose (Recommended for Podman 4.7+)¶
Podman 4.7+ includes built-in podman compose support. Verify:
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:
Expected output:
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_cgroupSELinux boolean - Sets
container_file_tcontext 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:
Rootless Podman
If running rootless Podman (not typical for MOJO), use:
Step 7: Stop Docker and Start with Podman¶
Stop the Docker-based MOJO¶
Verify all Docker containers are stopped:
Disable Docker¶
To prevent conflicts, disable the Docker daemon. Do not uninstall Docker yet in case a rollback is needed.
Start MOJO with Podman¶
The launcher will:
- Detect
CONTAINER_RUNTIME="podman"fromconfigs/mojo.env - Use the
podmanbinary and/run/podman/podman.socksocket - Set up the compose command (
podman composeordocker-composewith Podman socket) - Verify Podman v4+ is installed
- Run the SELinux setup if SELinux is detected
- 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:
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:
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:
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:
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:
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
- Migrate the secondary node first
- Verify replication is working
- Then migrate the primary node
On Each Node¶
Follow Steps 1-7 above on each node, then additionally:
-
Transfer the mojo-ha image (see Step 2 above)
-
Verify the HA image is available:
-
Rebuild the HA image if needed:
The build script auto-detects the available runtime (Docker or Podman).
-
Configure firewalld for HA (if not already done):
-
Start MOJO with HA:
The launcher will automatically start the
mojo-hacontainer ifHA_ENABLED=yis set inconfigs/mojo.env. -
Verify HA status:
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¶
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¶
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¶
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:
- Contact Support -- Reach our support team for technical assistance
- FAQ -- Answers to common questions
- Email: support@metify.io