Skip to main content
AI in Production 2026 is now open for talk proposals.
Share insights that help teams build, scale, and maintain stronger AI systems.
items
Menu
  • About
    • Overview 
    • Join Us  
    • Community 
    • Contact 
  • Training
    • Overview 
    • Course Catalogue 
    • Public Courses 
  • Posit
    • Overview 
    • License Resale 
    • Managed Services 
    • Health Check 
  • Data Science
    • Overview 
    • Visualisation & Dashboards 
    • Open-source Data Science 
    • Data Science as a Service 
    • Gallery 
  • Engineering
    • Overview 
    • Cloud Solutions 
    • Enterprise Applications 
  • Our Work
    • Blog 
    • Case Studies 
    • R Package Validation 
    • diffify  

Automating Dockerfile creation for Shiny apps

Authors: Jamie Owen & Colin Gillespie

Published: October 20, 2022

tags: r, shiny, docker

Introduction

For creating a production deployment of a {shiny} application it is often useful to be able to provide a Docker image that contains all the dependencies for that application. Here we explore how one might go about automating the creation of a Dockerfile that will allow us to build such an image for a {shiny} application.

What is docker?

Docker is an open source platform that enables developers to build, deploy and run containers, standardised executable components that combine application source code with the operating system libraries and dependencies required to run that code.

A general introduction to Docker for R users can be found in this blog post by Colin Fay, and the docker website also has some excellent documentation.

Data comes in all shapes and sizes. It can often be difficult to know where to start. Whatever your problem, Jumping Rivers can help.

Obtaining system dependencies

When installing R packages, occasionally you will need additional system dependencies. When building a Docker image we will want to include the installation of those system dependencies into the Dockerfile. If we are to automate the process of writing a Dockerfile for building an image to run a {shiny} application then we need to find some programmatic solution to determining the required system dependencies.

It turns out that the RStudio Package Manager (RSPM) product has an API that can be queried to obtain the system requirements of a collection of R packages. The lovely folk at Posit also provide an instance of RSPM that anyone can make use of so it is trivial to obtain this information even if you do not have RSPM yourself. For example if we wanted to inspect the system dependencies of a package like {shiny} for Ubuntu 22.04 then a request to

https://packagemanager.rstudio.com/__api__/repos/1/sysreqs?all=false&pkgname=shiny&distribution=ubuntu&release=22.04

would do the trick.

In fact the {vetiver} package has a non-exported function, glue_sys_reqs() that will build a string for the command to install these system requirements.

glue_sys_reqs = function(pkgs) {
  rlang::check_installed("curl")
  rspm = Sys.getenv("RSPM_ROOT", "https://packagemanager.rstudio.com")
  rspm_repo_id = Sys.getenv("RSPM_REPO_ID", 1)
  rspm_repo_url = glue::glue("{rspm}/__api__/repos/{rspm_repo_id}")
  
  pkgnames = glue::glue_collapse(unique(pkgs), sep = "&pkgname=")
  
  req_url = glue::glue(
    "{rspm_repo_url}/sysreqs?all=false",
    "&pkgname={pkgnames}&distribution=ubuntu&release=22.04"
  )
  res = curl::curl_fetch_memory(req_url)
  sys_reqs = jsonlite::fromJSON(rawToChar(res$content), simplifyVector = FALSE)
  if (!is.null(sys_reqs$error)) rlang::abort(sys_reqs$error)

  sys_reqs = purrr::map(sys_reqs$requirements, purrr::pluck, "requirements", "packages")
  sys_reqs = sort(unique(unlist(sys_reqs)))
  sys_reqs = glue::glue_collapse(sys_reqs, sep = " \\\n    ")
  glue::glue(
    "RUN apt-get update -qq && \\ \n",
    "  apt-get install -y --no-install-recommends \\\n    ",
    sys_reqs,
    "\ && \\\n",
    "  apt-get clean && \\ \n",
    "  rm -rf /var/lib/apt/lists/*",
    .trim = FALSE
  )
}

Trying that out on a vector of packages we get something like

glue_sys_reqs(c("shiny", "dplyr"))
#> RUN apt-get update -qq && \ 
#>   apt-get install -y --no-install-recommends \
#>     make \
#>     zlib1g-dev && \
#>   apt-get clean && \ 
#>   rm -rf /var/lib/apt/lists/*

To grab the system requirements for all packages that are used by a {shiny} app then we could use renv::dependencies() to scan our code and list the used packages, then feed then to this function.

appdir = "app/"
pkgs = renv::dependencies(appdir)$Package
sys_reqs = glue_sys_reqs(pkgs)

Building out the rest of the Dockerfile

In order to reproduce the application that works on our system with a particular R version and the versions of packages that we have we want to build a Docker image that has that same version of R and packages. The rocker project provides a collection of Docker images for different purposes tagged for different R versions which makes this substantially easier so it’s really a case of ensuring that we match everything up.

We can write the line that will give me the rocker/shiny image for my R version fairly easily

(from_shiny_version = glue::glue("FROM rocker/shiny:{getRversion()}"))
#> FROM rocker/shiny:4.2.1

and {renv} makes it trivial to snapshot the versions of packages that we have installed and required for my project.

appdir = "app"
lockfile = "shiny_renv.lock"
renv::snapshot(
  project = appdir,
  lockfile = lockfile,
  prompt = FALSE,
  force = TRUE
)

We then want to get this lock file into the Docker image and renv::restore() the state of the library.

copy_renv = glue::glue("COPY {lockfile} renv.lock")
renv_install = 'RUN Rscript -e "install.packages(\'renv\')"'
renv_restore  = 'RUN Rscript -e "renv::restore()"'

