Operating Rootless Containers
Goal
Run a container in rootless mode, check the volume, user mapping, and port constraints yourself, and write a Quadlet unit file.
Why it matters
There are fixed things you are bound to hit when moving from docker to podman. restart: always does not mean start at boot (because there is no daemon), you cannot bind to ports below 1024, the owner of volume files appears as an unfamiliar UID, and in an SELinux environment you get Permission denied without the :Z label. If you know these four in advance, the switch takes 30 minutes; if you do not, half a day.
And the podman way of starting at boot is Quadlet. If you place a .container file, systemd reads it and generates a service unit. podman generate systemd has become outdated.\n\n## This lab runs on a VM\n\nRootless podman really runs. In a Pod, newuidmap required CAP_SETUID, so a subuid range could not be given, and that made this lab half a lab — the container did not actually start, so grading could only look at files.\n\nOn a VM, podman run works as it is, and the Quadlet in step 7 is really translated into a service and starts. You can check it yourself with systemctl --user status labhub-web. The first start takes a little over a minute.
Steps
- Create the
/root/podrundirectory, load/opt/images/labhub-alpine.taraspodster, and save the image list to/root/podrun/images.txt. - Run a container once with that image to execute
echo labhub-ok, and save the output to/root/podrun/run.txt. - Mount
/home/podster/datato/datain the container, create/data/hello.txtinside the container, check that the file is visible on the host, and save the file path and content to/root/podrun/volume.txt. - Write the host-side owner UID of the file you created in step 3, as one line with a number, in
/root/podrun/owner.txt. - Try to publish a privileged port so that it fails, and save that error message to
/root/podrun/port-fail.txt. Then try again with port 8090, and save the successful result to/root/podrun/port-ok.txt. - Run a container in the background with the name
labhub-web, and save thepodman psoutput to/root/podrun/ps.txt. That name must appear in the list. - Write
/home/podster/.config/containers/systemd/labhub-web.container. It must have the three sections[Unit],[Container], and[Install], with the three keysImage,PublishPort, andVolumein[Container]andWantedByin[Install]. - Create
/root/podrun/diff.txtwith the following 6 lines.DAEMON=none/STORAGE=<podster 의 graphRoot 절대 경로>/PRIVPORT=denied/RESTART=quadlet/GPUFLAG=--device/SELINUX_LABEL=:Z(the placeholder is podster's absolute graphRoot path)
Notes
- You run it in a form like
su - podster -c "podman run --rm localhost/labhub-alpine:1 sh -c 'echo labhub-ok'". - Mount the volume with
-v /home/podster/data:/data:Z. - For background execution, use
-d --name labhub-web. Leave it running; do not stop it. - The Quadlet file does not need to run. Only the syntax and the required keys are graded.
- The container runtime may be blocked on this node. If creating unprivileged user namespaces is blocked, rootless podman ends with
cannot re-exec process. Even then, actually run the command and save its output (including errors) as it is to the specified file — the grader detects the environment and judges based on that record and the configuration you wrote. - Common mistake 1: creating the volume directory owned by root in step 3 so that podster cannot write to it.
- Common mistake 2: trying to capture the failure message only on standard output in step 5. Errors come out on standard error.
Load the image
Create the /root/podrun directory, load /opt/images/labhub-alpine.tar as podster, and save the image list to /root/podrun/images.txt.
There is no internet access, so use the archive prepared in advance. Check the list after loading.
Run a container
Run a container once with that image to execute echo labhub-ok, and save the output to /root/podrun/run.txt.
For a one-off run, add the option that cleans up after exit. Keep the output in a file.
Mount a volume
Mount /home/podster/data to /data in the container, create /data/hello.txt inside the container, check that the file is visible on the host, and save the file path and content to /root/podrun/volume.txt.
Join the host path and the container path with a colon. Also add the label option in case of an SELinux environment.
Check the user mapping
Write the host-side owner UID of the file you created in step 3, as one line with a number, in /root/podrun/owner.txt.
When you view a file created in the volume from the host, the owner appears as the mapped UID. Using the keep-id option changes this.
Reproduce the privileged port failure
Try to publish a privileged port so that it fails, and save that error message to /root/podrun/port-fail.txt. Then try again with port 8090, and save the successful result to /root/podrun/port-ok.txt.
Trying to publish a port below 1024 fails. Save that error message as it is and try again with a high port.
Check the running container
Run a container in the background with the name labhub-web, and save the podman ps output to /root/podrun/ps.txt. That name must appear in the list.
Starting it with a name makes it easier to manage. There is an option that specifies the output format.
Write the Quadlet unit
Write /home/podster/.config/containers/systemd/labhub-web.container. It must have the three sections [Unit], [Container], and [Install], with the three keys Image, PublishPort, and Volume in [Container] and WantedBy in [Install].
It is a file with the extension .container. Image is required in the [Container] section, and [Install] is needed too.
Summarize the differences from docker
Create /root/podrun/diff.txt with the following 6 lines.
DAEMON=none / STORAGE=<podster 의 graphRoot 절대 경로> / PRIVPORT=denied / RESTART=quadlet / GPUFLAG=--device / SELINUX_LABEL=:Z (the placeholder is podster's absolute graphRoot path)
The values must be ones you actually checked in the earlier steps. Write the storage path as an absolute path.