Join the Shiny Community every month at Shiny Gatherings

R Shiny Docker: How To Run Shiny Apps in a Docker Container


It doesn’t matter if you’ve created the world’s best R Shiny application if you can’t share it with others. Reproducibility and portability are two major key points in software development, and they basically stand for the idea that the code running on your machine should be easily reproduced on another machine.

That’s where Docker comes in. In the context of R Shiny, Docker allows you to create and package an application that’s ready to be shipped and recreated on another operating system. Think of it as an entire virtual machine running Shiny but without the unnecessary stuff.

Today you’ll learn why Docker is important for R Shiny applications, and how to get started using R Shiny Docker by coding an app from scratch and Dockerizing it. Let’s dig in!

Are your data science pipelines reproducible? Read our guide to clean and function-oriented pipelines with R targets.

Table of contents:


Why R Shiny Docker – The Benefits

Docker is a platform for developing, shipping, and running applications in isolated environments, or containers, which is something you know if you’ve read our previous blog post on getting started with R and Docker.

It can provide a lot of benefits for you in the context of R and R Shiny, such as:

  • Consistency: You want your code running consistently across different environments, such as development, testing, and production. Docker containers essentially encapsulate programming environments, so you can rest assured the code and dependencies will remain the same across all of them.
  • Reproducibility: Docker images are highly flexible, meaning they allow you to specify the R version needed, R dependencies, and other OS-related dependencies. This ensures you (or anyone else) won’t run into any issues when sharing your code. It’s a write-once-run-anywhere system.
  • Portability: The containers you create can be run on any system that supports Docker, such as servers, other laptops, home NAS solutions, and even Raspberry PI.
  • Scalability: Docker makes it easy to scale applications, R Shiny included. You can create multiple containers with identical configurations and scale your app horizontally. This is a good way of future-proofing your infrastructure as your app’s workload increases.

There are many other, excellent reasons to use Docker, but we find these ones to be the most relevant in the context of R and R Shiny.

As for the Docker installation, we’ve covered it in a previous article, and it boils down to a couple of mouse clicks once you download the installation file from the web.

Linux users might have to do a couple of extra steps, but it’s all well-documented online.

Writing the R Shiny Application from Scratch

The focus of today’s article isn’t R Shiny, so the app we’ll write will be relatively simple.

We’ll leverage the well-known Gapminder dataset to visualize the average life expectancy over time, and also the average GDP per capita. The user will be able to select the continent, and the app will automatically update when a change is detected.

In case you need a refresher on data visualization in R:

If you have some experience with R Shiny, the dashboard code should read like plain English. If not, try tweaking a couple of things to see what happens.

The only thing that should look new is a call to options() function at the top of app.R. We’re using this function to explicitly define the host and port on which the app will run.

You generally want to take maximum control over the deployment process, so that’s why we highly recommend you do the same as we did

Anyhow, here’s the full source code for app.R:

library(shiny)
library(dplyr)
library(ggplot2)
library(gapminder)

# Specify the application port
options(shiny.host = "0.0.0.0")
options(shiny.port = 8180)


ui <- 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")
    )
  )
)

server <- function(input, output, session) {
  # Filter data and store as reactive value
  data <- reactive({
    gapminder %>%
      filter(continent == input$inContinent) %>%
      group_by(year) %>%
      summarise(
        AvgLifeExp = round(mean(lifeExp)),
        AvgGdpPercap = round(mean(gdpPercap), digits = 2)
      )
  })

  # Common properties for charts
  chart_theme <- 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)
  )

  # Render Life Exp chart
  output$outChartLifeExp <- 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
  })

  # Render GDP chart
  output$outChartGDP <- 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
  })
}

shinyApp(ui = ui, server = server)

This is what the app looks like when you run it:

Image 1 – Gapminder R Shiny application

As you would expect, the chart contents update when you change the dropdown menu value:

Image 2 – Gapminder R Shiny application (2)

To conclude, this R Shiny app is nothing fancy, but will serve us just fine for simple deployment purposes. Let’s go over that next.

R Shiny Docker – How to Dockerize your Shiny App

In this section, you’ll see how to write a simple Dockerfile for an R Shiny application, how to create a Docker image from Dockerfile, and also how to create a container from the previously created image.

Writing the Dockerfile

