RED HAT ENTERPRISE LINUX

Container Management

Managing Containers with Podman

College-Level Course Module | RHEL System Administration

Learning Objectives

1
Understand container concepts

Images, containers, registries, and Podman architecture

2
Manage container images

Search, pull, list, inspect, and remove images

3
Run and manage containers

Create, start, stop, execute commands, and configure containers

4
Configure persistent storage and services

Use volumes for data persistence, run containers as systemd services

What Are Containers?

Containers are lightweight, isolated environments that package applications with their dependencies. They share the host kernel but have isolated filesystems, processes, and network stacks.

Virtual Machines

Full OS per VM
Hypervisor required
GB of memory each
Minutes to start
Strong isolation

Containers

Shared host kernel
No hypervisor needed
MB of memory each
Seconds to start
Process isolation

Container A | Container B | Container C
Container Runtime (Podman)
Host Operating System (RHEL)
Hardware

Images vs Containers

Container Image

Read-only template containing application and dependencies. Like a class definition or a recipe.

  • Built from layers
  • Stored in registries
  • Immutable once created
  • Shared across containers

Container

Running instance of an image with a writable layer. Like an object or a meal from a recipe.

  • Created from an image
  • Has isolated runtime
  • Writable layer on top
  • Can be started/stopped
Registry
Image
Container
Container
Container

Introduction to Podman

Podman (Pod Manager) is RHEL's container engine. It provides Docker-compatible commands without requiring a daemon or root privileges. It is the default container tool in RHEL 8 and 9.

FeaturePodmanDocker
DaemonNo daemon requiredRequires dockerd daemon
Root accessRootless by defaultRequires root (or docker group)
Command syntaxdocker-compatibledocker commands
Systemd integrationNative supportLimited
Pod supportNative podsNo native pods
# Install Podman
[root@server ~]# dnf install container-tools

# Verify installation
[user@server ~]$ podman --version
podman version 4.6.1

# Podman commands are Docker-compatible
[user@server ~]$ alias docker=podman   # Works for most commands

Container Registries

Registries are servers that store and distribute container images. You pull images from registries and can push your own images to them.

RegistryURLDescription
Red Hat Registryregistry.redhat.ioOfficial Red Hat images (requires login)
Red Hat Catalogregistry.access.redhat.comCertified partner images
Quay.ioquay.ioRed Hat's public registry
Docker Hubdocker.ioPublic Docker registry
# Registry configuration
[user@server ~]$ cat /etc/containers/registries.conf
unqualified-search-registries = ["registry.access.redhat.com", "registry.redhat.io", "docker.io"]

# Login to registry (required for registry.redhat.io)
[user@server ~]$ podman login registry.redhat.io
Username: your-redhat-username
Password: 
Login Succeeded!

# Full image name format:
registry.redhat.io/rhel9/httpd-24:latest
registry/namespace/image:tag

Searching for Images

# Search registries for images
[user@server ~]$ podman search httpd
INDEX       NAME                                      DESCRIPTION                 STARS   OFFICIAL
redhat.io   registry.redhat.io/rhel8/httpd-24         Apache HTTP Server 2.4      0
redhat.io   registry.redhat.io/rhel9/httpd-24         Apache HTTP Server 2.4      0       
docker.io   docker.io/library/httpd                   The Apache HTTP Server      4523    [OK]
docker.io   docker.io/centos/httpd-24-centos7         Platform for running...     44

# Search specific registry
[user@server ~]$ podman search registry.redhat.io/httpd

# Limit results
[user@server ~]$ podman search --limit 5 nginx

# Show only official images
[user@server ~]$ podman search --filter=is-official=true httpd

# List available tags (requires skopeo)
[user@server ~]$ skopeo list-tags docker://registry.redhat.io/rhel9/httpd-24
{
    "Repository": "registry.redhat.io/rhel9/httpd-24",
    "Tags": ["1-290", "1-299", "latest"]
}

Pulling Images

# Pull an image from registry
[user@server ~]$ podman pull registry.redhat.io/rhel9/httpd-24
Trying to pull registry.redhat.io/rhel9/httpd-24:latest...
Getting image source signatures
Copying blob 8e5fca777171 done
Copying blob 3c72a8ed6814 done
Copying config 8f1de90813 done
Writing manifest to image destination
8f1de908137c2c3b9f2c7e8e4c9d9de1f4a6b5c3d2e1f0...

# Pull with specific tag
[user@server ~]$ podman pull registry.redhat.io/ubi9/ubi:9.3

# Pull from Docker Hub
[user@server ~]$ podman pull docker.io/library/nginx:1.25

# Pull without specifying registry (searches configured registries)
[user@server ~]$ podman pull ubi9
? Please select an image:
    registry.access.redhat.com/ubi9:latest
  ▸ registry.redhat.io/ubi9:latest
UBI (Universal Base Image): Red Hat provides freely distributable base images (UBI) that can be used without a subscription. Perfect for building custom containers.

Managing Images

# List local images
[user@server ~]$ podman images
REPOSITORY                           TAG      IMAGE ID       CREATED        SIZE
registry.redhat.io/rhel9/httpd-24    latest   8f1de90813c2   2 weeks ago    462 MB
registry.redhat.io/ubi9/ubi          9.3      a1b2c3d4e5f6   3 weeks ago    211 MB
docker.io/library/nginx              1.25     b2c3d4e5f6a7   1 month ago    142 MB

# Inspect image details
[user@server ~]$ podman inspect registry.redhat.io/ubi9/ubi:9.3
[{
    "Id": "a1b2c3d4e5f6...",
    "Config": {
        "Cmd": ["/bin/bash"],
        "Env": ["PATH=/usr/local/sbin:..."],
        ...
    }
}]

# Show image history (layers)
[user@server ~]$ podman history registry.redhat.io/ubi9/ubi:9.3

# Remove an image
[user@server ~]$ podman rmi registry.redhat.io/ubi9/ubi:9.3

# Remove all unused images
[user@server ~]$ podman image prune -a

Running Containers

# Run a container (pulls image if needed)
[user@server ~]$ podman run registry.redhat.io/ubi9/ubi echo "Hello Container"
Hello Container

# Run interactively with terminal
[user@server ~]$ podman run -it registry.redhat.io/ubi9/ubi /bin/bash
[root@a1b2c3d4 /]# cat /etc/redhat-release
Red Hat Enterprise Linux release 9.3 (Plow)
[root@a1b2c3d4 /]# exit

# Run in detached mode (background)
[user@server ~]$ podman run -d --name myweb registry.redhat.io/rhel9/httpd-24
a1b2c3d4e5f6g7h8i9j0...

# Run with automatic removal when stopped
[user@server ~]$ podman run --rm registry.redhat.io/ubi9/ubi cat /etc/os-release
Key options: -i interactive (keep STDIN open), -t allocate TTY, -d detached (background), --name assign name, --rm auto-remove on exit.

Container Lifecycle

# List running containers
[user@server ~]$ podman ps
CONTAINER ID  IMAGE                                   COMMAND               STATUS        PORTS   NAMES
a1b2c3d4e5f6  registry.redhat.io/rhel9/httpd-24      /usr/bin/run-http...  Up 5 minutes          myweb

# List all containers (including stopped)
[user@server ~]$ podman ps -a

# Stop a container
[user@server ~]$ podman stop myweb

# Start a stopped container
[user@server ~]$ podman start myweb

# Restart a container
[user@server ~]$ podman restart myweb

# Remove a stopped container
[user@server ~]$ podman rm myweb

# Force remove a running container
[user@server ~]$ podman rm -f myweb

# Remove all stopped containers
[user@server ~]$ podman container prune
Image
Created
Running
Stopped
Removed

Executing Commands

# Execute command in running container
[user@server ~]$ podman exec myweb cat /etc/httpd/conf/httpd.conf

# Get interactive shell in running container
[user@server ~]$ podman exec -it myweb /bin/bash
[root@a1b2c3d4 /]# ps aux
USER  PID  %CPU %MEM    VSZ   RSS TTY  STAT START   TIME COMMAND
root    1   0.0  0.1  12345  1234 ?    Ss   10:00   0:00 httpd -D FOREGROUND
...
[root@a1b2c3d4 /]# exit

# View container logs
[user@server ~]$ podman logs myweb
[Mon Jan 20 10:00:00.000000 2024] [core:notice] Starting Apache...

# Follow logs in real-time
[user@server ~]$ podman logs -f myweb

# Show container resource usage
[user@server ~]$ podman stats myweb
ID            NAME    CPU %   MEM USAGE / LIMIT     MEM %   NET IO      BLOCK IO
a1b2c3d4e5f6  myweb   0.50%   45.2MiB / 7.768GiB    0.57%   1.2kB/0B    0B/0B

Port Mapping

Containers have isolated network stacks. To access container services from outside, map container ports to host ports using -p.

# Map host port 8080 to container port 80
[user@server ~]$ podman run -d --name web -p 8080:80 registry.redhat.io/rhel9/httpd-24

# Access from host
[user@server ~]$ curl http://localhost:8080
<html>...</html>

# Map multiple ports
[user@server ~]$ podman run -d -p 8080:80 -p 8443:443 --name secureweb nginx

# Map to specific host interface
[user@server ~]$ podman run -d -p 192.168.1.100:8080:80 --name web nginx

# Let Podman choose host port
[user@server ~]$ podman run -d -p 80 --name web nginx
[user@server ~]$ podman port web
80/tcp -> 0.0.0.0:43210

# View port mappings
[user@server ~]$ podman ps
...  PORTS                   NAMES
...  0.0.0.0:8080->80/tcp    web

Environment Variables

# Set environment variable
[user@server ~]$ podman run -d --name mydb \
    -e MYSQL_ROOT_PASSWORD=secret123 \
    -e MYSQL_DATABASE=appdb \
    docker.io/library/mysql:8.0

# Verify environment inside container
[user@server ~]$ podman exec mydb env | grep MYSQL
MYSQL_ROOT_PASSWORD=secret123
MYSQL_DATABASE=appdb

# Pass environment from file
[user@server ~]$ cat myenv.txt
DB_HOST=database.example.com
DB_USER=appuser
DB_PASS=secretpass

[user@server ~]$ podman run -d --name myapp --env-file myenv.txt myapp:latest

# Pass host environment variable
[user@server ~]$ export API_KEY="abc123"
[user@server ~]$ podman run -e API_KEY myapp:latest
Configuration pattern: Many container images use environment variables for configuration. Check image documentation for available variables.

Persistent Storage

By default, container data is ephemeral. When a container is removed, its writable layer is deleted. Use volumes for data that must persist.

# Bind mount host directory into container
[user@server ~]$ podman run -d --name web \
    -v /home/user/website:/var/www/html:Z \
    -p 8080:80 \
    registry.redhat.io/rhel9/httpd-24

# Create and use a named volume
[user@server ~]$ podman volume create dbdata
[user@server ~]$ podman run -d --name mysql \
    -v dbdata:/var/lib/mysql \
    -e MYSQL_ROOT_PASSWORD=secret \
    docker.io/library/mysql:8.0

# List volumes
[user@server ~]$ podman volume ls
DRIVER   VOLUME NAME
local    dbdata

# Inspect volume
[user@server ~]$ podman volume inspect dbdata

# Remove volume
[user@server ~]$ podman volume rm dbdata

SELinux and Volumes

SELinux blocks container access to host files by default. Use volume options to set correct context, or container cannot read/write mounted directories.

# Without SELinux option - fails with permission denied
[user@server ~]$ podman run -v /home/user/data:/data ubi9 ls /data
ls: cannot open directory '/data': Permission denied

# With :Z - relabels for this container only (private)
[user@server ~]$ podman run -v /home/user/data:/data:Z ubi9 ls /data
file1.txt  file2.txt

# With :z - relabels for shared access (multiple containers)
[user@server ~]$ podman run -v /shared/data:/data:z ubi9 ls /data

# Check SELinux context
[user@server ~]$ ls -lZ /home/user/data
-rw-r--r--. user user system_u:object_r:container_file_t:s0 file1.txt
:Z vs :z - Use uppercase :Z for private volumes (one container). Use lowercase :z for shared volumes (multiple containers). Both relabel for container access.

Containers as Services

For production, run containers as systemd services. This provides automatic startup, restart on failure, and standard service management.

# Create container first (do not start yet for user service)
[user@server ~]$ podman create --name myweb -p 8080:80 registry.redhat.io/rhel9/httpd-24

# Generate systemd unit file
[user@server ~]$ mkdir -p ~/.config/systemd/user
[user@server ~]$ podman generate systemd --name myweb --files --new
~/.config/systemd/user/container-myweb.service

# Reload systemd and enable service
[user@server ~]$ systemctl --user daemon-reload
[user@server ~]$ systemctl --user enable --now container-myweb

# Check status
[user@server ~]$ systemctl --user status container-myweb

# Enable lingering (service runs without user login)
[user@server ~]$ loginctl enable-linger $USER
Enable lingering! Without loginctl enable-linger, user services stop when the user logs out.

Rootless vs Root

Rootless (Default)

  • Run by regular users
  • No root privileges needed
  • Container escape = user access only
  • Cannot bind ports below 1024
  • Limited network features
  • User systemd services

Root Containers

  • Run by root user
  • Full system access
  • Container escape = root access!
  • Can bind any port
  • Full network features
  • System systemd services
# Rootless - run as regular user
[user@server ~]$ podman run -d -p 8080:80 --name web nginx

# Root - run as root (when necessary)
[root@server ~]# podman run -d -p 80:80 --name web nginx

# Storage locations differ:
# Rootless: ~/.local/share/containers/
# Root: /var/lib/containers/
Best practice: Use rootless containers whenever possible. Only run as root when you need privileged ports or specific system access.

Inspecting Containers

# Detailed container information
[user@server ~]$ podman inspect myweb
[{
    "Id": "a1b2c3d4e5f6...",
    "State": { "Status": "running", "Pid": 12345 },
    "NetworkSettings": { "IPAddress": "10.88.0.2" },
    "Mounts": [...],
    ...
}]

# Get specific value with format
[user@server ~]$ podman inspect --format '{{.State.Status}}' myweb
running

[user@server ~]$ podman inspect --format '{{.NetworkSettings.IPAddress}}' myweb
10.88.0.2

# View container processes
[user@server ~]$ podman top myweb
USER    PID   PPID   %CPU   ELAPSED   TTY   COMMAND
root    1     0      0.0    10m30s    ?     httpd -D FOREGROUND
apache  10    1      0.0    10m29s    ?     httpd -D FOREGROUND

# Show container filesystem changes
[user@server ~]$ podman diff myweb
C /var/log/httpd
A /var/log/httpd/access_log
A /var/log/httpd/error_log

Best Practices

Do

  • Use rootless containers when possible
  • Use specific image tags, not :latest
  • Use volumes for persistent data
  • Set resource limits (--memory, --cpus)
  • Use named containers for easy management
  • Run containers as systemd services
  • Keep images updated for security
  • Use :Z or :z for bind mounts (SELinux)

Do Not

  • Run as root unless necessary
  • Store important data only in containers
  • Use untrusted images in production
  • Expose unnecessary ports
  • Hard-code secrets in images
  • Forget to clean up unused resources
  • Ignore container logs
  • Skip SELinux labels on bind mounts
# Good: Specific tag, named, resource limits, volume for data
[user@server ~]$ podman run -d --name mydb \
    --memory 512m --cpus 1 \
    -v dbdata:/var/lib/mysql:Z \
    -e MYSQL_ROOT_PASSWORD_FILE=/run/secrets/db-password \
    mysql:8.0.35

Key Takeaways

1

Podman: RHEL's daemonless, rootless container engine. Docker-compatible commands. Install with dnf install container-tools.

2

Images: podman search/pull/images/rmi. Use specific tags. Pull from trusted registries.

3

Containers: podman run/ps/stop/start/rm/exec/logs. Use -d for background, -p for ports, -e for environment, -v for volumes.

4

Services: podman generate systemd creates unit files. Enable lingering for user services. Use :Z for SELinux.

LAB EXERCISES

  • Pull images from registry.redhat.io and run a web server container
  • Map ports and access container service from host
  • Use volumes to persist data across container restarts
  • Configure container with environment variables
  • Create systemd service for container with auto-restart
  • Practice rootless container management as regular user

Next: Building Custom Container Images with Containerfiles