class: title, cover, middle, center <h1>Desert Island Docker</h1> <h2>R Edition</h2> <div class="details"> <div class="conference">useR! (Salzburg)</div> <div class="date">11 July 2024</div> <div class="author">Andrew B. Collier</div> </div> <img class="logo-full" src="img/fathom-logo-full.svg" width="20%"> --- background-image: url("data:image/png;base64,#img/shipwreck.webp") background-repeat: no-repeat background-size: 100% 100% ??? Lightning flashes. Thunder crashes. The sea is whipped into a fury and your ship is smashed upon the rocks. You swim ashore, holding your trusty laptop aloft. When you wake in the morning you're relieved to find that your battery is fully charged and you have your three favourite Docker images. What are they? --- class: middle, center .big7x[Which<br> .deep-sky.big12x[three]<br> Docker<br> images?] ??? What 3 Docker images would you want with you if you were shipwrecked on a desert island? <!-- ====================================================================== --> <!-- ====================================================================== --> <!-- === === --> <!-- === R === --> <!-- === === --> <!-- ====================================================================== --> <!-- ====================================================================== --> --- class: section, center .big12x.top[1] .big5x[Food & Water: .deep-sky[R]] ??? Many of these problems can be resolved by just using the R Docker image. --- background-image: url("data:image/png;base64,#img/cookie-cutter-docker.webp") background-repeat: no-repeat background-size: 100% 100% ??? It's time to introduce some terminology. Two terms are pervasive with Docker: image and container. You can think of an image as a template (or by analogy, a cookie cutter). A container then is what you get by applying the template (or using the same analogy, cut out a cookie). You can have multiple images (perhaps for Python, NGINX and R) and each can be used to create one or more containers. --- class: middle .big3x[A .deep-sky[container] comes from an .deep-sky[image].] ??? So, a container comes from an image. --- class: middle .big3x[Where does an .deep-sky[image] come from?] ??? But where does an image come from? --- class: inverse, middle <img src="img/image-official-rocker-tidyverse.png" width="100%" > <br><br> .big2x[https://hub.docker.com/r/rocker/tidyverse] More than 10 million downloads. 🤩 Around 200 000 downloads per week. ??? There are a variety of image sources, but the most common is Docker Hub. It hosts both "official" as well as "user" images. You can find the details of the R image at this URL. It's fairly popular: as you can see it's been downloaded more than 1 billion times. --- class: inverse, middle The `rocker` organisation publishes a number of images including: - `rocker/r-ver` — Specific R version 🟢 - `rocker/rstudio` — Adds RStudio ⭐ - `rocker/tidyverse` — Adds `{tidyverse}` and `{devtools}` - `rocker/verse` — Adds TeX and publishing packages ⭐ - `rocker/geospatial` — Adds geospatial packages - `rocker/shiny` — Adds Shiny server 🟢 - `rocker/shiny-verse` — Shiny plus `{tidyverse}` and `{devtools}` - `rocker/binder` — Adds requirements to run on https://mybinder.org/ - `rocker/cuda` — Adds CUDA support .footnote[* 🟢 — included in this talk; ⭐ — very handy indeed!] --- class: middle <img src="img/pull-run.svg" width="100%" > ??? There are two steps to using Docker. First you need to pull and image. Then you run the image to create a container. --- class: inverse, middle, center <!-- Image made with "screenshot - large" profile. --> <img class="shadow" src="img/r-ver-pull.png" width="950px"> ??? The first step towards creating a R container is to download (or "pull") the image using the `docker pull` command. That doesn't actually run the image. It just downloads it onto your host. --- class: inverse <!-- Image made with "screenshot - large" profile. --> .center[<img class="shadow" src="img/r-ver-run.png" width="950px">] .footnote[* Er, that's rather underwhelming. So what?] ??? To create a container you use the `docker run` command. The result might seem underwhelming. And it is. But there's a reason for that: containers are non-interactive by default. --- class: inverse <!-- Image made with "screenshot - large" profile. --> .center[<img class="shadow" src="img/r-ver-run-interactive.png" width="950px">] ??? If you want to interact with the container then you need to provide the `-i` (interactive) and `-t` (terminal) flags. You might be wondering what the "latest" is all about. --- class: middle .big3x[What's the `:latest` for?] -- .right.big3x[It's a .deep-sky[tag].] ??? Images come with tags. These are labels for different versions of an image. --- class: inverse, middle <img src="img/image-tag-example.png" width="100%" > --- class: middle .big3x[Use the .deep-sky[right tag]!] --- class: inverse, center <!-- Image made with "screenshot - large" profile. --> <img class="shadow" src="img/r-ver-new.png" width="950px"> ??? This code assumes that strings are converted into factors by default when creating a data frame. This changed in recent versions of R. This is a breaking change if you code relied on the implicit conversion taking place. --- class: inverse, center <!-- Image made with "screenshot - large" profile. --> <img class="shadow" src="img/r-ver-old.png" width="950px"> --- background-color: #fdfbf7 background-image: url("data:image/png;base64,#img/tardis.webp") background-repeat: no-repeat background-size: 100% auto ??? Docker is a time machine. It allows you to safely and reliably access old, perhaps even ancient, versions of R. <!-- ====================================================================== --> <!-- ====================================================================== --> <!-- === === --> <!-- === SHINY === --> <!-- === === --> <!-- ====================================================================== --> <!-- ====================================================================== --> --- class: section, center .big12x.top[2] .big5x[Shelter: .deep-sky[Shiny]] --- class: inverse, middle <!-- 200% zoom in Firefox on laptop screen. Cropped in Gimp. --> <img src="img/image-official-rocker-shiny.png" width="100%" > <img src="img/image-official-rocker-shiny-latest.png" width="100%" > --- class: inverse, middle, center <!-- Image made with "screenshot - large" profile. --> <img class="shadow" src="img/shiny-run.png" width="950px"> --- class: inverse, middle <img class="shadow" src="img/shiny-unable-to-connect.png" width="100%" > --- class: inverse, middle, center <img class="shadow" src="img/thats-a-bummer.jpg" width="50%" > --- class: middle .big3x[A container is a .deep-sky[sealed environment]!] .footnote[* You could say that everything is "contained". Nothing comes in. Nothing goes out.] ??? But there's a problem. Not only are containers non-interactive by default, each container is a sealed environment. Nothing comes in or goes out. --- background-image: url("data:image/png;base64,#img/submarine.webp") background-repeat: no-repeat background-size: 100% auto ??? This is by design. In many cases you want your container to be completely isolated. A self-contained environment like a submarine. But sometimes you need to get information in and out. --- class: middle .big3x[ But you can 👊 .deep-sky[punch] 👊 holes in it. `-v` — mount a (disk) volume<br> `-p` — publish a (network) port ] ??? However, there are some run time flags that can be used to share information with a running container. --- background-image: url("data:image/png;base64,#img/submarine-surface.webp") background-repeat: no-repeat background-size: 100% auto --- class: inverse ```bash docker run rocker/shiny ``` But that's .deep-sky[inaccessible]! ☹️ -- Share the app directory (so you can access files from the host). ``` bash docker run \ * -v $(pwd)/shiny-faithful/:/srv/shiny-server/ \ rocker/shiny ``` -- Make it accessible via port 3838 (so you can access the app with your browser). ``` bash docker run \ -v $(pwd)/shiny-faithful/:/srv/shiny-server/ \ * -p 3838:3838 \ rocker/shiny ``` --- class: inverse, middle <img class="shadow" src="img/shiny-old-faithful.png" width="100%" > --- class: section-slide-orange .big8x[Make<br>this<br>.underline[much more]</br>useful!] --- background-color: #fdfbf7 background-image: url("data:image/png;base64,#img/dockerfile-image-container.webp") background-repeat: no-repeat background-size: 100% auto .footnote[* Roll your own Docker image by creating a `Dockerfile`.] --- class: inverse, middle ``` bash library(leaflet) N <- 20; SPREAD <- 0.0625 ui <- shiny::fluidPage( leafletOutput("map"), p(), actionButton("recalc", "New points") ) server <- function(input, output, session) { points <- eventReactive(input$recalc, { cbind(rnorm(N) * 2 * SPREAD + 13.03, rnorm(N) * SPREAD + 47.8) }, ignoreNULL = FALSE) output$map <- renderLeaflet({ leaflet() %>% addProviderTiles(providers$Stadia.StamenTerrain) %>% addMarkers(data = points()) }) } shiny::shinyApp(ui, server) ``` --- class: inverse, middle Create a `Dockerfile`: ``` bash FROM rocker/shiny RUN R -e "install.packages('leaflet')" COPY app.R /srv/shiny-server/ ``` Then build and run: ```bash docker build -t shiny-leaflet . docker run -p 3838:3838 shiny-leaflet ``` 🚨 No need for `-v` because the application is baked into the image. 👎 Less flexible. 👍 Easier to deploy. ??? We can make this more useful by creating a derived image and installing Numpy on it. --- class: inverse, middle <img class="shadow" src="img/shiny-leaflet.png" width="100%" > --- background-color: #fdfbf7 background-image: url("data:image/png;base64,#img/cake.webp") background-repeat: no-repeat background-size: 100% auto .footnote[* Deploying is a piece of cake. Still don't do it on a Friday.] ??? This means that deploying your app should now be a piece of cake. I'm not for a moment suggesting that you deploy on a Friday though. Nobody is that crazy, not even with Docker. <!-- ====================================================================== --> <!-- ====================================================================== --> <!-- === === --> <!-- === JUPYTER === --> <!-- === === --> <!-- ====================================================================== --> <!-- ====================================================================== --> --- class: section, center .big12x.top[3] .big5x[Build a Raft: .deep-sky[Jupyter]] --- class: inverse, middle <!-- 200% zoom in Firefox on laptop screen. Cropped in Gimp. --> <img src="img/image-jupyter-r-notebook.png" width="100%" > --- class: inverse, middle ``` bash docker run \ * -p 8888:8888 \ * -v $(pwd):/home/jovyan \ jupyter/r-notebook ``` - The `-p` option makes it accessible via port 8888. - The `-v` option shares the content of a directory on the host. --- class: inverse, middle <!-- 170% zoom in Firefox on laptop screen. --> .center[<img class="shadow" src="img/jupyter.png" width="90%" >] --- class: middle .big3x[Let's make this more interesting by...] --- class: middle .big3x.right[... installing .deep-sky[`{rJava}`].] ??? This is a well known challenge with R. Perhaps not as challenging as getting out of `vim`, but it's up there. --- class: inverse, middle .center[<img class="shadow" src="img/installing-rjava.jpeg" width="90%" >] --- class: middle .big3x.deep-sky[True story.] --- class: inverse, middle ``` bash FROM jupyter/r-notebook:r-4.3.1 USER root RUN apt update -y && \ * apt install -y default-jdk ENV JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 ENV LD_LIBRARY_PATH=${JAVA_HOME}/lib/server:${LD_LIBRARY_PATH} *RUN R CMD javareconf # Set CRAN repository. COPY Rprofile /home/jovyan/.Rprofile # Install {rJava} because it's a dependency of {mailR}. *RUN R -e 'install.packages(c("rJava", "mailR"))' COPY send-email.ipynb /home/jovyan ``` --- class: inverse, middle <!-- 170% zoom in Firefox on laptop screen. --> .center[<img class="shadow" src="img/jupyter-send-mail.png" width="90%" >] --- class: inverse, middle <!-- 150% zoom in Firefox on laptop screen. Hid some advertising. --> <!-- GO TO https://www.smtpbucket.com/emails?sender=sender@gmail.com --> .center[<img class="shadow" src="img/smtpbucket.png" width="90%" >] --- class: inverse background-image: url("data:image/png;base64,#img/emayili-details.png") background-repeat: no-repeat background-size: auto 100% background-position: right center .middle[ But if you want to send email from R, then check out `{emayili}`. <br><br> .center[<img class="shadow" src="img/emayili-hex.png" width="35%" >] <br> https://datawookie.github.io/emayili/ ] --- class: middle .leftcol60[ .big4x[1. .deep-sky[R] 2. .deep-sky[Shiny] 3. .deep-sky[Jupyter] ] Don't wait to be marooned... <br><br> .big2x[🌴 Use these images **now**! 🏝️] ] .rightcol40[ <div style="text-align: right;"> <img src="https://avatars.githubusercontent.com/u/6134409?v=4" width="200px"><br> Andrew B. Collier <code>andrew.b.collier@gmail.com</code> .smaller.list-style-none[ -
https://github.com/datawookie -
https://twitter.com/datawookie -
https://www.linkedin.com/in/datawookie ] <img class="logo-full" src="img/fathom-logo-full.svg" width="40%"> </div> ] <div style="clear: both;"></div> Slides at https://bit.ly/2024-salzburg-desert-island-docker. .footnote[* Add a mechanical keyboard and gaming mouse and I might never leave.] ??? As an R developer these are the images that I'd want to have with me on a desert island. They should keep me happy and productive until I'm rescued. Throw in a mechanical keyboard and gaming mouse and I might never want to leave. But don't tell my wife.