Think of Dockerfile as an instruction file responsible for telling Docker how to build an image. In a nutshell, you pick a base image (typically Linux with R language installed), and then specify instructions for installing additional dependencies, copying project files, and running R scripts.

It’s a simple file when you get used to the syntax, but uses a couple of distinct keywords you have to learn:

  • FROM: A starting point for every Dockerfile. Used to tell Docker from which base image you’ll build your own. A rocker/shiny image already has R and Shiny installed, so that’s the one we’ll use.
  • RUN: Runs a shell command. Useful for creating files and folders, installing dependencies, and more.
  • COPY: Copies contents from your local machine to the Docker container. You first specify the path to the local file, followed by the file on the container.
  • EXPOSE: Exposes a port of a Docker container so you can access it from your local machine.
  • CMD: Command that will be used every time you launch the container. We’ll use it to launch the R Shiny app.

You’ll find the contents of our Dockerfile below. Create your file first (no extensions), and copy the following snippet:

# Base R Shiny image
FROM rocker/shiny

# Make a directory in the container
RUN mkdir /home/shiny-app

# Install R dependencies
RUN R -e "install.packages(c('dplyr', 'ggplot2', 'gapminder'))"

# Copy the Shiny app code
COPY app.R /home/shiny-app/app.R

# Expose the application port
EXPOSE 8180

# Run the R Shiny app
CMD Rscript /home/shiny-app/app.R

To summarize, we’re using the latest rocker/shiny image since it already has R programming language and Shiny frameworks installed – less work for us to do. Then, we’re creating a directory to store our app, install R package dependencies, copy the app.R file, exposing the port on which the Shiny app will run (the one hardcoded in app.R), and then finally using Rscrit to launch the app.

The next step is to create our custom image from this Dockerfile.

Creating the Image and Running the Container

Note: At the time of writing this article (September 2023), the rocker/shiny base image isn’t supported on Apple Silicon. If you’re using an M-series Mac, you’ll have to append the --platform linux/x86_64 tag when building the image.

To build an image from a Dockerfile, run the following command (make sure you navigate to the source code folder first):

docker build -t shiny-docker-demo .

This will create a new image called shiny-docker-demo.

If you’re using an Apple Silicon Mac like us, run this command instead:

docker build --platform linux/x86_64 -t shiny-docker-demo .

This is the Terminal output you will see:

Image 3 – Building the Docker image

Downloading the base image and R dependencies will take some time, and you’ll see something similar once finished:

Image 4 – Building the Docker image (2)

Your Docker image is now created, which means the only thing left to do is to run it inside a container.

To do so, run the following command (works on M1/M2 Macs):

docker run -p 8180:8180 shiny-docker-demo

Here’s what you’ll see in the Terminal:

Image 5 – Running the Docker container

The R Shiny application is now running inside the Docker container on port 8180. You can access it from your local machine since we’ve exposed that port in the Dockerfile.

Here’s what the app looks like:

Image 6 – Dockerized R Shiny application

As before, you can change the value in the dropdown menu and the charts will update automatically:

Image 7 – Dockerized R Shiny application (2)

And that’s it – you’ve successfully Dockerized an R Shiny application!

Monitoring Dockerized R Shiny Apps

The Docker Desktop application can do all sorts of powerful things. For example, you can click on the container that’s running your R Shiny application to inspect the runtime logs and even the file system.

You can see that the directory structure was created as per the Dockerfile instructions:

Image 8 – Docker container files and folders

Further, you can monitor the resource usage under the Stats tab:

Image 9 – Docker container resource usage

This tab might need some time to populate the chart values, but you get the point – there’s a lot of tooling and monitoring built into the Docker Desktop application, so make sure to spend some time investigating.


Summing up R Shiny Docker

Congratulations – you now have a fully working R Shiny application running in a Docker container! Writing Dockerfiles is definitely something to get used to, but it will feel like a walk in the park after a couple of weeks.

Today you’ve successfully Dockerized one Shiny application, but what if you want to run two or more Shiny apps at once in separate containers? Make sure to stay tuned to Appsilon Blog so you don’t miss out on advanced deployment techniques.

What’s your preferred way of sharing R Shiny applications? Do you use Docker or something else? Let us know in the comment section below.

Yes, R programming can significantly improve your business workflows – Here are 5 ways how.