Podman Support

Using Podman as the container engine for sfp-server deployments.

Docker is the default container engine for sfp-server. Podman is supported as an alternative through Docker Compose v2 running behind podman compose.

Select the engine with the --container-engine CLI flag or the SFP_CONTAINER_ENGINE environment variable. When set to podman, sfp-server uses the Podman adapter for runtime commands (login, volume creation, image management) and generates compose files with Podman-specific adjustments — Docker Hub image references are fully qualified, and bind-mount volumes are relabeled with :Z for SELinux compatibility.


[1] Prerequisites

The section is marked conceptual because it is a requirements checklist — no single surface to capture.

RequirementHow to check
Podman installedpodman --version
Docker Compose v2 installed as the Compose providerpodman compose version — must report Docker Compose v2, not podman-compose
Deploy user can run Podman rootlessly (or via sudo)podman ps without errors
SELinux graphroot label is not user_home_t (rootless RHEL/Fedora)ls -Zd $(podman info --format '{{.Store.GraphRoot}}') — should show data_home_t or container_var_lib_t. See 1.c
Private sfp-pro images require registry credentialsSee [1.a] below

podman-compose (the Python package) is not supported. sfp-server requires Docker Compose v2 as the Compose provider behind podman compose. Install the standalone docker-compose v2 binary if it is not already present.

[1.a] Registry credentials

sfp-server images are pulled from source.flxbl.io, a private registry. Podman needs credentials the same way Docker does — through the existing secrets / env / config flow described in Setting up sfp server → [1.1].

Log in before running sfp server init or sfp server start:

podman login source.flxbl.io -u <your-user> --password-stdin <<< "<your-token>"
Terminal output of podman login against source.flxbl.io with a piped token, ending with Login Succeeded
Authenticating Podman to the private source.flxbl.io registry.

[1.b] SELinux — bind mounts

On SELinux-enforcing hosts (RHEL, Fedora, CentOS Stream), bind mounts need the :Z label so containers can access host paths. When the container engine is set to podman, sfp-server's compose renderer adds :Z to bind-mount volumes automatically (it skips socket paths and already-labeled mounts).

If you still see Permission denied errors on bind-mounted paths, relabel the host directories manually:

chcon -Rt svirt_sandbox_file_t /opt/sfp-server/tenants/<tenant>/
Terminal output of chcon relabeling a tenant directory and ls -Zd confirming the svirt_sandbox_file_t label
Relabeling a bind-mounted tenant directory for SELinux.

[1.c] SELinux — graphroot labels (rootless Podman on RHEL)

Rootless Podman stores overlay layers under the user's graphroot — typically ~/.local/share/containers/storage. On standard home directories, SELinux labels this path correctly. However, when the home directory lives on a non-default mount point (/home/remote/, RAID, NFS, a custom partition), the graphroot inherits user_home_t instead of the expected data_home_t or container_var_lib_t label.

Containers that start from a user_home_t graphroot fail with:

  • glibc (Debian/Ubuntu-based images): cannot apply additional memory protection after relocation: Permission denied
  • musl/Alpine-based images: RELRO protection failed

These errors come from SELinux blocking mprotect() on the overlay filesystem — not from missing file permissions.

sfp server init --container-engine podman detects SELinux Enforcing mode automatically, resolves the graphroot path from podman info, and runs a best-effort restorecon to fix the labels. If restorecon succeeds, init continues normally.

