Appendix A: RHEL Developer Environment Setup

Appendix Overview

Purpose: Prepare a RHEL or Fedora workstation for Module 1 (Developer Environment)
Duration: 5-25 minutes depending on the option chosen
Required for: Module 1 labs (Building, Security Scanning, SELinux Hardening)

What gets configured:

  • Podman 5.7.1, Buildah, and Skopeo

  • Rootless Podman with user namespaces

  • Registry access for Hummingbird and UBI images

  • OpenJDK 21 and Quarkus CLI

  • Security tools (cosign, syft, grype)

  • Podman Desktop GUI

Choose one of the three options below to prepare your developer environment. Option 1 is the fastest path.

Option 1: RHDP Pre-Provisioned Environment (Recommended)

The Red Hat Demo Platform (RHDP) provides a ready-to-use RHEL 9 desktop with Podman and container tools pre-installed. This is the fastest way to get started.

Deploy the Environment

  1. Click the link below to open the RHDP catalog:

  2. Log in with your Red Hat credentials (SSO for associates, user account for partners)

  3. Click Order and wait for the environment to provision (typically 5-10 minutes)

  4. Once ready, you will receive connection details (SSH or web console access)

After Provisioning

The RHDP RHEL 9 Desktop environment comes with Podman and core container tools pre-installed. Depending on the environment version, you may still need to run the automation script (Option 2) or selected manual steps (Option 3) to complete the Hummingbird-specific configuration such as registry setup, JDK 21, Quarkus CLI, and Podman Desktop.

Verify your environment is ready:

podman --version
buildah --version
skopeo --version
java --version
quarkus --version
cosign version
syft version
grype version
podman info --format '{{.Host.Security.Rootless}}'

If any of the above commands fail, use Option 2 (script) or Option 3 (manual steps) below to complete the setup.

Option 2: Automation Script

Run a single script to install and configure everything automatically. This works on RHEL 9.5, RHEL 10.0, or Fedora 43.

You can either download the script and run it, or copy-paste the inline version below.

Download and Run

curl -LO https://raw.githubusercontent.com/rhpds/zero-cve-hummingbird-showroom/main/content/modules/ROOT/assets/attachments/setup-rhel-developer.sh
chmod +x setup-rhel-developer.sh
./setup-rhel-developer.sh

After the script completes, reload your shell so the Quarkus CLI is on your PATH:

source ~/.bashrc

Or download directly: setup-rhel-developer.sh

Or Copy-Paste Inline

bash << 'SETUP_SCRIPT'
#!/bin/bash
set -euo pipefail

echo "=== Hummingbird Workshop: RHEL Developer Environment Setup ==="
echo ""

# --- System Update ---
echo "[1/13] Updating system packages..."
sudo dnf update -y

# --- Core Container Tools ---
echo "[2/13] Installing Podman, Buildah, Skopeo, and container-tools..."
sudo dnf install -y podman buildah skopeo container-tools

# --- JDK 21 ---
echo "[3/13] Installing OpenJDK 21..."
sudo dnf install -y java-21-openjdk-devel

# --- Quarkus CLI via JBang ---
echo "[4/13] Installing Quarkus CLI (via JBang)..."
curl -Ls https://sh.jbang.dev | bash -s - trust add https://repo1.maven.org/maven2/io/quarkus/quarkus-cli/
curl -Ls https://sh.jbang.dev | bash -s - app install --fresh --force quarkus@quarkusio
export PATH="$HOME/.jbang/bin:$PATH"
if ! grep -q '.jbang/bin' ~/.bashrc 2>/dev/null; then
    echo 'export PATH="$HOME/.jbang/bin:$PATH"' >> ~/.bashrc
fi

# --- Rootless Podman: subuid/subgid ---
echo "[5/13] Configuring rootless Podman (subuid/subgid)..."
if ! grep -q "^$(whoami):" /etc/subuid; then
    echo "$(whoami):100000:65536" | sudo tee -a /etc/subuid
fi
if ! grep -q "^$(whoami):" /etc/subgid; then
    echo "$(whoami):100000:65536" | sudo tee -a /etc/subgid
fi

# --- User Lingering ---
echo "[6/13] Enabling user lingering..."
sudo loginctl enable-linger $(whoami)

# --- Podman Socket ---
echo "[7/13] Enabling Podman socket..."
systemctl --user enable --now podman.socket

# --- Registry Configuration ---
echo "[8/13] Configuring container registries..."
mkdir -p ~/.config/containers

cat > ~/.config/containers/registries.conf << 'EOF'
unqualified-search-registries = ["registry.access.redhat.com", "quay.io", "docker.io"]

[[registry]]
location = "registry.access.redhat.com"
insecure = false
blocked = false

[[registry]]
location = "registry.redhat.io"
insecure = false
blocked = false

[[registry]]
location = "quay.io"
insecure = false
blocked = false

[[registry]]
location = "quay.io/hummingbird-hatchling"
insecure = false
blocked = false
EOF

# --- Storage Configuration ---
echo "[9/13] Configuring container storage..."
cat > ~/.config/containers/storage.conf << 'EOF'
[storage]
driver = "overlay"

[storage.options]
mount_program = "/usr/bin/fuse-overlayfs"

[storage.options.overlay]
mountopt = "nodev,metacopy=on"
EOF

# --- Security Tools: Cosign ---
echo "[10/13] Installing Cosign (image signing)..."
COSIGN_VERSION=v2.4.1
curl -sLO https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64
sudo install -m 755 cosign-linux-amd64 /usr/local/bin/cosign
rm -f cosign-linux-amd64

# --- Security Tools: Syft ---
echo "[11/13] Installing Syft (SBOM generation)..."
SYFT_VERSION=v1.17.0
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sudo sh -s -- -b /usr/local/bin ${SYFT_VERSION}

# --- Security Tools: Grype ---
echo "[12/13] Installing Grype (vulnerability scanning)..."
GRYPE_VERSION=v0.88.0
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sudo sh -s -- -b /usr/local/bin ${GRYPE_VERSION}

# --- Podman Desktop ---
echo "[13/13] Installing Podman Desktop..."
RHEL_MAJOR=$(rpm -E '%{rhel}' 2>/dev/null || echo "9")
ARCH=$(uname -m)
LAUNCH_CMD="podman-desktop"

if [[ "${RHEL_MAJOR}" -ge 10 ]]; then
    # RHEL 10+: official Red Hat build via extensions repo
    sudo subscription-manager repos --enable "rhel-${RHEL_MAJOR}-for-${ARCH}-extensions-rpms" 2>/dev/null || true
    if sudo dnf install -y rh-podman-desktop 2>/dev/null; then
        echo "Podman Desktop (Red Hat build) installed via dnf."
    else
        flatpak remote-add --if-not-exists --user flathub https://flathub.org/repo/flathub.flatpakrepo
        flatpak install --user -y flathub io.podman_desktop.PodmanDesktop
        LAUNCH_CMD="flatpak run io.podman_desktop.PodmanDesktop"
        echo "Podman Desktop installed via Flatpak."
    fi
else
    # RHEL 9: Flatpak from Flathub (official recommendation for Linux)
    flatpak remote-add --if-not-exists --user flathub https://flathub.org/repo/flathub.flatpakrepo
    if flatpak install --user -y flathub io.podman_desktop.PodmanDesktop 2>/dev/null; then
        LAUNCH_CMD="flatpak run io.podman_desktop.PodmanDesktop"
        echo "Podman Desktop installed via Flatpak."
    else
        echo "Warning: Podman Desktop could not be installed automatically."
        echo "Install manually from https://podman-desktop.io/downloads"
        LAUNCH_CMD="# see https://podman-desktop.io/downloads"
    fi
fi

echo ""
echo "=== Setup Complete ==="
echo "Podman:      $(podman --version)"
echo "Buildah:     $(buildah --version)"
echo "Skopeo:      $(skopeo --version)"
echo "Java:        $(java --version 2>&1 | head -1)"
echo "Quarkus CLI: $(quarkus --version)"
echo "Cosign:      $(cosign version 2>&1 | head -1)"
echo "Syft:        $(syft version 2>&1 | head -1)"
echo "Grype:       $(grype version 2>&1 | head -1)"
echo "Rootless:    $(podman info --format '{{.Host.Security.Rootless}}')"
echo "Storage:     $(podman info --format '{{.Store.GraphDriverName}}')"
echo ""
echo "You can now launch Podman Desktop with: ${LAUNCH_CMD} &"
echo ""
echo "IMPORTANT: Run 'source ~/.bashrc' to make the Quarkus CLI available."
echo ""
echo "Proceed to Module 1 to start the workshop labs."
SETUP_SCRIPT

Once the script completes, reload your shell so the Quarkus CLI is on your PATH:

source ~/.bashrc

Verify the setup:

quarkus --version
cosign version
syft version
grype version
podman pull quay.io/hummingbird/openjdk:21-runtime
podman pull registry.access.redhat.com/ubi9/openjdk-21:latest
podman images | grep -E "hummingbird|ubi9"

Option 3: Manual Steps

Follow the detailed steps below if you prefer to configure each component individually, or if you need to troubleshoot a specific part of the setup.

System Update and Core Tool Installation

Step 1: System Update

sudo dnf update -y
Expected output:
Complete!

If a kernel update is installed, you may need to reboot your system before proceeding. Check with:

sudo needs-restarting -r

Step 2: Install Core Container Tools

Install Podman, Buildah, and Skopeo - the foundational tools for container workflows.

sudo dnf install -y podman buildah skopeo container-tools
What these tools do:
  • Podman: Daemonless container engine for running and managing containers

  • Buildah: Specialized tool for building container images

  • Skopeo: Utility for inspecting, copying, and managing container images across registries

Step 3: Verify Container Tools

podman --version
buildah --version
skopeo --version
Expected output:
podman version 5.7.1
buildah version 1.38.0
skopeo version 1.16.1

Versions may vary slightly depending on your distribution. Podman 5.7.1 or later is recommended for this workshop.

JDK 21 and Quarkus CLI

The workshop uses Quarkus as its primary application framework. The Quarkus CLI scaffolds projects in a single command.

Step 4: Install OpenJDK 21

sudo dnf install -y java-21-openjdk-devel

Verify the installation:

java --version
Expected output:
openjdk 21.0.6 2025-01-21 LTS
OpenJDK Runtime Environment (Red_Hat-21.0.6.0.7-1) (build 21.0.6+7-LTS)
OpenJDK 64-Bit Server VM (Red_Hat-21.0.6.0.7-1) (build 21.0.6+7-LTS, mixed mode, sharing)

The exact version may differ. Any OpenJDK 21.x release will work for this workshop.

Step 5: Install Quarkus CLI via JBang

JBang is a lightweight launcher for JVM-based tools. The Quarkus CLI uses it for installation and version management.

curl -Ls https://sh.jbang.dev | bash -s - trust add https://repo1.maven.org/maven2/io/quarkus/quarkus-cli/
curl -Ls https://sh.jbang.dev | bash -s - app install --fresh --force quarkus@quarkusio

Add JBang to your PATH for this session and persist it:

export PATH="$HOME/.jbang/bin:$PATH"
echo 'export PATH="$HOME/.jbang/bin:$PATH"' >> ~/.bashrc

Verify the Quarkus CLI:

quarkus --version
Expected output:
3.17.0

The Quarkus CLI version may be newer than shown. Any 3.x release is compatible with this workshop.

Rootless Podman Configuration

Rootless containers run without requiring root privileges, enhancing security. Let’s configure the necessary user namespaces.

Step 6: Configure Rootless Podman

Configure user namespaces and subuid/subgid mappings:

# Check if subuid/subgid are already configured
grep "^$(whoami):" /etc/subuid /etc/subgid

# If empty, configure them (100000 subordinate UIDs/GIDs starting at 100000)
if ! grep -q "^$(whoami):" /etc/subuid; then
    echo "$(whoami):100000:65536" | sudo tee -a /etc/subuid
fi

if ! grep -q "^$(whoami):" /etc/subgid; then
    echo "$(whoami):100000:65536" | sudo tee -a /etc/subgid
fi
What this does:

Configures 65,536 subordinate UIDs and GIDs for your user, allowing Podman to map container user namespaces to your regular user account.

Step 7: Enable User Lingering

Enable lingering to allow your user systemd services to run even when you’re not logged in:

sudo loginctl enable-linger $(whoami)

This is essential for the Podman socket to remain active for Podman Desktop integration.

Step 8: Start Podman Socket

Enable and start the Podman socket for API access (used by Podman Desktop):

systemctl --user enable --now podman.socket
systemctl --user status podman.socket
Expected output:
● podman.socket - Podman API Socket
     Loaded: loaded (/usr/lib/systemd/user/podman.socket; enabled)
     Active: active (listening)

Press q to exit the status view.

Step 9: Verify Rootless Configuration

podman info --format '{{.Host.Security.Rootless}}'
Expected output:
true
podman run --rm quay.io/hummingbird/openjdk:21-runtime java --version
Expected output:
openjdk 21.0.6 2025-01-21 LTS
...

If you see permission errors, verify your subuid/subgid configuration and ensure you’ve logged out and back in after making changes to /etc/subuid and /etc/subgid.

Registry Configuration

Configure authentication and trust for the registries hosting Hummingbird and UBI images.

Step 10: Configure Registry Authentication

On RHEL and Fedora systems, container registries are configured in /etc/containers/registries.conf (system-wide) or ~/.config/containers/registries.conf (user-specific). For this workshop, we’ll use user-specific configuration.

mkdir -p ~/.config/containers

cat > ~/.config/containers/registries.conf << 'EOF'
# Registry configuration for Hummingbird and UBI images

# Search registries for unqualified image names
unqualified-search-registries = ["registry.access.redhat.com", "quay.io", "docker.io"]

# Red Hat Registry (UBI images)
[[registry]]
location = "registry.access.redhat.com"
insecure = false
blocked = false

# Red Hat Certified Registry (subscription required for some images)
[[registry]]
location = "registry.redhat.io"
insecure = false
blocked = false

# Quay.io (Hummingbird images)
[[registry]]
location = "quay.io"
insecure = false
blocked = false

# Hummingbird-specific namespace
[[registry]]
location = "quay.io/hummingbird-hatchling"
insecure = false
blocked = false
EOF
What this configuration does:
  • unqualified-search-registries: When you run podman pull nodejs, Podman searches these registries in order

  • registry.access.redhat.com: Public Red Hat registry (no authentication required for UBI images)

  • registry.redhat.io: Authenticated Red Hat registry (requires Red Hat subscription for some images)

  • quay.io: Public container registry hosting Hummingbird images

Red Hat Subscription Note: UBI (Universal Base Images) from registry.access.redhat.com are freely available without authentication. For authenticated access to registry.redhat.io (needed for some RHEL-specific content), you would need to authenticate with your Red Hat credentials.

Step 11: Test Registry Access

Pull a sample Hummingbird image to verify registry connectivity:

podman pull quay.io/hummingbird/openjdk:21-runtime
Expected output:
Trying to pull quay.io/hummingbird-hatchling/openjdk:21-runtime...
Getting image source signatures
Copying blob sha256:abc123...
Copying blob sha256:def456...
Copying config sha256:789...
Writing manifest to image destination
Storing signatures

Verify the image is available locally:

podman images | grep hummingbird
Expected output:
quay.io/hummingbird-hatchling/openjdk  21-runtime  abc123def456  2 days ago  253 MB

Step 12: Test UBI Registry Access

podman pull registry.access.redhat.com/ubi9/openjdk-21:latest
Expected output:
Trying to pull registry.access.redhat.com/ubi9/openjdk-21:latest...
Getting image source signatures
Copying blob sha256:xyz789...
Writing manifest to image destination
Storing signatures

RHEL vs Fedora Registry Behavior:

  • RHEL systems: Come pre-configured with Red Hat registries in /etc/containers/registries.conf

  • Fedora systems: May need manual configuration as shown above

  • User config: ~/.config/containers/registries.conf overrides system config for your user

To view your active configuration:

podman info --format json | jq -r '.registries'

Optimize storage configuration for better performance on RHEL/Fedora:

cat > ~/.config/containers/storage.conf << 'EOF'
[storage]
driver = "overlay"

[storage.options]
mount_program = "/usr/bin/fuse-overlayfs"

[storage.options.overlay]
mountopt = "nodev,metacopy=on"
EOF
What these settings do:
  • overlay driver: Modern, efficient layered filesystem (default on RHEL 9+)

  • fuse-overlayfs: Allows rootless users to use overlay mounts without privileges

  • metacopy=on: Performance optimization for metadata operations

On RHEL 9+ and recent Fedora versions, the overlay driver with fuse-overlayfs is already the default for rootless Podman. This configuration explicitly sets it for consistency across environments.

Security Tools Installation

Install tools for container image signing, SBOM generation, and vulnerability scanning. These are used extensively in Module 1 labs.

Step 14: Install Cosign (Image Signing)

Cosign is used to sign and verify container images:

COSIGN_VERSION=v2.4.1
curl -LO https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64
sudo install -m 755 cosign-linux-amd64 /usr/local/bin/cosign
rm cosign-linux-amd64

cosign version
Expected output:
cosign version {cosign-version}

Cosign in Production:

  • Development: Generate key pairs locally (as we’ll do in this workshop)

  • Production: Use keyless signing with OIDC identity or KMS-backed keys

  • CI/CD: Integrate with HashiCorp Vault or cloud KMS (AWS KMS, Azure Key Vault, GCP KMS)

Step 15: Install Syft (SBOM Generation)

Syft generates Software Bill of Materials (SBOMs) from container images:

SYFT_VERSION=v1.17.0
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sudo sh -s -- -b /usr/local/bin ${SYFT_VERSION}

syft version
Expected output:
syft version 1.17.0

What is an SBOM?

A Software Bill of Materials (SBOM) is a complete inventory of all packages, libraries, and dependencies in your container image. SBOMs are essential for:

  • Supply chain security: Know exactly what’s in your images

  • Compliance: Meet regulatory requirements (NIST, EU Cyber Resilience Act)

  • Vulnerability management: Quickly identify affected systems when new CVEs are disclosed

Step 16: Install Grype (Vulnerability Scanning)

Grype scans container images for known CVEs:

GRYPE_VERSION=v0.88.0
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sudo sh -s -- -b /usr/local/bin ${GRYPE_VERSION}

grype version
Expected output:
grype version 0.88.0

Grype downloads a vulnerability database on first run. This may take a few minutes initially. The database is cached locally and updated periodically.

Podman Desktop Installation

The installation method differs between RHEL 9 and RHEL 10.

Step 17a: Install Podman Desktop on RHEL 9 (Flatpak)

On RHEL 9, Flatpak from Flathub is the official recommended installation method.

flatpak remote-add --if-not-exists --user flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak install --user -y flathub io.podman_desktop.PodmanDesktop

flatpak is included in the RHEL 9 AppStream repository. If it is not already installed:

sudo dnf install -y flatpak

The --user flag installs Podman Desktop for the current user only and does not require sudo for the install step. The .desktop launcher entry is created automatically.

Step 17b: Install Podman Desktop on RHEL 10 (dnf)

On RHEL 10, the official Red Hat build is available via the extensions repository.

sudo subscription-manager repos --enable "rhel-10-for-$(uname -m)-extensions-rpms"
sudo dnf install -y rh-podman-desktop

If the extensions repository is not available in your subscription, fall back to Flatpak as shown in Step 17a above.

Step 18: Launch Podman Desktop

RHEL 9 (Flatpak):

flatpak run io.podman_desktop.PodmanDesktop &

RHEL 10 (dnf):

podman-desktop &
Initial Setup:
  1. Podman Desktop will detect your Podman installation automatically

  2. Verify the connection shows as "Running" in the main dashboard

  3. The status indicator in the bottom-left should show Podman version 5.7.1

If Podman Desktop doesn’t detect your Podman socket, check that systemctl --user status podman.socket shows it as active and listening.

Podman Desktop Quick Reference

Main Navigation (Left Sidebar):

  • Dashboard: Overview of running containers and resources

  • Images: Browse, pull, and manage container images

  • Containers: View and manage running/stopped containers

  • Pods: Manage groups of containers (Kubernetes-style pods)

  • Volumes: Persistent storage management

  • Extensions: Add functionality (Kind, OpenShift Local, etc.)

  • Settings: Configure registries, Podman machine, preferences

Hummingbird No-Shell Design:

Hummingbird images intentionally omit shells (/bin/sh, /bin/bash) to minimize attack surface. You cannot exec into them interactively. To debug a running Hummingbird container, use the sidecar pattern:

podman run -it --rm \
  --pid=container:<container-name> \
  --net=container:<container-name> \
  registry.access.redhat.com/ubi9/ubi:latest \
  /bin/bash

This gives you a shell with access to the target container’s PID and network namespaces while the Hummingbird container itself remains hardened.

Podman Desktop Documentation: * Official Podman Desktop Docs * Podman Desktop GitHub

Troubleshooting

Issue: Podman socket not starting

systemctl --user restart podman.socket
systemctl --user status podman.socket
journalctl --user -u podman.socket -n 50

Issue: Registry authentication failures

podman login quay.io
podman login registry.access.redhat.com

Issue: Permission errors with rootless Podman

grep "^$(whoami):" /etc/subuid /etc/subgid

loginctl terminate-user $(whoami)
# Log back in

Issue: Podman Desktop not detecting Podman

Ensure the Podman socket is active and listening:

systemctl --user status podman.socket
ls -la /run/user/$(id -u)/podman/podman.sock

Issue: Quarkus CLI not found after installation

Ensure JBang’s bin directory is on your PATH:

export PATH="$HOME/.jbang/bin:$PATH"
quarkus --version

If JBang itself isn’t installed, re-run the installation:

curl -Ls https://sh.jbang.dev | bash -s - trust add https://repo1.maven.org/maven2/io/quarkus/quarkus-cli/
curl -Ls https://sh.jbang.dev | bash -s - app install --fresh --force quarkus@quarkusio