Chapter 16 ⏱️ 55 min read 📚 Advanced

Run Containers (Podman)

Master container management with Podman. Learn to find, pull, run, and manage containers as a rootless user. Configure persistent storage and systemd integration.

🎯 Introduction to Podman

Podman is a daemonless container engine for OCI containers. It's Docker-compatible but runs rootless by default, making it more secure.

Podman vs Docker

Feature Podman Docker
Daemon No daemon required Requires dockerd
Root Rootless by default Requires root
Systemd Native integration Third-party tools
Pods Native pod support Requires compose/swarm

Installing Podman

# Install podman
sudo dnf install -y podman

# Install additional tools
sudo dnf install -y podman-docker  # Docker compatibility
sudo dnf install -y buildah         # Image building
sudo dnf install -y skopeo          # Image operations

# Verify installation
podman --version
podman info

Registry Configuration

# View configured registries
cat /etc/containers/registries.conf

# Common registries
# registry.access.redhat.com (Red Hat)
# docker.io (Docker Hub)
# quay.io (Red Hat Quay)

# Search for images
podman search nginx
podman search --limit 5 httpd

# Search specific registry
podman search quay.io/nginx
💡 Rootless Containers

Podman runs as regular user without root privileges. User containers store in ~/.local/share/containers. Root containers (sudo podman) store in /var/lib/containers. They're completely separate!

💿 Managing Images

Container images are read-only templates used to create containers.

Pulling Images

# Pull latest image
podman pull nginx
podman pull docker.io/library/nginx

# Pull specific tag
podman pull nginx:1.24
podman pull registry.access.redhat.com/ubi9/ubi:latest

# Pull from specific registry
podman pull quay.io/podman/hello

# Verify pulled image
podman images

Listing and Inspecting Images

# List all images
podman images

# List with specific format
podman images --format "{{.Repository}}:{{.Tag}}"

# Show image details
podman inspect nginx

# Show image history (layers)
podman history nginx

# Show image size
podman images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"

Removing Images

# Remove specific image
podman rmi nginx

# Remove by image ID
podman rmi 12345678

# Remove all unused images
podman image prune

# Remove all images (dangerous!)
podman rmi --all

Tagging Images

# Tag an image
podman tag nginx:latest mynginx:v1

# Tag for private registry
podman tag nginx localhost:5000/mynginx:v1

# List all tags
podman images
📘 Image Names

Full format: registry/namespace/repository:tag
Example: quay.io/podman/hello:latest
Default registry: docker.io
Default tag: latest

🐳 Running Containers

Basic Container Operations

# Run container (interactive)
podman run -it ubi9 /bin/bash

# Run container (detached/background)
podman run -d nginx

# Run with custom name
podman run -d --name webserver nginx

# Run with port mapping
podman run -d -p 8080:80 --name web nginx

# Run with environment variables
podman run -d -e MYSQL_ROOT_PASSWORD=secret mysql

# Run with auto-remove (delete after exit)
podman run --rm -it ubi9 /bin/bash

Managing Running Containers

# List running containers
podman ps

# List all containers (including stopped)
podman ps -a

# Show container details
podman inspect webserver

# Show container logs
podman logs webserver
podman logs -f webserver  # Follow logs

# Show container processes
podman top webserver

# Show container resource usage
podman stats webserver

Container Lifecycle

# Stop container
podman stop webserver

# Start stopped container
podman start webserver

# Restart container
podman restart webserver

# Kill container (force stop)
podman kill webserver

# Pause container
podman pause webserver

# Unpause container
podman unpause webserver

# Remove container
podman rm webserver

# Remove running container (force)
podman rm -f webserver

Executing Commands in Containers

# Execute command in running container
podman exec webserver ls /usr/share/nginx/html

# Interactive shell in running container
podman exec -it webserver /bin/bash

# Execute as specific user
podman exec -u nginx webserver whoami

# Execute with environment variable
podman exec -e VAR=value webserver env

Port Mapping

Option Description Example
-p HOST:CONTAINER Map host port to container -p 8080:80
-p IP:HOST:CONTAINER Bind to specific IP -p 127.0.0.1:8080:80
-p CONTAINER Random host port -p 80
-P Publish all exposed ports -P

Procedure: Run Nginx Web Server

  1. Pull nginx image:
    podman pull nginx
  2. Run container with port mapping:
    podman run -d --name webserver -p 8080:80 nginx
  3. Verify container is running:
    podman ps
  4. Check logs:
    podman logs webserver
  5. Test web server:
    curl http://localhost:8080
  6. Stop and remove:
    podman stop webserver
    podman rm webserver

💾 Persistent Storage

By default, container storage is ephemeral. Use volumes or bind mounts for persistence.

Volume Types

Type Description Use Case
Bind Mount Host directory → container Development, config files
Volume Podman-managed storage Databases, persistent data

Bind Mounts

# Create host directory
mkdir -p ~/web-content

# Run with bind mount
podman run -d --name web \
  -v ~/web-content:/usr/share/nginx/html:Z \
  -p 8080:80 nginx

# :Z sets SELinux context (critical on RHEL!)
# Without :Z, SELinux blocks access

# Read-only bind mount
podman run -d --name web \
  -v ~/web-content:/usr/share/nginx/html:ro,Z \
  nginx

# Multiple mounts
podman run -d --name web \
  -v ~/web-content:/usr/share/nginx/html:Z \
  -v ~/web-logs:/var/log/nginx:Z \
  nginx

Named Volumes

# Create named volume
podman volume create mysql-data

