Skip to content

Convert Podman Quadlet deployment from rootful to rootless#379

Draft
pablomh wants to merge 13 commits intotheforeman:masterfrom
pablomh:rootless
Draft

Convert Podman Quadlet deployment from rootful to rootless#379
pablomh wants to merge 13 commits intotheforeman:masterfrom
pablomh:rootless

Conversation

@pablomh
Copy link
Copy Markdown
Contributor

@pablomh pablomh commented Feb 19, 2026

Converts Foreman deployment to rootless Podman containers with dedicated service user and proper namespace isolation.

Key changes:

  • Auto-allocate matching UID/GID for foreman service user
  • Map container volumes to proper UIDs (PostgreSQL:26, Redis:1001, Pulp:700)
  • Move certificates from /root to /var/lib/foreman with correct ownership
  • Add migration playbook for converting existing rootful deployments
  • Move Quadlet files to user scope (~/.config/containers/systemd)
  • Enable loginctl linger and configure unprivileged ports

New components:

  • rootless_user role: Service user creation with auto-allocation
  • migrate-to-rootless playbook: Automated rootful-to-rootless migration

@pablomh pablomh marked this pull request as draft February 19, 2026 11:37
@pablomh pablomh force-pushed the rootless branch 13 times, most recently from c8b8f97 to 667b826 Compare February 19, 2026 14:39
@ehelms
Copy link
Copy Markdown
Member

ehelms commented Feb 19, 2026

Will rootless containers work with private podman networks?

@pablomh
Copy link
Copy Markdown
Contributor Author

pablomh commented Feb 19, 2026

I need to investigate it. My current tests have involved only installing and checking that I was able to pass the login page successfully.

@ehelms
Copy link
Copy Markdown
Member

ehelms commented Feb 19, 2026

Just keeping track of things that have come up around whether rootless will work, this is the other issue I could think of #220

@pablomh
Copy link
Copy Markdown
Contributor Author

pablomh commented Feb 19, 2026

Thanks! I'll keep an eye on that (I know it's one of the main issues, but wanted to share my work in case it could speed up development).

@pablomh pablomh force-pushed the rootless branch 10 times, most recently from 53a4056 to 0b7d9c7 Compare February 21, 2026 21:45
@pablomh pablomh force-pushed the rootless branch 6 times, most recently from 887c970 to f109267 Compare February 26, 2026 00:18
pablomh and others added 12 commits March 26, 2026 22:22
Introduces a new role that creates and configures the foremanctl system
user for running Podman Quadlet services in rootless mode:
- Creates system user/group with configurable home, shell, subuid/subgid
- Configures unprivileged port binding via sysctl
- Enables systemd lingering for the user
- Creates user-scope Quadlet and systemd directories
- Updates check_subuid_subgid to use the new role variables
- Adds acl package to pre_install (required for Ansible become_user)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- deploy.yaml: add rootless_user role setup play, map XDG_RUNTIME_DIR
- vars/base.yaml: add foremanctl user/group/home/quadlet dir variables
- vars/default_certificates.yml: use foremanctl_home for cert directory
- vars/installer_certificates.yml: update cert directory to foremanctl_home
- systemd_target: run as rootless user with scope=user

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- certificates: set owner/group to foremanctl_user on all generated
  cert files and directories so they can be read by the rootless user
- checks: use systemctl --machine=foremanctl@ --user to inspect
  user-scope services; check linger and unprivileged port settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Run the PostgreSQL Podman Quadlet as the foremanctl rootless user:
- Wrap all container operations in become_user: foremanctl block
- Use podman unshare to set container UID/GID ownership on data dir
- Add scope: user to systemd tasks and handlers
- Add container UID/GID defaults
- Slurp SSL cert/key as root before become_user block to avoid
  permission denied when creating podman secrets from /root/ paths
- Add postgresql.container.d/ssl.conf Quadlet override to mount
  SSL secrets into the container