Finally we want to include the app in the image, let others know on which port the application is going to communicate (shiny-server defaults to 3838) and launch the {shiny} server on running the image.

copy_app = glue::glue("COPY {appdir} /srv/shiny-server/")
expose = ifelse(expose, glue::glue("EXPOSE {port}"), "")
cmd = 'CMD ["/usr/bin/shiny-server"]'

Combining all those steps into a single list and writing to file gives us a final Dockerfile. We can wrap this in a function to make it nicer to use:

shiny_write_docker = function(
  path = ".", appdir = "app", lockfile = "shiny_renv.lock",
  port = 3838, expose = TRUE, rspm = TRUE
) {
  rspm_env = ifelse(
    rspm,
    "ENV RENV_CONFIG_REPOS_OVERRIDE https://packagemanager.rstudio.com/cran/latest\n",
    ""
  )
  from_shiny_version = glue::glue("FROM rocker/shiny:{getRversion()}")
  renv::snapshot(
    project = path,
    lockfile = lockfile,
    prompt = FALSE,
    force = TRUE
  )
  pkgs = renv::dependencies(appdir)$Package
  sys_reqs = glue_sys_reqs(pkgs)
  copy_renv = glue::glue("COPY {lockfile} renv.lock")
  renv_install = 'RUN Rscript -e "install.packages(\'renv\')"'
  renv_restore  = 'RUN Rscript -e "renv::restore()"'
  
  copy_app = glue::glue("COPY {appdir} /srv/shiny-server/")
  expose = ifelse(expose, glue::glue("EXPOSE {port}"), "")
  cmd = 'CMD ["/usr/bin/shiny-server"]'
  
  ret = purrr::compact(list(
    from_shiny_version,
    rspm_env,
    sys_reqs,
    copy_renv,
    renv_install,
    renv_restore,
    copy_app,
    expose,
    cmd
  ))
  readr::write_lines(ret, file = file.path(path, "Dockerfile"))
}

Taking the old faithful example shiny app template as my app in a directory called app\

shiny_write_docker(path = ".", appdir = "app")
#> * Lockfile written to 'shiny_renv.lock'.
#> Finding R package dependencies ... Done!

produces the following Dockerfile

FROM rocker/shiny:4.2.0
ENV RENV_CONFIG_REPOS_OVERRIDE https://packagemanager.rstudio.com/cran/latest

RUN apt-get update -qq && apt-get install -y --no-install-recommends \
  make \
  zlib1g-dev\
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY shiny_renv.lock renv.lock
RUN Rscript -e "install.packages('renv')"
RUN Rscript -e "renv::restore()"
COPY app /srv/shiny-server/
EXPOSE 3838
CMD ["/usr/bin/shiny-server"]

Running the app

From our Dockerfile we can build the image

docker build --tag auto_shiny_docker .

and run a container using that image mapping the shiny server port to the same port on localhost.

docker run --rm --publish 3838:3838 auto_shiny_docker

If we navigate in our browser to

http://localhost:3838

we should see the running application.

See also

  • There’s a nice blog post by Mark Sellors, which focuses on a particular app, whereas the above is the general case. Definitely worth a look.
  • Our amazing Docker courses

Jumping Rivers Logo

Recent Posts

  • Start 2026 Ahead of the Curve: Boost Your Career with Jumping Rivers Training 
  • Should I Use Figma Design for Dashboard Prototyping? 
  • Announcing AI in Production 2026: A New Conference for AI and ML Practitioners 
  • Elevate Your Skills and Boost Your Career – Free Jumping Rivers Webinar on 20th November! 
  • Get Involved in the Data Science Community at our Free Meetups 
  • Polars and Pandas - Working with the Data-Frame 
  • Highlights from Shiny in Production (2025) 
  • Elevate Your Data Skills with Jumping Rivers Training 
  • Creating a Python Package with Poetry for Beginners Part2 
  • What's new for Python in 2025? 

Top Tags

  • R (236) 
  • Rbloggers (182) 
  • Pybloggers (89) 
  • Python (89) 
  • Shiny (63) 
  • Events (26) 
  • Training (23) 
  • Machine Learning (22) 
  • Conferences (20) 
  • Tidyverse (17) 
  • Statistics (14) 
  • Packages (13) 

Authors

  • Amieroh Abrahams 
  • Colin Gillespie 
  • Aida Gjoka 
  • Gigi Kenneth 
  • Osheen MacOscar 
  • Sebastian Mellor 
  • Keith Newman 
  • Pedro Silva 
  • Shane Halloran 
  • Russ Hyde 
  • Myles Mitchell 
  • Tim Brock 
  • Theo Roe 

Keep Updated

Like data science? R? Python? Stan? Then you’ll love the Jumping Rivers newsletter. The perks of being part of the Jumping Rivers family are:

  • Be the first to know about our latest courses and conferences.
  • Get discounts on the latest courses.
  • Read news on the latest techniques with the Jumping Rivers blog.

We keep your data secure and will never share your details. By subscribing, you agree to our privacy policy.

Follow Us

  • GitHub
  • Bluesky
  • LinkedIn
  • YouTube
  • Eventbrite

Find Us

The Catalyst Newcastle Helix Newcastle, NE4 5TG
Get directions

Contact Us

  • hello@jumpingrivers.com
  • + 44(0) 191 432 4340

Newsletter

Sign up

Events

  • North East Data Scientists Meetup
  • Leeds Data Science Meetup
  • Shiny in Production
British Assessment Bureau, UKAS Certified logo for ISO 9001 - Quality management British Assessment Bureau, UKAS Certified logo for ISO 27001 - Information security management Cyber Essentials Certified Plus badge
  • Privacy Notice
  • |
  • Booking Terms

©2016 - present. Jumping Rivers Ltd