# List volumes
podman volume ls

# Inspect volume
podman volume inspect mysql-data

# Run with named volume
podman run -d --name db \
  -v mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  mysql

# Remove volume
podman volume rm mysql-data

# Remove all unused volumes
podman volume prune

SELinux Context for Volumes

Option Description
:z Shared content (multiple containers)
:Z Private content (single container)
⚠️ SELinux and Volumes

On RHEL systems with SELinux enforcing, ALWAYS use :Z or :z when mounting host directories! Without it, containers can't access the files. Use :Z for single-container volumes, :z for shared.

Procedure: Persistent Web Content

  1. Create content directory:
    mkdir -p ~/webdata
    echo "<h1>Hello Podman</h1>" > ~/webdata/index.html
  2. Run nginx with bind mount:
    podman run -d --name myweb \
      -v ~/webdata:/usr/share/nginx/html:Z \
      -p 8080:80 nginx
  3. Test content:
    curl http://localhost:8080
  4. Update content (container sees changes):
    echo "<h1>Updated!</h1>" > ~/webdata/index.html
    curl http://localhost:8080
  5. Remove container (data persists):
    podman rm -f myweb
    ls ~/webdata  # Files still there!

⚙️ Systemd Integration

Integrate containers with systemd for automatic startup and management.

Generate Systemd Unit

# Run a container first
podman run -d --name webserver -p 8080:80 nginx

# Generate systemd unit file
podman generate systemd --name webserver --files --new

# This creates: container-webserver.service

# Move to user systemd directory
mkdir -p ~/.config/systemd/user
mv container-webserver.service ~/.config/systemd/user/

# Reload systemd
systemctl --user daemon-reload

# Enable at login
systemctl --user enable container-webserver.service

# Start service
systemctl --user start container-webserver.service

# Check status
systemctl --user status container-webserver.service

Generate Options

Option Description
--new Create new container on start (not reuse)
--files Write to files (not stdout)
--name Use container name
--restart-policy always, on-failure, no

Enable Linger (Rootless)

# Allow user containers to start without login
sudo loginctl enable-linger $(whoami)

# Check linger status
loginctl show-user $(whoami) | grep Linger

# Now user containers start at boot, even if user not logged in

System vs User Containers

Aspect System (Root) User (Rootless)
Command sudo podman podman
Systemd systemctl (root) systemctl --user
Unit Path /etc/systemd/system/ ~/.config/systemd/user/
Ports Can use 1-1024 Must use 1024+

Procedure: Container as Systemd Service

  1. Create and test container:
    podman run -d --name myapp -p 8080:80 nginx
  2. Generate systemd unit:
    cd ~
    podman generate systemd --name myapp --files --new
  3. Stop and remove original container:
    podman stop myapp
    podman rm myapp
  4. Install systemd unit:
    mkdir -p ~/.config/systemd/user
    mv container-myapp.service ~/.config/systemd/user/
    systemctl --user daemon-reload
  5. Enable linger (start at boot):
    sudo loginctl enable-linger $(whoami)
  6. Enable and start service:
    systemctl --user enable --now container-myapp.service
  7. Verify:
    systemctl --user status container-myapp.service
    curl http://localhost:8080
  8. Test persistence (reboot and check):
    sudo reboot
    # After reboot
    systemctl --user status container-myapp.service

📝 Practice Questions

Question 1: How do you run a container in the background?

  • A) podman run -b nginx
  • B) podman run -d nginx
  • C) podman run --background nginx
  • D) podman start nginx
Answer: B) podman run -d nginx
-d (detached) runs container in background. Returns container ID. Use podman ps to see running containers. -it runs interactive terminal (foreground).

Question 2: What's the critical SELinux option for bind mounts?

  • A) -v /host:/container
  • B) -v /host:/container:Z
  • C) -v /host:/container:selinux
  • D) -v /host:/container:secure
Answer: B) -v /host:/container:Z
:Z sets SELinux context for private use. Without it, SELinux blocks access. Use :z (lowercase) for shared volumes. This is CRITICAL on RHEL with SELinux enforcing!

Question 3: How to map host port 8080 to container port 80?

  • A) -p 80:8080
  • B) -p 8080:80
  • C) --port 8080:80
  • D) --expose 8080:80
Answer: B) -p 8080:80
Format: -p HOST:CONTAINER. Access via http://host:8080 which forwards to container port 80. Rootless users must use ports 1024+ on host.

Question 4: Command to generate systemd unit file?

  • A) podman systemd --name myapp --files
  • B) podman generate systemd --name myapp --files --new
  • C) podman create-service myapp
  • D) systemctl generate myapp
Answer: B)
podman generate systemd --name myapp --files --new. --new creates new container on start. --files writes to file. Move to ~/.config/systemd/user/ then systemctl --user daemon-reload.

Question 5: Enable rootless containers at boot?

  • A) systemctl --user enable container-name
  • B) sudo loginctl enable-linger $USER
  • C) podman enable container-name
  • D) Both A and B
Answer: D) Both A and B
Need both: systemctl --user enable container-name (enable service) AND sudo loginctl enable-linger $USER (allow user services without login). Without linger, containers only run when user logged in.

Question 6: Where are rootless container images stored?

  • A) /var/lib/containers
  • B) ~/.local/share/containers
  • C) ~/podman/images
  • D) /home/containers
Answer: B) ~/.local/share/containers
Rootless (user) containers: ~/.local/share/containers. Root containers: /var/lib/containers. They're completely separate! User and root can have same image name without conflict.