ixblog | ixpantia

How to Run R Shiny in Docker: A Step-by-Step Guide

Written by Andrés Quintero Moreano | Oct 8, 2024 1:05:55 PM

R Shiny is one of the most efficient ways of writing interactive data applications. And when it’s time to ship your advanced analytical dashboards to your users, it is important to guarantee reproducible environments and stability. This will avoid unpleasant surprises and delays at the end of the line , when both you and your users thought you were ready.

The need for a smooth path from development to production deployments is not unique to Shiny, but applies many technologies in different industries. Docker is a great way to achieve this, so much so that Docker (and containers in general) became an industry standard for shipping applications.

In this blog post you will learn how to get started in running a Shiny application from within a container, and then use faucet to deploy your shiny app to production.

Why use Docker for R Shiny?

Docker is the most popular tool for building and running containers. It hosts the most popular registry of container images DockerHub and has been ported to all popular platforms.

Using containers can bring a lot of benefits including but not limited to:

  • Stability: Running applications that last for years will always be a challenge. However, doing it without a stable environment can make this almost impossible. Containers provide a stable environment that your application can mostly rely on. If you run a container today, it is conceivable that it will keep on running for a couple of years.
  • Portability: Containers allow you to deploy your application in many environments. You can deploy on managed services like Google Cloud Run, AWS ECS or Fargate and Azure Container Apps. You can run your container directly on a virtual machine which will grant you greater control over resources. All of these work with containers quite well.
  • Reproducibility: Targeting containers allows for your team to streamline the development process. If the container works on one computer you can expect it to work on another.

You can get started with docker by visiting docker.com for platform specific instructions for installation.

Setting up a Shiny App

For this example we will create a very minimal Shiny application. We will use a single app.R instead of separating it into multiple files for simplicity, however the example should hold in both cases.

This will be the code we will include in the app.R file:

library(shiny)
library(bslib)

ui <- page_sidebar(
  title = "Hello Shiny!",
  sidebar = sidebar(
    sliderInput(
      inputId = "bins",
      label = "Number of bins:",
      min = 1,
      max = 50,
      value = 30
    )
  ),
  plotOutput(outputId = "distPlot")
)

server <- function(input, output) {
  output$distPlot <- renderPlot({
    x    <- faithful$waiting
    bins <- seq(min(x), max(x), length.out = input$bins + 1)
    hist(x, breaks = bins, col = "#007bc2", border = "white",
         xlab = "Waiting time to next eruption (in mins)",
         main = "Histogram of waiting times")
  })
}

shinyApp(ui = ui, server = server)

We can verify that the app works by running the app in our R console with shiny::runApp().

Writing a Dockerfile

In this section you will learn to write a Dockerfile for your Shiny App. The Dockerfile contains the recipe to create your image. A Dockerfile typically contains a base image and a set of instructions.

This is the Dockerfile we will use for our minimal Shiny app:

# Base image for our image
FROM ixpantia/faucet:r4.4

# Install the packages we need
RUN Rscript -e "install.packages(c('shiny', 'bslib'))"

# Copy the file app.R from our computer to the image
COPY app.R app.R

# We are done!

This Dockerfile can be boiled down to:

  • Base image: This Dockerfile uses ixpantia/faucet:r4.4 as a base image. Faucet is a program that takes the responsibility of running the app, logging requests, and even creating replicas.
  • Dependencies: We then install shiny and bslib (our two dependencies), which we need to run our application.
  • Copy our code: We then copy our code into the image, in this case we only copy our app.R.

Before continuing it is recommended to save this file with the name Dockerfile in the save directory as the app.R.

Why use ixpantia/faucet?

The docker image ixpantia/faucet comes with  faucet pre configured to run on the default working directory. This allows you to create minimal docker files without a lot of additional configuration.

Faucet provides several benefits out of the box, like app replicas, logging, a multi-app router and support for Plumber APIs. This means you can use the same workflow for most of the infrastructure you need to deploy with R, giving your team a great boost to productivity.

ixpantia/faucet also supports arm64 and amd64, which means faucet runs natively in ARM servers, PCs and laptops, while other options may only be available for x86 systems or require emulation layers which are a hit to performance.

To learn more about faucet check it out of GitHub or read the official documentation.

Building and Running our Docker Image

Now that we have a Dockerfile we need to build it into an image. Make sure your working directory is the same directory where the Dockerfile and app.R are located. You can then run:

docker build -t shiny-app .

This will start the build process with the tag shiny-app.

Once the image is built we can run the application. One thing to note is that we will need to export a port to the users computer. We can do this with the -p flag.

To run the container we can run:

docker run -p 3838:3838 shiny-app

We can now access localhost:3838 and see our application running!

If you take a look at the output of your terminal you may see detailed logging about what is going on in the App.

Tweaking options for production

If you are looking to take your application to production you may be interested in tweaking some options to make sure your application runs as you intend it to.

Setting the number of replicas

If you can use the container on a multi-core system you likely notice many workers starting. These workers are replicas of our applications. This allows you to scale your app by serving concurrent connections on different cores / processes on your container. You can tweak this by setting the environment variable FAUCET_WORKERS in your Dockerfile. Unless set, the number of workers will equal the number of logical cores on your container.

# Base image for our image
FROM ixpantia/faucet:r4.4

# Install the packages we need
RUN Rscript -e "install.packages(c('shiny', 'bslib'))"

# Copy the file app.R from our computer to the image
COPY app.R app.R

# Set four workers
ENV FAUCET_WORKERS=4

# We are done!

Graceful shutdown

If you are looking to deploy your application on a service like Google Cloud Run or AWS ECS or Fargate, graceful shutdown will help the service make continuous deployment smoother. Graceful shutdown tells the application and its replicas to not stop until all connections or requests are finished. We can add graceful shutdown with the environment variable FAUCET_SHUTDOWN.

# Base image for our image
FROM ixpantia/faucet:r4.4

# Install the packages we need
RUN Rscript -e "install.packages(c('shiny', 'bslib'))"

# Copy the file app.R from our computer to the image
COPY app.R app.R

# Enable graceful shutdown
ENV FAUCET_SHUTDOWN=graceful

# We are done!

Conclusion

In conclusion, using Docker to containerize your R Shiny application offers significant benefits, including stability, portability, and reproducibility. By following the steps outlined in this guide, you can ensure that your Shiny app runs in a consistent environment, whether for development or production.

If you want to take your R infrastructure to the next level, contact us and let's begin your journey with ixpantia!