Hosting a Shiny App using Docker
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
I used shinyapps.io for my own shiny app. It’s a great service. You can deploy your app for free, test it and show it to other people. But there’s also a downside: The memory an app can use is limited.
So I was looking for another way to deploy my app. So I took a look at Docker.
What is Docker?
A Docker container contains all programs and libraries which are required for running a special application. So it’s easy to transfer or distribute it to another “Docker host” and run the application on it.
Two docker container are separated so they can’t interfere with another. They can only talk with each other using defined ports or directories.
So that’s a way to solve the problem with different package versions or versions of R.
How to put a shiny app into a Docker container?
You have to write a Dockerfile to describe how Docker builds an image which can be started as a Docker container. The Dockerfile is a recipe with several steps. If you change one step all steps before the changed step can be reused. The changed step and all steps after that one must be run again.
I found a nice description of a Dockerfile for a shiny app at this page of statworx.
Building a container with the right R Environment using Renv
The main problem is to install all the R packages needed by your shiny app into your Docker container. Statworx uses the new renv R package management tool from RStudio.
Renv can write all needed packages into a file called renv.lock. If you run renv::restore()
these packages are installed again.
In practice some packages aren’t installed because some requirements on the OS-level aren’t met. You’ll get error messages which guide you to the Linux packages you should install into your container before installing the R packages.
Dockerfile
But how does my Dockerfile look like? Here it is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# Base image https://hub.docker.com/u/rocker/ FROM rocker/shiny-verse:latest # system libraries of general use ## install debian packages RUN apt-get update -qq && apt-get -y --no-install-recommends install \ libxml2-dev \ libcairo2-dev \ libsqlite3-dev \ libpq-dev \ libssh2-1-dev \ unixodbc-dev \ r-cran-v8 \ libv8-dev \ net-tools \ libprotobuf-dev \ protobuf-compiler \ libjq-dev \ libudunits2-0 \ libudunits2-dev \ libgdal-dev \ libssl-dev ## update system libraries RUN apt-get update && \ apt-get upgrade -y && \ apt-get clean # copy necessary files ## renv.lock file COPY /app/renv.lock ./renv.lock # install renv & restore packages RUN Rscript -e 'install.packages("renv")' RUN Rscript -e 'renv::restore()' ## app folder COPY /app ./app # expose port EXPOSE 3838 # run app on container start # CMD ["R", "-e", "shiny::runApp('/app', host = '0.0.0.0', port = 3838)"] CMD ["Rscript", "-e", "rmarkdown::run('/app/app.Rmd', shiny_args=list(host = '0.0.0.0', port=3838))"] |
But let’s break it down into simple pieces:
The base image
1 2 |
# Base image https://hub.docker.com/u/rocker/ FROM rocker/shiny-verse:latest |
That’s an image which contains a base R installation, tidyverse and the shiny server. This image uses Ubuntu as operating system.
Adding Ubuntu packages
So now we install all required (Ubuntu-) packages required for our R-packages:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# system libraries of general use ## install debian packages RUN apt-get update -qq && apt-get -y --no-install-recommends install \ libxml2-dev \ libcairo2-dev \ libsqlite3-dev \ libpq-dev \ libssh2-1-dev \ unixodbc-dev \ r-cran-v8 \ libv8-dev \ net-tools \ libprotobuf-dev \ protobuf-compiler \ libjq-dev \ libudunits2-0 \ libudunits2-dev \ libgdal-dev \ libssl-dev ## update system libraries RUN apt-get update && \ apt-get upgrade -y && \ apt-get clean |
Installing R packages
Now we install all R packages required by our shiny app. That’s an optimization in respect to the statworx example I did here.
Statworx copies the whole app including the renv.lock
file into the image in this next steps and
installs the R packages.
I copy only the renv.lock
file and install the packages. You’ll see the advantage soon.
1 2 3 4 5 6 7 |
# copy necessary files ## renv.lock file COPY /app/renv.lock ./renv.lock # install renv & restore packages RUN Rscript -e 'install.packages("renv")' RUN Rscript -e 'renv::restore()' |
That’s the step you’ll get the most errors because of missing dependancies. It’s also the most time consuming step. So you want to run it only if it is really required.
Installing and running the shiny app
1 2 3 4 5 6 7 8 9 |
## app folder COPY /app ./app # expose port EXPOSE 3838 # run app on container start # CMD ["R", "-e", "shiny::runApp('/app', host = '0.0.0.0', port = 3838)"] CMD ["Rscript", "-e", "rmarkdown::run('/app/app.Rmd', shiny_args=list(host = '0.0.0.0', port=3838))"] |
Now we copy the shiny app into the image and run it. We also say that the app runs on port 3838 and declare this port to be visible from outside the container.
Interations
So now you can see the advantage of splitting the renv.lock file and the whole app: Once you have set up your R environment in your container and you do only changes to your shiny app you can reuse the first and time consuming steps of your container creating process. Docker only has to rerun the last steps: Copying your app and running it.
Reverse Proxy
When running your Docker container on a server you may want to use a reverse proxy such as nginx to map the local port to the standard port 80 resp. 443 and use tls-encryption to protect the traffic.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.