Running Shiny Server in Docker
Tags: shiny, docker
Published: 2021-06-11 15:28:36

*By: [Peter Solymos](https://hosting.analythium.io/author/peter/)*

We looked at [best practices for R with Docker](https://hosting.analythium.io/best-practices-for-r-with-docker/) where we compared four commonly used parent images. It was common in these images that these all contained a ["local" Shiny app](https://hosting.analythium.io/run-shiny-apps-locally/) using `shiny::runApp()`. A special breed of dockerized Shiny apps is when the Shiny Server is running inside the Docker container. In this post, we'll explore this "dockerized Shiny Server" setup and discuss the pros and cons.

The files from this post can be found in the [analythium/covidapp-shiny](https://github.com/analythium/covidapp-shiny) GitHub repository, inside the `99-images` folder.

<figure> <img src="https://github.githubassets.com/favicons/favicon.svg" class="kg-bookmark-icon" alt="" /> </figure>

The Dockerfile
--------------

The parent image of the `Dockerfile` is [`rocker/shiny`](https://hub.docker.com/r/rocker/shiny). This image is part of the Rocker project and is based on the [versioned](https://github.com/rocker-org/rocker-versioned2) Ubuntu line of R images. It contains a versioned R installation, Shiny Server Open Source. It already has the `shiny` user defined and port 3838 exposed.

<figure> <img src="https://hosting.analythium.io/content/images/2021/06/Docker-Shiny-Server.jpg" class="kg-image" sizes="(min-width: 720px) 720px" srcset="https://hosting.analythium.io/content/images/size/w600/2021/06/Docker-Shiny-Server.jpg 600w, https://hosting.analythium.io/content/images/size/w1000/2021/06/Docker-Shiny-Server.jpg 1000w, https://hosting.analythium.io/content/images/2021/06/Docker-Shiny-Server.jpg 1366w" width="1366" height="768" alt="" /><figcaption>Shiny Server running inside a Docker container</figcaption> </figure>

On top of the parent image, you can follow the same pattern that we used for other base images: install system dependencies, install R packages, copy files for the Shiny app. The Shiny app goes inside the `/srv/shiny-server/` folder where you can have more than one shiny app in different folders. You may also copy HTML files to provide navigation for your users.

We'll copy the files for the COVID-19 app, change the user to `shiny` (the `EXPOSE` directive is not needed but I left it there as a reminder). Finally, we define the command as `/usr/bin/shiny-server` to start the Shiny Server:

    FROM rocker/shiny:4.0.5

    # Install system requirements for index.R as needed
    RUN apt-get update && apt-get install -y \
        --no-install-recommends \
        git-core \
        libssl-dev \
        libcurl4-gnutls-dev \
        curl \
        libsodium-dev \
        libxml2-dev \
        libicu-dev \
        && apt-get clean \
        && rm -rf /var/lib/apt/lists/*

    ENV _R_SHLIB_STRIP_=true
    COPY Rprofile.site /etc/R
    RUN install2.r --error --skipinstalled \
        shiny \
        forecast \
        jsonlite \
        ggplot2 \
        htmltools \
        plotly

    COPY ./app/* /srv/shiny-server/

    USER shiny

    EXPOSE 3838

    CMD ["/usr/bin/shiny-server"]

The Docker image
----------------

I used the Docker BuildKit as explained before, then tested the image. Here is the script for your to replicate:

    export IMAGE="analythium/covidapp-shiny:shiny"
    export FILE="Dockerfile.shiny"
    DOCKER_BUILDKIT=1 docker build --no-cache -f $FILE -t $IMAGE .
    docker run -p 8080:3838 $IMAGE

Here is how build times and image sizes compare across the five images:

<table> <thead> <tr class="header"> <th>Parent image</th> <th>Parent size (GB)</th> <th>Final size (GB)</th> <th>Bild time (min)</th> </tr> </thead> <tbody> <tr class="odd"> <td><code>rhub/r-minimal</code></td> <td>0.035</td> <td>0.222</td> <td>27.0</td> </tr> <tr class="even"> <td><code>rocker/r-base</code></td> <td>0.761</td> <td>1.050</td> <td>2.9</td> </tr> <tr class="odd"> <td><code>rocker/r-ubuntu</code></td> <td>0.673</td> <td>1.220</td> <td>3.1</td> </tr> <tr class="even"> <td><code>rstudio/r-base</code></td> <td>0.894</td> <td>1.380</td> <td>3.1</td> </tr> <tr class="odd"> <td><code>rocker/shiny</code></td> <td>1.380</td> <td>1.610</td> <td>2.3</td> </tr> </tbody> </table>

Build time was comparably small for all the other non-Alpine images, however, the size of the parent and the final images were the largest among the 5 different options.

Here are some thoughts to consider when using the `rocker/shiny` parent image to host a containerized Shiny applications.

You can run multiple Shiny apps over the same port exposed by Docker. The Shiny Server configuration allows you to [disable the websocket](https://code.markedmondson.me/shiny-cloudrun/) protocol, this way you can host your apps in Container-as-a-Service environments, like [Google Cloud Run](https://cloud.google.com/run), etc.

However, the image size is the largest among the comparable options. If the image includes multiple apps, those apps are not isolated, and managing their dependencies might become problematic over time.

An alternative use case for the `rocker/shiny` image is to host Shiny apps that are not copied into the image. This is done by mounting the app folder as a volume to the container (that is a live instance of the image) as explained on the [Docker Hub page](https://hub.docker.com/r/rocker/shiny).

The next command mounts two volumes, one with the apps and one for the Shiny Server logs in a `-v host:container`  fashion. The directories on the host will be created if those don't already exist.

    docker run -d -p 8080:3838 \
        -v /srv/shinyapps/:/srv/shiny-server/ \
        -v /srv/shinylog/:/var/log/shiny-server/ \
        rocker/shiny

This setup can be especially useful for hosting [Shiny Apps in a Windows environment](https://community.rstudio.com/t/shiny-webserver-deployment-in-windows-operating-system/9349/4). You will need to add dependencies using the `RUN` directive.

Summary
-------

The dockerized Shiny Server is a heavyweight option for hosting a single app, but it plays an important role in the Shiny hosting landscape. It allows you to run Shiny apps in unusual situations, like on Windows, or hosted container platforms.

Further reading
---------------

-   A [detailed blog
    post](https://blog.sellorm.com/2021/04/25/shiny-app-in-docker/) by
    Mark Sellors
-   [Scaling Shiny in Google
    Cloud](https://code.markedmondson.me/shiny-cloudrun/) by Mark
    Edmondson
-   [Mounting directories on
    Windows](https://rominirani.com/docker-on-windows-mounting-host-directories-d96f3f056a2c)