RedHat Enterprise Linux containers for R

I have wanted to create RedHat Enterprise Linux Docker containers for a long time, to better test some of the distro-specific tools I work on.

Prerequisites

  • A RedHat Enterprise Linux subscription. I got the Individual Developer Subscription. This subscription allows creating up to 16 physical or virtual instances.

  • I created one activation key for each RHEL version I used, so one each for RHEL 7.x, 8.x and 9.x, in the new Hybrid Cloud Console. (Services -> RedHat Enterprise Linux -> Inventory -> System Configuration -> Activation Keys.)

  • The “Activation Keys” page in the RedHat console also shows the “Organization ID”, I also needed that.

  • It might make sense to change the staleness config, so that stale VMs are deleted as soon as possible. This helps if the container quits before unregistering with RedHat. I did that in the hybrid cloud console, in Services -> Red Hat Enterprise Linux -> Inventory -> System Configuration -> Staleness and Deletion. I set all three values to the smallest possible, 1 day, 2 days and 3 days respectively.

Dockerfile

The Dockerfiles are slightly different for RHEL 7, 8 and 9. Here I’ll only include the one for RHEL 9.x, and link to the others.

Dockerfile for R on RHEL9See on GitHub
1
2
FROM redhat/ubi9:latest
ARG R_VERSION=release

I started with the RedHat Universal Base Image.

It makes sense to make the R version a build argument, it defaults to the current release of R.

1
2
3
# To work around a rig bug and a pak bug
ENV RIG_PLATFORM=rhel-9
ENV PKG_SYSREQS_PLATFORM=redhat-9

Temporary workarounds for bugs in rig and pak.

Now comes the main part of the file.

1
2
3
4
5
6
7
8
9
10
11
12
RUN --mount=type=secret,id=REDHAT_ORG_RHEL9 \
--mount=type=secret,id=REDHAT_ACTIVATION_KEY_RHEL9 \
subscription-manager register \
--org `cat /run/secrets/REDHAT_ORG_RHEL9` \
--activationkey `cat /run/secrets/REDHAT_ACTIVATION_KEY_RHEL9` && \
yum install -y https://github.com/r-lib/rig/releases/download/latest/r-rig-latest-1.$(arch).rpm && \
rig add ${R_VERSION} && \
sed -i 's|/rhel8/|/rhel9/|g' /opt/R/current/lib/R/library/base/R/Rprofile && \
yum install -y git libgit2 fribidi-devel glibc-langpack-en && \
yum clean all && \
rm -rf /tmp/* && \
subscription-manager unregister

During the build I had to register the container with subscription-manager because I needed to use the default RedHat repositories to install R’s dependencies. Some of them are not available on the default UBI repositories. This means that I had to inject two pieces of information into the bulding container: the organization id and the activation key.

I put all these into a single step, so there are no Docker layers that store a state that is registered to RedHat. If such a layer was included in a public Docker image, somebody could use it to run an instance against my RedHat subscription, in theory. I don’t know enough of how the subscription management work to decide if this is a real danger or not, but did not want to risk the leakage of my organization id or activation keys.

I used build secrets to pass the org id and the activation key to the builder, because it is not safe to use build arguments for confidential data. REDHAT_ORG_RHEL9 and REDHAT_ACTIVATION_KEY_RHEL9 are passed to docker build as environment variables and they are stored in files in the /run/secrets/ directory in the container during build.

The next two lines install rig and then use rig to install the requested R version.

The sed line is another temporary workaround for a bug in rig.

I preinstalled a couple of packages (git, libgit2, fribidi-devel) from the RedHat repos, so in the final container it would be possible to install the tidyverse package without registration. I also pre-installed glibc-langpack-en, so the en_US.UTF-8 locale is available, this is needed for R CMD check.

The final lines clean up and unregister the instance.

1
2
3
RUN curl -o /usr/local/bin/checkbashisms \
https://raw.githubusercontent.com/r-hub/containers/main/dependencies/checkbashisms/checkbashisms && \
chmod +x /usr/local/bin/checkbashisms

This last part installs the checkbashisms script. It is currently not available in an RHEL 9 package, as far as I can tell. I could not find a way to skip the check that needs it during R CMD check. (No, the _R_CHECK_BASHISMS_=false env var does not work.)

The image has a single R version, to keep it lean, but I can use rig to easily add more, using one of the Posit R builds, from R 3.0.0 to the daily build of the next R release, or the development version of R:

1
2
3
4
rig add 4.3.3
rig add next
rig add devel
...

Note that using rig needs registration via subscription-manager register first.

I can build this image with:

1
2
3
4
REDHAT_ORG_RHEL9=<org> REDHAT_ACTIVATION_KEY_RHEL9=<key> \
docker buildx build --platform linux/amd64,linux/arm64 \
--secret id=REDHAT_ORG_RHEL9 --secret id=REDHAT_ACTIVATION_KEY_RHEL9 \
--load -t rhel9 .

This Dockerfile is in the r-hub/containers repo. The RHEL 7 and RHEL 8 images are very similar, and they are also in the same repository.

The built images are in the GHCR and Docker Hub registries, for both linux/amd64 and linux/arm64 platforms. They are rebuilt daily.

The R-hub containers web site has the latest information about the software on the images. The rhel7, rhel8, rhel9 images are at the bottom.

GitHub Actions

I managed to use the RHEL 8 and RHEL9 images on GitHub Actions. The RHEL 7 image is trickier because RHEL 7 cannot run Node 20.x, which is needed for the newer versions of the Node.js actions, e.g. actions/checkout@v4.

The repository of the cli package has a workflow that runs R CMD check both on RHEL 8 and RHEL 9, using the standard r-lib/actions actions.

You’ll need to add your own RedHat organization id and activation keys as repository (or organization) secrets to use this workflow.

Some more interesting parts:

GHA workflow for `R CMD check` on the RHEL containersSee on GitHub
1
2
3
4
5
6
7
8
9
10
11
12
jobs:
rhel:
runs-on: ubuntu-latest
name: ${{ matrix.config.os }} (${{ matrix.config.r }})
strategy:
fail-fast: false
matrix:
config:
- { os: rhel8, r: release, key: REDHAT_ACTIVATION_KEY_RHEL8 }
- { os: rhel9, r: release, key: REDHAT_ACTIVATION_KEY_RHEL9 }
container:
image: ghcr.io/r-hub/containers/${{ matrix.config.os }}:latest

The activation keys are taken from the REDHAT_ACTIVATION_KEY_RHEL8 and REDHAT_ACTIVATION_KEY_RHEL9 secrets.

GHA workflow for `R CMD check` on the RHEL containersSee on GitHub
1
2
3
4
5
6
7
8
9
steps:
- uses: actions/checkout@v4

- name: Register
run: |
subscription-manager register \
--org ${{ secrets.REDHAT_ORG }} \
--activationkey ${{ secrets[matrix.config.key] }}
shell: bash

The REDHAT_ORG secret is used for the organization id.

GHA workflow for `R CMD check` on the RHEL containersSee on GitHub
1
2
3
4
5
- name: Install R
if: ${{ matrix.config.r != 'release' }}
run: |
rig add ${{ matrix.config.r }}
shell: bash

I used rig to install R, unless the preinstalled version is requested.

GHA workflow for `R CMD check` on the RHEL containersSee on GitHub
1
2
3
4
5
6
7
8
9
10
- uses: r-lib/actions/setup-r-dependencies@v2
with:
extra-packages: any::rcmdcheck
needs: check

- uses: r-lib/actions/check-r-package@v2
with:
build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")'
env:
NOT_CRAN: true

This is mostly as usual, but since the image does not set NOT_CRAN, I do it here, to make testthat::skip_on_cran() work.

GHA workflow for `R CMD check` on the RHEL containersSee on GitHub
1
2
3
4
- name: Unregister
if: always()
run: |
subscription-manager unregister || true

Always unregister at the end.

Improvements

It would be great to be able to use these images in R-hub but that seems difficult because of the need for the secrets.

I could use a custom GHA shell to run RHEL 7, the same way I ran s390x Linux. I am not sure how important this is, as RHEL 7 is not supported by RedHat any more.

Updates

2024-09-26: note about changing the staleness config.