Running JIM on Podman¶
JIM runs on Podman as well as Docker, from the same images, with the same settings and the same hardening. Podman is the container engine Red Hat ships and supports on RHEL 8, 9 and 10, so this is the way to run JIM where Docker is not permitted. Podman needs no extra software: no Docker engine, no compose tool.
The installer sets everything on this page up for you. Read on to understand what it sets up, to operate JIM day to day, or to install by hand.
Requirements¶
- Podman 4.4 or later, with systemd. RHEL 9 and 10 ship Podman 5. systemd starts JIM at boot through Podman's Quadlet, which arrived in Podman 4.4. Without systemd JIM still runs, but does not start by itself after a reboot (see Without systemd).
- Everything else in the Deployment Guide's prerequisites: hardware, OpenSSL, an OIDC identity provider.
How JIM Runs on Podman¶
JIM runs as two pods on a network named jim:
| Pod | Containers | systemd unit |
|---|---|---|
jim |
jim-web, jim-worker, jim-scheduler |
jim.service |
jim-database |
jim-database-postgres (optional) |
jim-database.service |
JIM reaches the bundled PostgreSQL at the host name jim-database. With your own PostgreSQL server, there is no database pod and no jim-database.service.
The pods are Kubernetes-style YAML files, which Podman runs with its built-in podman kube play. They are not Kubernetes manifests: JIM does not support Kubernetes or OpenShift.
| File | What it holds | Yours to edit? |
|---|---|---|
/opt/jim/jim.yaml |
The JIM pod | No: an upgrade replaces it |
/opt/jim/jim-database.yaml |
The PostgreSQL pod, and its tuning | Only to tune PostgreSQL |
/opt/jim/jim-config.yaml |
JIM's settings, with the same names as the Docker path's .env (see the Configuration Reference) |
Yes |
/opt/jim/tls/ |
JIM's certificate and key, and the installer's certificate authority | Through setup.sh --certificate |
jim.kube, jim-database.kube, jim.network |
The Quadlet units systemd runs the pods from, including the HTTPS port (PublishPort=443:8443 in jim.kube) |
The port only |
The Quadlet units are in /etc/containers/systemd/, or in ~jim/.config/containers/systemd/ for a rootless installation.
JIM's secrets are not in any of these files. The database password, your identity provider's client secret and the optional infrastructure API key are the Podman secret jim-secrets; JIM's certificate and key are the Podman secret jim-tls. Podman keeps secrets in its secret store, readable only by root (or, rootless, by the account that runs JIM): the same exposure as the Docker path's .env, which only root can read.
JIM's data is in named volumes with the same names as on Docker: jim-db-volume, jim-logs-volume, jim-keys-volume and jim-connector-files-volume. You also see a volume named jim-tls, which Podman fills from the secret of the same name each time JIM starts.
Rootful or Rootless¶
By default the installer runs JIM rootful: Podman runs as root, as Docker does. JIM's containers still run as a non-root user, with every capability dropped and a read-only root file system, exactly as on Docker.
To run JIM rootless instead, pass --rootless to the installer. JIM then runs under a dedicated account named jim, which the installer creates with no login shell, a range of subordinate user IDs for its containers, and lingering enabled, so that the account's own systemd manager runs JIM with nobody logged in and starts it at boot. Rootless adds a second boundary: a process that escaped a container would land in an account that owns nothing else on the server. Some security policies require it.
Choose on these differences:
| Rootful (default) | Rootless | |
|---|---|---|
| The client address JIM records, for audit events and for rate-limiting unauthenticated requests | ✅ Each client's own address | ❌ One internal address for every client: Podman's rootless port forwarding does not pass on the client's address |
| A process escaping a container | Is JIM's container user, or root if it escapes that too | Is the unprivileged jim account |
| Port 443 | Nothing needed | The installer lets unprivileged programs use ports from 443 up (net.ipv4.ip_unprivileged_port_start=443 in /etc/sysctl.d/90-jim.conf) |
| Operating JIM | As root | Through the jim account (see Rootless commands) |
| File Connector folders on the host | Owned by UID 1654, as on Docker |
Owned by a subordinate ID of jim (see File Access) |
Rootless JIM cannot tell clients apart
Every request to a rootless JIM appears to come from one internal address. Its security audit events then record that address rather than the client's, and unauthenticated API requests from every client share one rate limit. Do not set JIM_TRUSTED_PROXIES to that address as a workaround: every client shares it, so any client could then claim any address. If your audit requirements need each client's address, keep the default, rootful.
The choice is made at installation. A reinstall keeps it, since JIM's images, secrets and data are in the Podman storage of the account that runs it; moving an installation between rootful and rootless means installing afresh and restoring a backup.
Operating JIM¶
# Status, restart, stop and start
sudo systemctl status jim.service
sudo systemctl restart jim.service
# JIM's containers, their logs and health
sudo podman ps
sudo podman logs -f jim-web
sudo podman logs jim-worker
sudo podman healthcheck run jim-web && echo "JIM is ready"
Restarting jim.service restarts the web, worker and scheduler together; jim-database.service runs the bundled PostgreSQL separately. Both start at boot. The installer's copy, /opt/jim/setup.sh, looks after the certificate as it does on Docker (see Renewing the Certificate); on Podman, installing a new certificate restarts the whole JIM pod, so do it outside a synchronisation run.
Rootless commands¶
A rootless JIM belongs to the jim account: its systemd manager runs JIM, and its Podman holds JIM's containers, images, secrets and volumes, which root's Podman does not see. As root, reach them like this:
# Podman as the jim account, from the root folder: rootless Podman fails in a folder the account cannot read,
# such as your home folder. This function saves typing that out each time.
jim-podman() { (cd / && sudo -u jim XDG_RUNTIME_DIR=/run/user/$(id -u jim) podman "$@"); }
sudo systemctl --user -M jim@ status jim.service
jim-podman ps
jim-podman logs -f jim-web
Wherever this documentation runs podman as root (sudo podman) for a rootful JIM, run jim-podman for a rootless one; and wherever it runs systemctl, run systemctl --user -M jim@.
Health checks and restarts¶
Each container has a health check, which podman ps shows. Podman 5 restarts a container whose health check keeps failing, where Docker only reports it, so the checks ask only whether a service has stopped responding:
- jim-web
Answers HTTP at all. Maintenance mode, or a database that is unreachable, makes JIM not ready without restartingjim-web, which would not help. - jim-worker, jim-scheduler
Refreshed their heartbeat within the last 60 or 120 seconds. - jim-database-postgres
Answered within five minutes, even to refuse a connection while it starts or recovers.
None of these checks starts until its service has finished starting, however long a first start or an upgrade's database changes take, so Podman never restarts a service part-way through starting.
Firewall, SELinux and AppArmor¶
- firewalld
Blocks JIM's port by default on RHEL. The installer offers to open it; by hand:firewall-cmd --permanent --add-service=https && firewall-cmd --reload(or--add-port=<port>/tcpfor another port). - SELinux
Needs nothing for JIM's own volumes, which Podman labels itself. A host folder you mount for the File Connector needs a label (see File Access), and an Apache httpd reverse proxy needs thehttpd_can_network_connectboolean (see Apache httpd Example). -
AppArmor, on Ubuntu 24.04
Ubuntu gives Podman'scrunandpodmanAppArmor profiles of their own. A rootful container that sets no-new-privileges, as JIM's do, cannot leave them for its own profile, and the combination allows it no network at all, so JIM cannot reach its database or identity provider. The installer offers to add a network rule to each profile's local override, Ubuntu's place for site changes; JIM's containers keep their own AppArmor profile. By hand, as root:echo 'network,' >> /etc/apparmor.d/local/crun echo 'network,' >> /etc/apparmor.d/local/podman apparmor_parser -r /etc/apparmor.d/crun /etc/apparmor.d/podmanA rootless JIM does not need it.
With Docker on the same server
Docker's firewall rules drop traffic forwarded to other container engines, so other machines cannot reach a rootful Podman JIM, the default, on a server that also runs Docker. Run JIM on one engine per server.
Installing by Hand¶
If your organisation's policy requires every step by hand, these steps do what the installer does. They use the files from a release: those attached to the release, or the podman/ folder of the release bundle, which keeps the three units in podman/quadlet/. Run them as root, from the folder holding the files.
-
Put the files in place. Leave out
jim-database.kubeif you use your own PostgreSQL server.mkdir -p /opt/jim/tls /etc/containers/systemd && chmod 700 /opt/jim/tls cp jim.yaml jim-database.yaml jim-config.yaml /opt/jim/ cp jim.network jim.kube jim-database.kube /etc/containers/systemd/The units expect the pod files in
/opt/jim; if you put them elsewhere, changeYaml=andConfigMap=in each.kubefile. To use a port other than 443, changePublishPort=injim.kube. -
Fill in
/opt/jim/jim-config.yaml: the identity provider settings, and for your own PostgreSQL server its name inJIM_DB_HOSTNAME. -
Load the images, from the release bundle's
docker-imagesfolder, or download them: -
Store the secrets. Fill in a copy of
jim-secrets.yaml(for the bundled PostgreSQL, choose a strong password: the database is created with it on first start), store it, and delete the copy. Then put JIM's certificate and key in/opt/jim/tlsastls.crtandtls.key(see The Certificate) and store them too: -
Open the port in firewalld, and on Ubuntu 24.04 allow JIM's containers the network under AppArmor (see Firewall, SELinux and AppArmor):
-
Start JIM, then wait until
podman healthcheck run jim-websucceeds:
Rootless, by hand¶
For a rootless installation, first create the account, enable lingering for it, and let it use port 443. Check /etc/subuid and /etc/subgid each give the account a range; current distributions add one when the account is created.
useradd --create-home --comment "JIM (Junctional Identity Manager)" --shell /sbin/nologin jim
grep '^jim:' /etc/subuid /etc/subgid
loginctl enable-linger jim
echo net.ipv4.ip_unprivileged_port_start=443 > /etc/sysctl.d/90-jim.conf && sysctl --system
jim-podman() { (cd / && sudo -u jim XDG_RUNTIME_DIR=/run/user/$(id -u jim) podman "$@"); }
Then follow the steps above, with three differences:
-
In step 1, put the units in the account's folder instead of
/etc/containers/systemd/: -
Run each
podmancommand asjim-podman. - Run each
systemctlcommand assystemctl --user -M jim@.
Without systemd¶
On a host without systemd, or with a Podman older than 4.4, run the pods directly. JIM runs, but does not start by itself after a reboot, so this is best effort rather than supported:
podman network create --ignore jim
podman kube play --replace --network jim --configmap /opt/jim/jim-config.yaml /opt/jim/jim-database.yaml
podman kube play --replace --network jim --configmap /opt/jim/jim-config.yaml --publish 443:8443 /opt/jim/jim.yaml
The installer does this for you on such a host.
Related¶
- Deployment Guide: installing, certificates, reverse proxies.
- Deploying with Ansible: the same files, deployed by Red Hat's podman system role.
- Upgrading, Backup & Disaster Recovery and Troubleshooting: each covers Podman alongside Docker.