Renv with Docker: How to Dockerize a Shiny Application with a Reproducible Environment

Estimated time:
time
min

You might have seen our previous articles on Dockerizing R scripts and R Shiny applications and were left wondering: <i>Are all of my dependency versions really fixed right now?</i> It's a good question, and the answer is both yes and no at the same time. Your dependencies are fixed, but their version might change in the future, which might cause unwanted behavior and crashes. <b>The solution?</b> Combining renv with Docker. The <code>renv</code> package is pretty well-known in the R community, and it creates project-specific packages and a list of dependencies with their versions, making it easy to share your project. Today you'll learn how to combine the best of both worlds or how to use renv with Docker. <blockquote>Need a refresher in R renv? <a href="https://appsilon.com/renv-how-to-manage-dependencies-in-r/" target="_blank" rel="noopener">Our guide for easy dependency management in R projects has you covered</a>.</blockquote> <h3>Table of contents</h3><ul><li><strong><a href="#what-is-renv">What is R renv and How it Works with Docker?</a></strong></li><li><strong><a href="#shiny-app">Creating an R Shiny Application with renv</a></strong></li><li><strong><a href="#docker">Renv with Docker: How to Launch Your App in a Container</a></strong></li><li><strong><a href="#summary">Summing up renv with Docker</a></strong></li></ul> <hr /> <h2 id="what-is-renv">What is R renv and How it Works with Docker?</h2> Renv stands for <i>Reproducible Environment</i> and is here to address the pain points many R developers have - managing environment dependencies. By default, R installs packages to a central library and shares them between projects. This means you don't have to install the dependencies again when working on a new project, but might introduce <b>dependency version conflict</b> when two or more developers are working on the same code. This typically results in a lot of crashes and bugs that occur only on one machine. The reason is always the same - environment differences and package mismatch. What renv allows you to do is to create a separate, reproducible environment that everyone can use. <b>But what does Docker have to do with renv?</b> Good question. Docker is a platform for developing, shipping and running applications in isolated environments on an OS level. It's kind of like a virtual machine but stripped down to bare bones. Combining renv with Docker means you'll be able to control the exact operating system your R code runs on along with system-wide dependencies, and you'll also be able to control R version and R package dependencies. It's a win-win configuration for ultimate reproducibility and portability control over your R projects. <b>Do you need more introduction-level resources for R and Docker?</b> Check out these articles: <ul><li><a href="https://appsilon.com/r-docker-getting-started" target="_blank" rel="noopener">How to run R Scripts in a Docker Container</a></li><li><a href="https://appsilon.com/r-shiny-docker-getting-started" target="_blank" rel="noopener">How to run R Shiny Apps in a Docker Container</a></li></ul> <h2 id="shiny-app">Creating an R Shiny Application with renv</h2> Creating and configuring a new R Shiny application with renv will take a couple of steps. You'll have to initialize a new RStudio project, install dependencies, and create an environment snapshot before even writing a single line of R code. <h3>Creating a New Project with renv</h3> To start, open RStudio and create a new project. The easiest way is to use the project wizard. Specify the path to where you want to save this new project, and make sure to <b>tick the "Use renv with this project"</b> checkbox. This will create all the files and folders needed to move forward: <img class="size-full wp-image-22488" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65aff9b20382920a2deeb587_Image-1-Creating-a-new-project-in-RStudio.webp" alt="Image 1 - Creating a new project in RStudio" width="1934" height="1996" /> Image 1 - Creating a new project in RStudio As soon as the new project is initialized, you'll see the following output in the R console: <img class="size-full wp-image-22490" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65aff9b333fd73557f142667_Image-2-Console-output.webp" alt="Image 2 - Console output" width="1594" height="540" /> Image 2 - Console output And if you were to navigate to the project folder, you'd see a bunch of R environment files: <img class="size-full wp-image-22492" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65aff9b47f84019491e055ee_Image-3-Directory-structure-created-by-renv.webp" alt="Image 3 - Directory structure created by renv" width="1710" height="1436" /> Image 3 - Directory structure created by renv Basically, you now have an isolated R environment free of any external dependencies. Just what you want. The next step is to install the dependencies needed to run our app. <h3>Installing R Dependencies with renv</h3> We'll create a simple R Shiny dashboard a bit later in the article, and it will rely on four external dependencies, so make sure to install them: <pre><code class="language-r">install.packages(c("shiny", "dplyr", "ggplot2", "gapminder"))</code></pre> You'll see the output that the packages are downloading: <img class="size-full wp-image-22494" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65aff9b6d8ce0378f4b09c3a_Image-4-Installing-R-packages.webp" alt="Image 4 - Installing R packages" width="1780" height="684" /> Image 4 - Installing R packages It will take a couple of minutes to download all dependencies and sub-dependencies, and you'll be asked for an installation confirmation when the download is complete: <img class="size-full wp-image-22496" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65aff9b75da7e16fa42e5afc_Image-5-Confirming-the-installation-in-a-renv-folder.webp" alt="Image 5 - Confirming the installation in a renv folder" width="1826" height="688" /> Image 5 - Confirming the installation in a renv folder Simply type "Y" and the packages will be installed in your isolated environment: <img class="size-full wp-image-22498" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65aff9b901f899ebf8e562b9_Image-6-Package-installation-success-message.webp" alt="Image 6 - Package installation success message" width="1826" height="748" /> Image 6 - Package installation success message The packages are now installed, but the R environment isn't aware of them. We'll have to write the R Shiny application first, and then create an environment snapshot. <h3>Coding the R Shiny application</h3> The Shiny app code will feel familiar if you've followed our previous articles on R and Docker. Put simply, it's an app that allows to user to change the continent (dropdown menu), and the two resulting charts change automatically. The app uses the <a href="https://appsilon.com/r-dplyr-gapminder/" target="_blank" rel="noopener">Gapminder dataset</a> to show the shift in average life expectancy and average GDP per capita over time, summarized on all countries belonging to a continent. <b>Take extra notes</b> on the call to <code>options()</code> function. We use it to hardcode the host and port values on which the Shiny application will run. This will make the deployment process easier later on. Anyhow, here's the full source code for our Shiny app (saved as <code>app.R</code>): <pre><code class="language-r">library(shiny) library(dplyr) library(ggplot2) library(gapminder) <br># Specify the application port options(shiny.host = "0.0.0.0") options(shiny.port = 8180) <br> ui &lt;- fluidPage(  sidebarLayout(    sidebarPanel(      tags$h4("Gapminder Dashboard"),      tags$hr(),      selectInput(inputId = "inContinent", label = "Continent", choices = unique(gapminder$continent), selected = "Europe")    ),    mainPanel(      plotOutput(outputId = "outChartLifeExp"),      plotOutput(outputId = "outChartGDP")    )  ) ) <br>server &lt;- function(input, output, session) {  # Filter data and store as reactive value  data &lt;- reactive({    gapminder %&gt;%      filter(continent == input$inContinent) %&gt;%      group_by(year) %&gt;%      summarise(        AvgLifeExp = round(mean(lifeExp)),        AvgGdpPercap = round(mean(gdpPercap), digits = 2)      )  }) <br>  # Common properties for charts  chart_theme &lt;- ggplot2::theme(    plot.title = element_text(hjust = 0.5, size = 20, face = "bold"),    axis.title.x = element_text(size = 15),    axis.title.y = element_text(size = 15),    axis.text.x = element_text(size = 12),    axis.text.y = element_text(size = 12)  ) <br>  # Render Life Exp chart  output$outChartLifeExp &lt;- renderPlot({    ggplot(data(), aes(x = year, y = AvgLifeExp)) +      geom_col(fill = "#0099f9") +      geom_text(aes(label = AvgLifeExp), vjust = 2, size = 6, color = "#ffffff") +      labs(title = paste("Average life expectancy in", input$inContinent)) +      theme_classic() +      chart_theme  }) <br>  # Render GDP chart  output$outChartGDP &lt;- renderPlot({    ggplot(data(), aes(x = year, y = AvgGdpPercap)) +      geom_line(color = "#f96000", size = 2) +      geom_point(color = "#f96000", size = 5) +      geom_label(        aes(label = AvgGdpPercap),        nudge_x = 0.25,        nudge_y = 0.25      ) +      labs(title = paste("Average GDP per capita in", input$inContinent)) +      theme_classic() +      chart_theme  }) } <br>shinyApp(ui = ui, server = server)</code></pre> This is what the app looks like when you run it: <img class="size-full wp-image-22500" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65aff9ba92c0c19b84405f22_Image-7-Custom-R-Shiny-application.webp" alt="Image 7 - Custom R Shiny application" width="2430" height="1918" /> Image 7 - Custom R Shiny application As expected, you can change the continent value and the charts will re-render automatically: <img class="size-full wp-image-22502" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65aff9bcfaf88fa49fea22c6_Image-8-Custom-R-Shiny-application-2.webp" alt="Image 8 - Custom R Shiny application (2)" width="2430" height="1918" /> Image 8 - Custom R Shiny application (2) The Shiny app created in this section is fairly simple, but we don't need anything more complex. The purpose is to showcase how the Docker renv combination works together, after all. In case you need a refresher on data visualization in R: <ul><li><a href="https://appsilon.com/ggplot2-bar-charts/" target="_blank" rel="noopener">How to Make Stunning Bar Charts in R</a></li><li><a href="https://appsilon.com/ggplot2-line-charts/" target="_blank" rel="noopener">How to Make Stunning Line Charts in R</a></li></ul> <h3>Creating an R renv Environment Snapshot</h3> The final step in this section is to create an R environment snapshot. We're doing this step now because renv will scan for the dependencies that are actually used in R scripts and won't pick on any that are installed but unused. Run the following command from the R console to create a snapshot: <pre><code class="language-r">renv::snapshot()</code></pre> This is the output you will see: <img class="size-full wp-image-22504" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65aff9bd32cb89e393b74b39_Image-9-Creating-the-environment-snapshot.webp" alt="Image 9 - Creating the environment snapshot" width="1768" height="814" /> Image 9 - Creating the environment snapshot Scroll down to the bottom and confirm the snapshot creation. As soon as you do, the folder structure in our isolated R environment will change: <img class="size-full wp-image-22506" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65aff9bfa0310cd414cb8028_Image-10-Updated-directory-structure.webp" alt="Image 10 - Updated directory structure" width="1710" height="1902" /> Image 10 - Updated directory structure The <code>renv.lock</code> file now also has a list of all dependencies and sub-dependencies used in our project: <img class="size-full wp-image-22508" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65aff9c097e73e854ed402c5_Image-11-Contents-of-a-renv.lock-file.webp" alt="Image 11 - Contents of a renv.lock file" width="1780" height="1996" /> Image 11 - Contents of a renv.lock file And that's it - you've done everything needed on the renv end. Now it's time to start working with Docker. <h2 id="docker">Renv with Docker: How to Launch Your App in a Container</h2> This article section will teach you how to write a <code>Dockerfile</code> suitable for using renv with Docker, and also how to create an image and container using a couple of shell commands. <h3>Writing a Dockerfile for R renv</h3> A <code>Dockerfile</code> is a simple set of instructions responsible for telling Docker how to build an image. We want to use a base image that already has R installed, and then copy our app and install all of the dependencies. The syntax of this file will take you some time to get used to, so let's go over all of the specific keywords used: <ul><li><code>FROM</code>: Tells Docker what is the base image on which your image will be based. For example, the <code>rocker/shiny</code> image already has R and Shiny installed, so it's a good starting point.</li><li><code>RUN</code>: Runs a shell command, such as the one for installing R packages, and creating directories, and files.</li><li><code>WORKDIR</code>: Sets a working directory so you don't have to specify a full path every time you want to copy a file.</li><li><code>COPY</code>: Copies contents from a local machine to a Docker container. You have to specify the path to the local file first, followed by a path to the file on the container.</li><li><code>EXPOSE</code>: Exposes a port of a Docker container so we can access it from a local machine.</li><li><code>CMD</code>: Command used every time you launch the Docker container. We'll use it to run our Shiny app.</li></ul> To get started, create a new file and name it <code>Dockerfile</code>, without any extensions. Paste the following contents inside it: <pre><code class="language-dockerfile"># Base R Shiny image FROM rocker/shiny <br># Make a directory in the container RUN mkdir /home/shiny-app <br># Install Renv RUN R -e "install.packages('renv', repos = c(CRAN = 'https://cloud.r-project.org'))" <br># Copy Renv files and Shiny app WORKDIR /home/shiny-app/ <br>RUN mkdir -p renv COPY app.R app.R COPY renv.lock renv.lock COPY .Rprofile  .Rprofile COPY renv/activate.R renv/activate.R COPY renv/settings.json renv/settings.json <br># Restore the R environment RUN R -e "renv::restore()" <br># Expose the application port EXPOSE 8180 <br># Run the R Shiny app CMD Rscript /home/shiny-app/app.R</code></pre> What we're doing here is actually simple. We're defining the base image, creating a folder to hold our Shiny app, installing <code>renv</code>, copying a bunch of files that contain our R Shiny application and environment info, running the <code>renv::restore()</code> function to restore an environment from the lock file, exposing a port on which the app will run, and finally, running the Shiny app. Sounds like a lot, but go over it section by section - you'll immediately understand what each part is responsible for. <h3>Building a Custom Docker Image</h3> We now have a <code>Dockerfile</code> saved, which means the only step left to do is to build the image and run the container. Unfortunately, the <code>rocker/shiny</code> image isn't yet supported on ARM platforms (such as M1/M2 Macs), so we'll have to be a bit creative on this step. But first, open up a new Terminal window and navigate to where your R project is saved. Run this shell command <b>if you're using an x86_64 architecture</b>: <pre><code class="language-bash">docker build -t renv-docker-demo .</code></pre> Similarly, run the following line <b>if you're using an ARM architecture</b>: <pre><code class="language-bash">docker build --platform linux/x86_64 -t renv-docker-demo .</code></pre> We're running this code on an M2 MacBook Air, so the second shell command does the trick for us: <img class="size-full wp-image-22510" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65aff9c1b9de568365561810_Image-12-Building-the-Docker-image.webp" alt="Image 12 - Building the Docker image" width="2490" height="1458" /> Image 12 - Building the Docker image The process of building the image will take some time, a couple of minutes at least. If you see that the R environment is restoring, it means you're on the right track: <img class="size-full wp-image-22512" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65aff9c29b485fa99397c478_Image-13-Building-the-Docker-image-2.webp" alt="Image 13 - Building the Docker image (2)" width="2490" height="1458" /> Image 13 - Building the Docker image (2) Once done, you'll see the following output: <img class="size-full wp-image-22514" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65aff9c34cb3437082f12735_Image-14-Image-build-success-message.webp" alt="Image 14 - Image build success message" width="2490" height="1458" /> Image 14 - Image build success message The only thing left to do is to create a container from this image. <h3>Running the Docker Container</h3> The following shell command will create a container based on the previously created image (<code>renv-docker-demo</code>) and will bind the local port 8180 to the one exposed by the Docker container: <pre><code class="language-bash">docker run -p 8180:8180 renv-docker-demo</code></pre> You'll see the message that the app is running: <img class="size-full wp-image-22516" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65aff9c59e721643db86a210_Image-15-Running-the-container.webp" alt="Image 15 - Running the container" width="2490" height="1458" /> Image 15 - Running the container You can now open up your browser and open localhost on port 8180 - here's what you'll see: <img class="size-full wp-image-22518" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65aff9c550ab0e1953e77d81_Image-16-Shiny-app-running-in-a-Docker-container.webp" alt="Image 16 - Shiny app running in a Docker container" width="2216" height="1996" /> Image 16 - Shiny app running in a Docker container This is the exact same app created in the previous section, but is now running in a Docker container and has all of the R dependencies locked because of renv. As before, you can change the dropdown menu value, and the contents will re-render automatically: <img class="size-full wp-image-22520" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65aff9c78fdd0d8561c07c82_Image-17-Shiny-app-running-in-a-Docker-container-2.webp" alt="Image 17 - Shiny app running in a Docker container (2)" width="2216" height="1996" /> Image 17 - Shiny app running in a Docker container (2) And that's how you can use renv with Docker! Let's make a brief recap next. <hr /> <h2 id="summary">Summing up renv with Docker</h2> To summarize, you can use both renv and Docker separately, but combining them is what you really should strive for. Not only does it allow you to have full control of the underlying OS and system dependencies, but it also enables you to set R package versions in stone, so they don't ever change. It is the ultimate approach to making your projects reproducible, sharable, and running in an isolated environment - just what you want! <i>Are you combining renv with Docker in your R/R Shiny projects?</i> Make sure to let us know in the comment section below. <blockquote>No data to display in your Shiny app, or is data still loading? <a href="https://appsilon.com/shiny-emptystate-for-shiny-apps/" target="_blank" rel="noopener">Make sure to deliver the best user experience with shiny.emptystate</a>.</blockquote>

Contact us!
Damian's Avatar
Damian Rodziewicz
Head of Sales
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
r
shiny
docker
tutorials