restorecon may exit non-zero on some paths under the graphroot (notably overlay/*/diff directories) because rootless user-namespace UID remapping prevents the restorecon process from accessing files owned by mapped UIDs. This is expected — sfp server init runs restorecon -i to skip EPERM and missing-file errors on those overlay paths, then verifies the top-level graphroot label separately. Errors on the graphroot root directory itself are not ignored and will surface as an actionable error.

If the label persists as user_home_t after init, sfp throws an actionable error with the manual fix commands. Run these as root:

Derive the path from podman info:

podman info --format '{{.Store.GraphRoot}}'
# Example output: /home/remote/pdinh/.local/share/containers/storage
# The parent to relabel is: /home/remote/pdinh/.local/share/containers

Then relabel as root:

# Tell SELinux to treat the containers path like /var/lib/containers
sudo semanage fcontext -a -e /var/lib/containers \
  /home/<user>/.local/share/containers

# Apply the new context recursively
sudo restorecon -R -v /home/<user>/.local/share/containers

Verify the fix:

ls -Zd $(podman info --format '{{.Store.GraphRoot}}')
# Should show data_home_t or container_var_lib_t — NOT user_home_t
Terminal output of ls -Zd on the Podman graphroot showing data_home_t after the corrective restorecon
Verifying the graphroot label after the manual semanage + restorecon fix.

Images pulled before restorecon retain stale user_home_t labels on their overlay layers. After relabeling, prune all images and re-pull:

podman system prune -a -f --volumes
podman pull <your-images>

Skipping this step causes the same RELRO / mprotect failures even though the graphroot directory itself is now correctly labeled.


[2] Selecting Podman

The section is marked conceptual — it describes an environment switch and its effects, not a screen.

Pass --container-engine podman to sfp server init, or set the environment variable:

export SFP_CONTAINER_ENGINE=podman

sfp server init then writes SFP_CONTAINER_ENGINE=podman into the tenant's .env. Every later sfp server command re-reads that file, so a one-time --container-engine podman at init is enough — you do not need to pass the flag on start, update, or stop.

When set, sfp-server:

  • Uses podman for runtime commands (login, volume create, image prune)
  • Uses podman compose (backed by Docker Compose v2) for stack lifecycle
  • Fully qualifies Docker Hub image references (e.g. docker.io/library/postgres:...) in the generated compose file
  • Adds :Z to bind-mount volumes for SELinux compatibility
  • Rewrites the optional OpenTelemetry collector's mounts and log parsing from Docker to Podman socket/paths

Platform note (both engines): every service in the self-hosted compose pins platform: linux/amd64. On an arm64 host the engine pulls the amd64 child images and runs them under emulation — the Podman renderer changes image qualification and mount labels but never the platform. Prefer an x86-64 host.


[3] Compose provider

The section is an engine comparison, so it is marked conceptual; the verification command below is run once at install time.

podman compose delegates to an external Compose provider. sfp-server requires Docker Compose v2 as that provider.

ProviderSupported?Install
Docker Compose v2 (standalone binary)✅ yesSee Setting up Docker on the server → Post-Installation Optional Steps
podman-compose (Python)❌ noExplicitly rejected by sfp-server

Verify after installing:

podman compose version
# Must show Docker Compose v2.x

[4] Troubleshooting

The section is a diagnostics table and carries no single capture, so it is marked conceptual.

SymptomCauseFix
podman compose: command not found or no compose provider foundDocker Compose v2 not installed as the Compose backend for PodmanInstall the standalone docker-compose v2 binary — see [3] above. podman-compose is not supported.
Image pull fails with 401 Unauthorized or authentication requiredPrivate sfp-pro registry credentials missing or expiredpodman login source.flxbl.io — see [1.a] above; verify token has package: Read scope
Permission denied on bind-mount volumesSELinux blocking container access to host pathsVerify --container-engine podman is set (enables automatic :Z relabeling). If still failing: chcon -Rt svirt_sandbox_file_t <path> — see [1.b] above
Docker works but Podman fails on the same compose filePodman's Docker-compat layer has subtle differences (cgroup, network, rootless UID mapping)Confirm --container-engine podman is set so sfp-server uses the Podman adapter (image qualification, :Z labels). Check podman info for rootless vs rootful mode; run podman compose up with --verbose for the specific error
cannot apply additional memory protection after relocation: Permission deniedSELinux user_home_t label on the Podman graphroot (rootless, non-default home mount)Follow the manual fix in 1.c. Prune and re-pull images afterward.
RELRO protection failed (musl/Alpine images)Same cause — SELinux blocks mprotect() on user_home_t overlay layersSame fix as above. Alpine/musl reports the error differently but the cause is identical.
restorecon: unable to set context ... Permission denied on overlay diff paths during sfp server initRootless UID remapping prevents restorecon from accessing mapped-UID filesExpected — sfp server init skips EPERM on overlay diff paths and verifies the graphroot root label separately. If ls -Zd $(podman info --format '{{.Store.GraphRoot}}') shows data_home_t, the diff-path errors are harmless.
Containers still fail after running restorecon on the graphrootImages pulled before relabeling retain stale user_home_t on their overlay layerspodman system prune -a -f --volumes then re-pull all images. See the warning in 1.c.

On this page