2.2 Align local development and deployment environments

When developing and testing an app locally, it is important to ensure the environment is aligned with the target deployment environment. This might imply using e.g. multiple R and package versions for the local development of different applications. This is not possible with the typical setup (especially on Linux systems), where only one R version (the latest release) exists.

The idea is then to rely on the same version-stable rocker containers used for the deployments, using a containerized versioned RStudio instance for the local development. This is available through Rocker’s versioned stack, so we could use e.g. rocker/rstudio:4.4.1.

Note that the same version-stable instance of RStudio can be used across all different projects for which such version is relevant. For this reason, a sensible choice is to rely on rocker/verse images, which add tidyverse and devtools to the stack. They also include R Markdown system dependencies TinyTeX and pandoc, sparing the effort of the tedious extra install.

2.2.1 Running versioned RStudio instances

Assume we want to run a containerized versioned instance of RStudio for R 4.4.1, possibly alongside instances for other versions of R.

First of all, we need to get the image from docker-hub:

docker pull rocker/verse:4.4.1

We then want to have a running instance on localhost (127.0.0.1), with the following setup:

  • No authentication required (local setup).
  • Enable root by setting the environment variable ROOT to TRUE, so that e.g. sudo apt-get can be used in RStudio.
  • Use a version-specific port, e.g. 4000 for R 4.0.0, 4410 for R 4.4.1 and so on, so that we can use localhost for concurrent R version instances. We bind the port to localhost (127.0.0.1:4410), so it is only accessible locally (see the Rocker reference).
  • The development code of all relevant projects should live outside the container and be shared with it (and possibly multiple other containers), e.g. under ~/workspace on the host machine and /home/rstudio/workspace in the container.
    • For this to work w/o permission issues, the container user (rstudio) must match the UID of the host user ($UID). This has the effect of setting the ownership of ~/workspace on the host machine to $UID if it is not already owned by that user.
  • In order for the RStudio settings to persist if the container is recreated (e.g. after pulling a new rocker image), we also use a shared volume (like ~/.rstudio-config/4.4.1) for the /home/rstudio/.config/rstudio directory, which is version-specific in case of multiple R versions.
  • If we want to use Meld via the compareWith addins, we need to
    • map the DISPLAY environment variable and volume /tmp/.X11-unix,
    • add DISPLAY to Renviron,
    • install Meld,
    • install dbus-x11.
  • Use a version-specific name for the container running the RStudio instance, e.g. rstudio_4.4.1.
R_VER=4.4.1
SHARED_DIR=workspace
mkdir -p $HOME/.rstudio-config/$R_VER
docker run -d --restart=always \
  -p 127.0.0.1:$(echo $R_VER | sed 's/[.]//g')0:8787 \
  -e DISABLE_AUTH=true \
  -e ROOT=true \
  -e USERID=$UID \
  -e GROUPID=$GID \
  -v $HOME/$SHARED_DIR:/home/rstudio/$SHARED_DIR \
  -v $HOME/.rstudio-config/$R_VER:/home/rstudio/.config/rstudio \
  -e DISPLAY=$DISPLAY \
  -v /tmp/.X11-unix:/tmp/.X11-unix:ro \
  --name rstudio_$R_VER \
  rocker/verse:$R_VER
# R and RStudio are not getting the DISPLAY environment variable
docker exec rstudio_$R_VER bash -c \
  'echo "DISPLAY=${DISPLAY}" >> /usr/local/lib/R/etc/Renviron'
# Install Meld
docker exec rstudio_$R_VER bash -c \
  'apt-get update && apt-get install -y --no-install-recommends meld dbus-x11'

If you are using R_VER=4.4.1, the running RStudio can then be accessed by visiting http://localhost:4410/.

You may find convenient to define a shell function for these steps:

run_rstudio_ver() {
  local R_VER=${1:?"you must supply the R version as first argument"}
  local SHARED_DIR=${2:?"you must supply the shared directory as second argument"}
  local RVER_IMAGE=${3:-"verse"}
  local BASE_IMAGE=rocker/$RVER_IMAGE:$R_VER
  local PORT=$(echo $R_VER | sed 's/[.]//g')0
  local CONTAINER_NAME=rstudio_$R_VER
  echo "Containerized version-stable RStudio for R "$R_VER\
       "based on image "$BASE_IMAGE\
       "with shared volume "$SHARED_DIR
  docker pull $BASE_IMAGE &&
  mkdir -p $HOME/.rstudio-config/$R_VER &&
  docker run -d --restart=always \
    -p 127.0.0.1:$PORT:8787 \
    -e DISABLE_AUTH=true \
    -e ROOT=true \
    -e USERID=$UID \
    -e GROUPID=$GID \
    -v $HOME/$SHARED_DIR:/home/rstudio/$SHARED_DIR \
    -v $HOME/.rstudio-config/$R_VER:/home/rstudio/.config/rstudio \
    -e DISPLAY=$DISPLAY \
    -v /tmp/.X11-unix:/tmp/.X11-unix:ro \
    --name $CONTAINER_NAME \
    $BASE_IMAGE &&
  # R and RStudio are not getting the DISPLAY environment variable
  docker exec $CONTAINER_NAME bash -c \
    'echo "DISPLAY=${DISPLAY}" >> /usr/local/lib/R/etc/Renviron' &&
  # Install Meld
  docker exec $CONTAINER_NAME bash -c \
    'apt-get update && apt-get install -y --no-install-recommends meld dbus-x11' &&
  echo "RStudio running in container "$CONTAINER_NAME" on port "$PORT &&
  echo "visit http://localhost:"$PORT
}

which you can re-use as compact command for any R version:

run_rstudio_ver 4.4.1 workspace

Note that --restart=always specifies that the container should stay up and restart itself after stopping, e.g. upon machine reboot or docker upgrade, so that it is always available. Still, you can explicitly stop the running container with

docker stop rstudio_4.4.1

Alternatively, you can omit --restart=always and explicitly start the container whenever needed with

docker start rstudio_4.4.1

Note that start/stop operations do not affect the persistence of any files created in rstudio while the container is running. However if the container is removed, files created outside of mounted volumes do not persist (docker rm, see below). This is why we use a mounted volume for the ~/.config/rstudio directory.

2.2.2 Using podman instead of docker

podman can be used instead of docker to run the above commands. In that case, the active user in the container will be root, which is then mapped to the user that invoked the podman command when writing files to the shared volume. Because of this, RStudio does not set the desired home directory as the initial working directory. To correct this, add

echo '{ "initial_working_directory": "/home/rstudio" }' > $HOME/.rstudio-config/$R_VER/rstudio-prefs.json &&

After mkdir -p $HOME/.rstudio-config/$R_VER in the run_rstudio_ver function. If you run into issues, the discussion in this rocker pull request about rootless container support may help.

2.2.3 Best-supported R versions

This tutorial uses images based on the rocker-versioned2 repository. We recommend using the latest patch version for each minor version - e.g. 4.0.5 for 4.0.x, as these seem to be the most regularly updated images (see e.g. rocker/verse on docker hub).

2.2.4 Cleanup

docker rm $(docker stop rstudio_4.4.1)