- Add REGISTRY_AUTH_FILE to image pull

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Run the Redis Podman Quadlet as the foremanctl rootless user:
- Wrap container operations in become_user: foremanctl block
- Use podman unshare to set container UID/GID ownership on data dir
- Add scope: user to systemd tasks and handler
- Add container UID/GID and data dir defaults
- Add REGISTRY_AUTH_FILE to image pull

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Run Candlepin Podman Quadlet as the foremanctl rootless user:
- Wrap all container/secret operations in become_user: foremanctl block
- Add scope: user to systemd tasks and handler
- Move log directory creation outside become_user (runs as root)
- Add REGISTRY_AUTH_FILE to image pull
- Add candlepin-db-ca secret for external database SSL CA cert
- Mount candlepin-db-ca in the container at candlepin_database_ssl_ca_path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Run Pulp Podman Quadlets as the foremanctl rootless user:
- Wrap all container/secret operations in become_user: foremanctl block
- Use podman unshare to set container UID/GID ownership on /var/lib/pulp
- Add scope: user to systemd tasks and handlers
- Add REGISTRY_AUTH_FILE to all three image pulls (api, content, worker)
- Add pulp-db-ca secret for external database SSL CA cert, mount in all
  containers including migration and admin-password one-shot containers
- Add After=foreman.target to all Pulp service unit files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Run Foreman and Dynflow Podman Quadlets as the foremanctl rootless user:
- Wrap all container/secret operations in become_user: foremanctl block
- Add scope: user to systemd tasks and handlers
- Add foreman-db-ca secret for external database SSL CA cert; use
  foreman_database_ssl_ca_path (container path) in DATABASE_URL
- Mount foreman-db-ca in all containers (foreman, dynflow, db-migrate)
- Add After=foreman.target ordering to Foreman and Dynflow units
- Flush handlers after proxy registration so Refresh Foreman Proxy
  fires after the proxy is already registered

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Run Foreman Proxy Podman Quadlet as the foremanctl rootless user:
- Wrap all container/secret operations in become_user: foremanctl block
- Add scope: user to systemd tasks and handler
- Add Refresh Foreman Proxy handler for post-registration proxy refresh
- Move feature loops inside become_user block; create container.d dir
  in foremanctl_quadlet_dir instead of /etc/containers/systemd
- Adapt feature.yaml to use foremanctl_quadlet_dir and foremanctl
  owner/group for container.d override files
- Adapt remote_execution_ssh feature task: generate SSH key in
  foremanctl_home instead of /root
- Flush handlers after proxy registration (not before)
- Add fapolicyd workaround to CI for Ansible become_user with foremanctl

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Pass --user=foremanctl to forge test so tests check user-scope
  systemd services instead of root Podman containers
- Use systemctl --machine=foremanctl@ --user to stop foreman.target
  in the upgrade job (it is now a user-scope target)
- Add fapolicyd rule to allow Ansible become_user with foremanctl:
  Python module files placed in /var/tmp/ need to be executable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a --user pytest option so the test suite can check services in
either rootful (Podman containers) or rootless (user-scope systemd)
mode without code duplication:

- conftest.py: add GenericService abstraction that checks either podman
  containers or user-scope systemctl depending on the --user option;
  add user, user_uid, user_service and database_user_service fixtures;
  fix certificates fixture to read ca_directory from the template
- target_lifecycle_test.py: use --machine=foremanctl@ --user for
  systemctl stop/start/restart; extend timeout for external DB mode
- All service tests (foreman, candlepin, pulp, redis, postgresql,
  foreman_proxy, foreman_target): use user_service fixture to check
  services via user-scope systemd when --user=foremanctl is passed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- remote-database: add rootless_user role setup play so the database
  host also runs PostgreSQL as a rootless user
- deploy-dev: add rootless_user role setup for the development env
- docs/certificates.md: document that certificate files are now owned
  by foremanctl instead of root

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Provides a migration path for existing rootful deployments to the new
rootless Podman Quadlet setup. Handles stopping root-level services,
transferring data directory ownership, and starting the new user-scope
services.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants