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.
📋 Table of Contents
🎯 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
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
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
- Pull nginx image:
podman pull nginx - Run container with port mapping:
podman run -d --name webserver -p 8080:80 nginx - Verify container is running:
podman ps - Check logs:
podman logs webserver - Test web server:
curl http://localhost:8080 - 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) |
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
- Create content directory:
mkdir -p ~/webdata echo "<h1>Hello Podman</h1>" > ~/webdata/index.html - Run nginx with bind mount:
podman run -d --name myweb \ -v ~/webdata:/usr/share/nginx/html:Z \ -p 8080:80 nginx - Test content:
curl http://localhost:8080 - Update content (container sees changes):
echo "<h1>Updated!</h1>" > ~/webdata/index.html curl http://localhost:8080 - 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
- Create and test container:
podman run -d --name myapp -p 8080:80 nginx - Generate systemd unit:
cd ~ podman generate systemd --name myapp --files --new - Stop and remove original container:
podman stop myapp podman rm myapp - Install systemd unit:
mkdir -p ~/.config/systemd/user mv container-myapp.service ~/.config/systemd/user/ systemctl --user daemon-reload - Enable linger (start at boot):
sudo loginctl enable-linger $(whoami) - Enable and start service:
systemctl --user enable --now container-myapp.service - Verify:
systemctl --user status container-myapp.service curl http://localhost:8080 - 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?
-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?
: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?
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?
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?
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?
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.