L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Déploiement et opérations · SKILL.md

rhel-docker-host

rhel-docker-host

Use when setting up and managing containers on RHEL 9 (and AlmaLinux/Rocky 9) — Podman (rootless and rootful), Docker CE installation, Buildah, Skopeo, container networking (podman network, macvlan), Quadlet systemd integration, pod management, Compose with podman-compose, private registry, SELinux container contexts, and security best practices. Part of th…

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentation de la Skill

GitHub

name: rhel-docker-host description: Use when setting up and managing containers on RHEL 9 (and AlmaLinux/Rocky 9) — Podman (rootless and rootful), Docker CE installation, Buildah, Skopeo, container networking (podman network, macvlan), Quadlet systemd integration, pod management, Compose with podman-compose, private registry, SELinux container contexts, and security best practices. Part of the rhel-* skill family. family: rhel applies_when: os_family == rhel disambiguation: Standing up containers on RHEL — Podman rootless and rootful, Docker CE, Buildah, Skopeo, SELinux. Network topology itself is docker-networking.

Container Host on RHEL 9 — Podman, Buildah, Skopeo, Docker CE

Companion skill to rhel-server-admin (parent). Podman is the default container runtime on RHEL 9. Docker CE is optional and requires the upstream docker-ce repo. For Dockerfile patterns and cross-platform design, see docker-admin.


1. Podman — Installation and Rootless Setup

Podman is pre-installed on RHEL 9 minimal. Verify or install:

podman --version && podman info
sudo dnf install -y podman buildah skopeo podman-compose containernetworking-plugins
# Rootless — verify subuid/subgid ranges; if missing, add them
grep $USER /etc/subuid /etc/subgid
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER
podman system migrate
sudo loginctl enable-linger $USER   # containers survive logout
# Core operations
podman run -d --name web -p 8080:80 docker.io/library/nginx:alpine
podman ps -a && podman logs -f --tail 200 web && podman exec -it web /bin/sh
podman stop web && podman rm web
podman run -d --name app --memory 512m --cpus 1.5 --pids-limit 100 myapp:latest
# Volume with SELinux label (:Z = private, :z = shared)
podman run -d --name db -v /srv/pgdata:/var/lib/postgresql/data:Z \
  -e POSTGRES_PASSWORD=secret docker.io/library/postgres:16-alpine
# Named volumes — rootless: ~/.local/share/containers/storage/volumes/
podman volume create mydata && podman volume ls

2. Podman Pods

Pods group containers sharing network/IPC/PID namespaces (like Kubernetes pods). Ports go on the pod, not individual containers.

podman pod create --name webapp -p 8080:80 -p 5432:5432
podman run -d --pod webapp --name web docker.io/library/nginx:alpine
podman run -d --pod webapp --name db -e POSTGRES_PASSWORD=secret \
  docker.io/library/postgres:16-alpine
# Containers share localhost — web can reach db at localhost:5432
podman pod ps && podman pod stop webapp && podman pod rm webapp -f
# Generate/deploy Kubernetes YAML
podman generate kube webapp > webapp.yaml
podman play kube webapp.yaml        # deploy
podman play kube webapp.yaml --down # tear down

3. Quadlet — Modern systemd Integration

Quadlet replaces the deprecated podman generate systemd. Place unit files in:

  • Rootful: /etc/containers/systemd/
  • Rootless: ~/.config/containers/systemd/
# /etc/containers/systemd/webapp.container
[Unit]
Description=Web Application Container
After=network-online.target
[Container]
Image=docker.io/library/nginx:alpine
ContainerName=webapp
PublishPort=8080:80
Volume=/srv/webapp/html:/usr/share/nginx/html:ro,Z
Environment=TZ=America/New_York
AutoUpdate=registry
Network=app-net.network
[Service]
Restart=always
TimeoutStartSec=120
[Install]
WantedBy=multi-user.target default.target
# /etc/containers/systemd/myapp.pod
[Pod]
PodName=myapp
PublishPort=8080:80
PublishPort=5432:5432
# /etc/containers/systemd/app-net.network
[Network]
Subnet=172.25.0.0/24
Gateway=172.25.0.1

Reference pod from .container with Pod=myapp.pod, network with Network=app-net.network.

