The pracpac
package enables developers to easily incorporate R packages into Docker images. What follows is a reproducible demonstration of select pracpac
use cases. Note that this vignette is by no means exhaustive, and the pattern of delivering an R package within a Docker image1 may prove useful in other scenarios as well.
The “pipeline” use case describes a scenario where a developer may want to use functions from a custom R package to perform processing on the input and/or output of a domain-specific tool. There are countless software packages that will not be implemented directly in R. Developers who want to leverage these tools in a reproducible context may choose to do so by using Docker. If the tool(s) in the workflow require(s) upstream or downstream processing that is best suited to R code, then the developer could write an R package and distribute everything together in Docker. Besides defining reproducible dependencies, Docker allows the developer to pass “instructions” for how the container should behave. These can include scripts to run when the container is launched.
To demonstrate this use case we use the hellow
R package source code that ships with pracpac
. We write a pipeline to use the isay()
function from hellow
to randomly select a flavor of “Hello”. The output is piped to a command-line tool called translate-shell
2 that translates the text to another language specified by the user (by default French). When the container runs, the pipeline is executed and outputs the translated results.
We will first move the example hellow
package to a temporary location:
library(pracpac)
library(fs)
## specify the temp directory
<- tempdir()
tmp ## copy the example hellow package to the temp directory
dir_copy(path = system.file("hellow", package = "pracpac"), new_path = path(tmp, "example", "hellow"))
The new directory includes the R package source contents of hellow
:
dir_tree(path(tmp, "example", "hellow"), recurse = TRUE)
├── DESCRIPTION
├── NAMESPACE
├── R
│ └── hello.R
└── man
└── isay.Rd
We can use use_docker(..., use_case="pipeline")
to create the template of files for building the Docker image:
use_docker(pkg_path = path(tmp, "example", "hellow"), use_case = "pipeline")
Using renv. Dockerfile will build from renv.lock in /tmp/RtmpsMexB6/example/hellow/docker.
Using template for the specified use case: pipeline
Writing dockerfile: /tmp/RtmpsMexB6/example/hellow/docker/Dockerfile
The directory will be created at /tmp/RtmpsMexB6/example/hellow/docker/assets
Assets for the specified use case (pipeline) will be copied there.
The specified use case (pipeline) includes the following asset: run.sh
The specified use case (pipeline) includes the following asset: pre.R
The specified use case (pipeline) includes the following asset: post.R
Building package hellow version 0.1.0 in /tmp/RtmpsMexB6/example/hellow/hellow_0.1.0.tar.gz
docker build command:
docker build --tag hellow:latest --tag hellow:0.1.0 /tmp/RtmpsMexB6/example/hellow/docker
The directory now contains the docker/
subdirectory, which has another subdirectory called assets/
for the templated pipeline scripts:
dir_tree(path(tmp, "example", "hellow"), recurse = TRUE)
├── DESCRIPTION
├── NAMESPACE
├── R
│ └── hello.R
├── docker
│ ├── Dockerfile
│ ├── assets
│ │ ├── post.R
│ │ ├── pre.R
│ │ └── run.sh
│ ├── hellow_0.1.0.tar.gz
│ └── renv.lock
└── man
└── isay.Rd
The files need to be edited as follows:
Dockerfile
FROM rocker/r-ver:latest
## copy the renv.lock into the image
COPY renv.lock /renv.lock
## install renv
RUN Rscript -e 'install.packages(c("renv"))'
## set the renv path var to the renv lib
ENV RENV_PATHS_LIBRARY renv/library
## restore packages from renv.lock
RUN Rscript -e 'renv::restore(lockfile = "/renv.lock", repos = NULL)'
## copy in built R package
COPY hellow_0.1.0.tar.gz /hellow_0.1.0.tar.gz
## run script to install built R package from source
RUN Rscript -e 'install.packages("/hellow_0.1.0.tar.gz", type="source", repos=NULL)'
## COPY in the pre processing R script and run shell script
COPY assets/pre.R /pre.R
COPY assets/run.sh /run.sh
## add the translate-shell tool and dependencies
RUN apt-get update && apt-get install -y bsdmainutils translate-shell
## enter at run shell script
## allows for parameters passed to container at runtime
ENTRYPOINT ["bash", "/run.sh"]
run.sh
#!/bin/bash
## get tranlsation language from first argument
## default to :fr
TOLANG="${1:-:fr}"
HELLO=$(Rscript /pre.R)
trans "$HELLO" "$TOLANG"
pre.R
library(hellow)
isay()
Note that in this case the docker/assets/post.R
template is not necessary, so we can delete it:
file_delete(path(tmp, "example", "hellow", "docker", "assets", "post.R"))
With the template files created and edited as described above, we can build the image:
build_image(pkg_path = path(tmp, "example", "hellow"))
In this case, the image will be built with build_image()
default behavior of tagging with the package name and version:
system("docker images")
hellow 0.1.0 e1a9bc2ebbb5 15 seconds ago 959MB
hellow latest e1a9bc2ebbb5 15 seconds ago 959MB
Now we can run the container, either from a Docker client directly on the host or from within R:
system("docker run --rm hellow:latest")
You are peachy!
[1] "Hello"
[1] "Bonjour"
Translations of [1] "Hello"
[ English -> Français ]
[1] "Hello"
[1] "Bonjour", [1] "Salut"
system("docker run --rm hellow:latest :es")
You are groundbreaking!
[1] "What's up?"
[1] "¿Qué pasa?"
Translations of [1] "What's up?"
[ English -> Español ]
[1] "What's up?"
[1] "¿Qué pasa?", [1] "¿Qué hay de nuevo?"
The “shiny” use case enables quick and intuitive templating of tools necessary to host a Shiny app in a Docker container. There are a number of ways that developers could ship a Shiny app inside an R package, and the example that follows demonstrates one strategy for doing so.
To demonstrate this use case we use the ocf
R package source code that ships with pracpac
. The ocf
package wraps a custom Shiny app that we’ve titled “Old Colorful”. This app is intended to provide basic motivation for how a Shiny image could be templated, built, and used with pracpac
. In short, the app code uses the bones of the “Old Faithful” Shiny app and simply adds a feature to add color to the eruptions histogram. The color palette is selected at random from palettes in the wesanderson
package via a function called get_pal()
. This function is embedded in a reactive context such that when the user clicks a button, the histogram plot title and bar colors dynamically update.
We will first move the example ocf
package to a temporary location:
library(pracpac)
library(fs)
## specify the temp directory
<- tempdir()
tmp ## copy the example ocf package to the temp directory
dir_copy(path = system.file("ocf", package = "pracpac"), new_path = path(tmp, "example", "ocf"))
dir_tree(path(tmp, "example", "ocf"), recurse = TRUE)
├── DESCRIPTION
├── NAMESPACE
├── R
│ └── pal.R
├── inst
│ └── app
│ └── app.R
└── man
└── get_pal.Rd
We can use use_docker(..., use_case="shiny")
to create the template of files for building the Docker image:
use_docker(pkg_path = path(tmp, "example", "ocf"), use_case = "shiny")
Using renv. Dockerfile will build from renv.lock in /tmp/RtmpsMexB6/example/ocf/docker.
Using template for the specified use case: shiny
Writing dockerfile: /tmp/RtmpsMexB6/example/ocf/docker/Dockerfile
The directory will be created at /tmp/RtmpsMexB6/example/ocf/docker/assets
Assets for the specified use case (shiny) will be copied there.
The specified use case (shiny) includes the following asset: app.R
Building package ocf version 0.1.0 in /tmp/RtmpsMexB6/example/ocf/ocf_0.1.0.tar.gz
docker build command:
docker build --tag ocf:latest --tag ocf:0.1.0 /tmp/RtmpsMexB6/example/ocf/docker
The directory now contains the docker/
subdirectory, which has another subdirectory called assets/
for the templated Shiny files:
dir_tree(path(tmp, "example", "ocf"), recurse = TRUE)
├── DESCRIPTION
├── NAMESPACE
├── R
│ └── pal.R
├── docker
│ ├── Dockerfile
│ ├── assets
│ │ └── app.R
│ ├── ocf_0.1.0.tar.gz
│ └── renv.lock
├── inst
│ └── app
│ └── app.R
└── man
└── get_pal.Rd
One of the files needs to be edited as follows:
app.R
library(shiny)
library(ocf)
# Define UI for application that draws a histogram
<- fluidPage(
ui
# Application title
titlePanel("Old Faithful Geyser Data (In Color!)"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30),
actionButton(inputId = "color", label = "Add Color")
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
)
# Define server logic required to draw a histogram
<- function(input, output) {
server
<- eventReactive(input$color, {
color_palette get_pal()
})
$distPlot <- renderPlot({
output# generate bins based on input$bins from ui.R
<- faithful[, 2]
x <- seq(min(x), max(x), length.out = input$bins + 1)
bins
# draw the histogram with the specified number of bins
hist(x,
breaks = bins,
col = color_palette()[[1]][1],
border = color_palette()[[1]][5],
main = paste0("Histogram in ", names(color_palette())))
})
}
# Run the application
shinyApp(ui = ui, server = server)
Note that for this particular example, the templated Dockerfile requires no editing. In some cases, it may be necessary to edit the Dockerfile to modify behavior of the Shiny server (e.g., edit the shiny-server.conf file).
With the template files created and edited as described above, we can build the image:
build_image(pkg_path = path(tmp, "example", "ocf"))
In this case, the image will be built with build_image()
default behavior of tagging with the package name and version:
system("docker images")
Now we can run the container, either from a Docker client directly on the host or from within R:
system("docker run --rm -it -d --user shiny -p 3838:3838 ocf:0.1.0")
In this case we run the container detached (-d
) and use the port 3838 such we can navigate to localhost:3838
in a web browser and view the running app.
Note that we discuss Docker terminology in the “Basic Usage” vignette: vignette("basic-usage", package = "pracpac")
↩︎