sudo systemctl daemon-reload
sudo systemctl enable --now webapp.service
journalctl -u webapp.service -f
# Rootless: systemctl --user daemon-reload && systemctl --user enable --now webapp.service
# Verify Quadlet generation
/usr/libexec/podman/quadlet --dryrun          # rootful
/usr/libexec/podman/quadlet --user --dryrun   # rootless
# Auto-update (containers with AutoUpdate=registry)
sudo podman auto-update --dry-run
sudo podman auto-update
sudo systemctl enable --now podman-auto-update.timer

4. Buildah — Building OCI Images

Buildah builds OCI-compliant images without a daemon and without requiring a Dockerfile.

# Build from Containerfile (familiar workflow)
buildah bud -t myapp:v1 -f Containerfile .
# Equivalent: podman build -t myapp:v1 -f Containerfile .

# Build interactively (no Dockerfile needed)
ctr=$(buildah from docker.io/library/alpine:3.20)
buildah run $ctr -- apk add --no-cache python3 py3-pip
buildah copy $ctr ./app /opt/app
buildah config --workingdir /opt/app --cmd '["python3", "main.py"]' --port 8080 $ctr
buildah commit $ctr myapp:v1 && buildah rm $ctr

# Build from scratch (minimal image, no base OS)
ctr=$(buildah from scratch)
buildah copy $ctr ./mystaticbinary /app
buildah config --entrypoint '["/app"]' $ctr
buildah commit $ctr myapp:minimal && buildah rm $ctr

# Push
buildah push myapp:v1 docker://registry.home.lab:5000/myapp:v1

5. Skopeo — Inspect and Copy Images

Skopeo works with remote images without pulling to local storage.

skopeo inspect docker://docker.io/library/nginx:alpine
skopeo list-tags docker://docker.io/library/nginx
# Copy between registries (no local pull)
skopeo copy docker://docker.io/library/nginx:alpine \
  docker://registry.home.lab:5000/nginx:alpine
# Mirror entire repo (all tags)
skopeo sync --src docker --dest docker \
  docker.io/library/nginx registry.home.lab:5000/mirror/nginx
# Export to archive / OCI layout
skopeo copy docker://docker.io/library/alpine:3.20 docker-archive:/tmp/alpine.tar:alpine:3.20
skopeo delete docker://registry.home.lab:5000/myapp:old
# Auth stored in ${XDG_RUNTIME_DIR}/containers/auth.json
podman login docker.io && podman login registry.home.lab:5000

6. Docker CE — Optional Installation

Docker CE is NOT default on RHEL 9. Only install when explicitly required.

sudo dnf remove -y podman-docker runc 2>/dev/null
sudo dnf install -y dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo
# AlmaLinux/Rocky: use centos repo instead
# sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf install -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
sudo usermod -aG docker $USER && newgrp docker

daemon.json (/etc/docker/daemon.json):

{
  "storage-driver": "overlay2",
  "log-driver": "json-file",
  "log-opts": { "max-size": "20m", "max-file": "5", "compress": "true" },
  "default-address-pools": [{ "base": "172.20.0.0/16", "size": 24 }],
  "live-restore": true,
  "userland-proxy": false
}

Coexistence notes: Podman and Docker use separate storage — images are not shared. Remove podman-docker before installing Docker CE. Podman socket (sudo systemctl enable --now podman.socket) can emulate the Docker socket for tools expecting DOCKER_HOST.


7. Networking

# Named bridge network (DNS resolution by container name)
podman network create --subnet 172.25.0.0/24 --gateway 172.25.0.1 app-net
podman run -d --name web --network app-net -p 8080:80 docker.io/library/nginx:alpine
podman run -d --name api --network app-net myapi:latest
# web can reach api by name: curl http://api:8080
podman network connect app-net existing-container
podman network disconnect app-net existing-container

# Macvlan — containers get their own LAN IP, no port mapping
podman network create -d macvlan --subnet=10.0.1.0/24 --gateway=10.0.1.1 \
  --ip-range=10.0.1.200/29 -o parent=ens18 lan-net
podman run -d --name pihole --network lan-net --ip 10.0.1.200 docker.io/pihole/pihole
# Host-to-macvlan shim (required for host to reach macvlan containers)
sudo ip link add macvlan-shim link ens18 type macvlan mode bridge
sudo ip addr add 10.0.1.199/32 dev macvlan-shim && sudo ip link set macvlan-shim up
sudo ip route add 10.0.1.200/29 dev macvlan-shim

# Host networking — shares host network stack, no isolation
podman run -d --network host --name monitoring docker.io/prom/prometheus

# Port binding: -p 8080:80 (all), -p 10.0.1.50:8080:80 (specific IP), -p 127.0.0.1:8080:80 (local), -p 53:53/udp
# Firewall — RHEL uses firewalld, NOT ufw
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload && sudo firewall-cmd --list-all
# Rootful Podman creates nftables rules; rootless uses slirp4netns/pasta (no firewall changes)

8. Compose

# podman-compose (native)
sudo dnf install -y podman-compose
podman-compose up -d && podman-compose ps && podman-compose logs -f app
# Docker Compose via Podman socket
sudo systemctl enable --now podman.socket      # rootful
systemctl --user enable --now podman.socket     # rootless
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock
docker compose up -d

RHEL compose keys: use fully qualified images, add :z/:Z to bind-mount volumes, use named volumes for data (no label needed).

# compose.yaml
services:
  app:
    image: docker.io/library/node:20-alpine
    ports: ["8080:3000"]
    volumes: [./src:/app/src:ro,z, app-data:/app/data]
    depends_on:
      postgres: { condition: service_healthy }
    restart: unless-stopped
  postgres:
    image: docker.io/library/postgres:16-alpine
    environment: { POSTGRES_DB: myapp, POSTGRES_USER: myapp, POSTGRES_PASSWORD: changeme }
    volumes: [pg-data:/var/lib/postgresql/data]
    healthcheck: { test: ["CMD-SHELL", "pg_isready -U myapp"], interval: 10s, retries: 5 }
    restart: unless-stopped
volumes: { app-data: {}, pg-data: {} }

9. Registry Configuration and Private Registry

registries.conf

# /etc/containers/registries.conf
unqualified-search-registries = ["docker.io", "quay.io", "registry.home.lab:5000"]

[[registry]]
location = "registry.home.lab:5000"
insecure = false
[[registry.mirror]]
location = "mirror.home.lab:5000"

Deploying a Private Registry

sudo mkdir -p /opt/registry/{data,certs,auth}
openssl req -newkey rsa:4096 -nodes \
  -keyout /opt/registry/certs/registry.key \
  -x509 -days 3650 -out /opt/registry/certs/registry.crt \
  -subj "/CN=registry.home.lab" \
  -addext "subjectAltName=DNS:registry.home.lab,IP:10.0.1.50"
sudo dnf install -y httpd-tools
htpasswd -Bbn admin 'S3cur3P@ss' > /opt/registry/auth/htpasswd
sudo podman run -d --name registry -p 5000:5000 \
  -v /opt/registry/data:/var/lib/registry:Z \
  -v /opt/registry/certs:/certs:ro,Z -v /opt/registry/auth:/auth:ro,Z \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/registry.key \
  -e REGISTRY_AUTH=htpasswd -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
  -e REGISTRY_STORAGE_DELETE_ENABLED=true docker.io/library/registry:2
# Trust cert on RHEL clients (NOT /usr/local/share/ca-certificates like Ubuntu)
sudo cp /opt/registry/certs/registry.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust
podman login registry.home.lab:5000
podman tag myapp:latest registry.home.lab:5000/myapp:v1.0 && podman push registry.home.lab:5000/myapp:v1.0

For production, deploy as a Quadlet .container unit (Section 3 pattern).


10. Security

Rootless (default): Container UID 0 maps to high host UID via user namespaces. Verify: podman unshare cat /proc/self/uid_map.

SELinux: Volume labels :Z/:z auto-relabel host paths. Manual: sudo chcon -Rt container_file_t /srv/webapp/. Audit denials: sudo ausearch -m AVC -ts recent.

# Hardened container
podman run -d --name secure-app \
  --read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  --cap-drop ALL --cap-add NET_BIND_SERVICE \
  --security-opt=no-new-privileges:true \
  --security-opt label=type:container_t \
  --pids-limit 100 --memory 512m --cpus 1.0 --user 1001:1001 \
  myapp:latest
# Custom seccomp profile
podman run --security-opt seccomp=/path/to/custom-seccomp.json myapp
# Generate seccomp from actual syscalls
sudo dnf install -y oci-seccomp-bpf-hook
podman run --annotation io.containers.trace-syscall=of:/tmp/seccomp.json myapp
# Image signing — configure /etc/containers/policy.json
podman push --sign-by admin@example.com registry.home.lab:5000/myapp:v1
# Rootful user namespace isolation — /etc/containers/containers.conf
# [containers]
# userns = "auto"

11. Maintenance

podman system df -v                          # disk usage
podman container prune -f && podman image prune -f
podman image prune -a -f && podman volume prune -f
podman builder prune -f --keep-storage=5G

Automated Cleanup (systemd timer)

# /etc/systemd/system/podman-cleanup.service
[Unit]
Description=Podman resource cleanup
[Service]
Type=oneshot
ExecStart=/usr/bin/podman container prune -f --filter "until=72h"
ExecStart=/usr/bin/podman image prune -f --filter "until=168h"
# /etc/systemd/system/podman-cleanup.timer
[Unit]
Description=Daily Podman cleanup
[Timer]
OnCalendar=*-*-* 04:00:00
Persistent=true
[Install]
WantedBy=timers.target

sudo systemctl enable --now podman-cleanup.timer

Backup and Updates

# Volume backup via helper container
podman run --rm -v myvolume:/source:ro -v /backup:/dest:Z \
  docker.io/library/alpine tar czf /dest/myvolume-$(date +%Y%m%d).tar.gz -C /source .
# App-level dump (preferred for databases)
podman exec postgres pg_dumpall -U myapp > /backup/pg-dump-$(date +%Y%m%d).sql
# Update container tools
sudo dnf update -y podman buildah skopeo && podman system migrate

Quick Reference

TaskCommand
Disk usagepodman system df -v
Networks / volumespodman network ls / podman volume ls
Live statspodman stats
Follow logspodman logs -f --tail 200 <ctr>
Shell inpodman exec -it <ctr> bash
Build imagepodman build -t myapp:v1 .
Inspect remoteskopeo inspect docker://docker.io/library/nginx:alpine
Copy registriesskopeo copy docker://src docker://dest
K8s YAMLpodman generate kube <pod>
Quadlet dry-run/usr/libexec/podman/quadlet --dryrun
Auto-updatespodman auto-update --dry-run
SELinux denialssudo ausearch -m AVC -ts recent

Anti-Patterns

Anti-PatternWhy It FailsCorrect Approach
Installing Docker CE from docker.io repo without removing Podman firstPackage conflicts between Docker and Podman; broken container runtime; confused CLI behaviorChoose one: Podman (default RHEL) or Docker CE; remove the other cleanly; do not mix runtimes
Running Podman containers as root by defaultLoses the key security advantage of Podman's rootless mode; container escape gives root accessUse rootless Podman (podman --user); root only when host resources (ports < 1024, specific devices) require it
Not using Quadlet for systemd service managementManual podman generate systemd output drifts from container config; updates require regenerationUse Quadlet (.container, .volume, .network files in /etc/containers/systemd/); declarative and auto-updating
Ignoring SELinux labels on bind mountsSELinux denials block container access to host directories; admins disable SELinux instead of fixing labelsUse :z (shared) or :Z (private) suffix on bind mounts; or set correct SELinux context with chcon
Not configuring container log rotationContainer logs grow unbounded; /var fills up; all containers on the host stop workingSet log driver options: --log-opt max-size=10m --log-opt max-file=3 in daemon.json or per container

Related Skills

WorkloadSkill
Core RHEL adminrhel-server-admin
Dockerfile patterns, cross-platformdocker-admin
Web servers (Nginx, Apache, Caddy)rhel-web-servers
Databases (PostgreSQL, MariaDB, Redis)rhel-databases
NFS, Samba, Stratisrhel-file-storage
Prometheus, Grafana, monitoringrhel-monitoring
Networking, VPN, load balancingrhel-network-infra
GPU passthrough, Ollamarhel-ollama-nvidia
Core Docker concepts, BuildKit, Dockerfilesdocker-fundamentals
Docker networking deep divedocker-networking
Volumes, storage driversdocker-storage
Image scanning, security hardeningdocker-security
CI/CD pipelines, multi-platform buildsdocker-cicd
Advanced Compose patternsdocker-compose-patterns
Skills enregistrées