I want to deploy a shiny application composed of files ui , server and global via Docker. All the files are in the folder deploy_test
I simulated this dataset
set.seed(123)
dir.create("deploy_test")
setwd("deploy_test")
mydata_<-data.frame(
gender=c(rep("Male",50),rep("Female",25)),
height=c(rnorm(50,1.70,0.05),rnorm(25,1.65,1))
)
saveRDS(mydata_,file = "mydata_.RDS")
Here are the contents of my files:
1. UI
source("global.R")
dashboardPage(
dashboardHeader(title = "Test of app deployment"),
dashboardSidebar(
selectInput("gender","Gender",as.character(unique(mydata_$gender)))
),
dashboardBody(
fluidRow(
column(6,plotOutput(
"plot1"
)),
column(6,plotOutput(
"plot2"
))
),
fluidRow(
dataTableOutput(
"table"
)
)
)
)
2. SERVER
source("global.R")
function(input, output, session){
output$plot1<-renderPlot(
{
data_<-mydata_%>%filter(
gender==input$gender
)
boxplot(data_$height)
}
)
output$plot2<-renderPlot(
{
data_<-mydata_%>%filter(
gender==input$gender
)
hist(data_$height)
}
)
output$table<-renderDataTable(
{
data_<-mydata_%>%filter(
gender==input$gender
)
data_
}
)
}
3. GLOBAL
library(shinydashboard)
library(shiny)
library(tidyverse)
library(DT)
mydata_<-readRDS("mydata_.RDS")
4. DOCKERFILE
Dockerfile is located in the same folder as the Shiny's:
# Base image
FROM rocker/shiny
#Make a directory in the container
RUN mkdir /home/shiny-app
# Install dependencies
RUN R -e "install.packages(c('tidyverse','shiny','shinydashboard','DT'))"
COPY . /home/shiny-app/
EXPOSE 8180
CMD ["R", "-e", "shiny::runApp('/home/shiny-app')"]
I built my container without any problem:
docker build -t deploy_test .
When I run it:
docker run -p 8180:8180 deploy_test
It generate the links: Listening on http://xxx.x.x.x:xxxx
But nothing appears when I access the link:
I got: La connexion a échoué
There are several pieces to this: either specify
runApp(.., host=, port=)or shift to using the built-in shiny-server in the parent image.Fix
runAppFirst is that you expose port 8180 but the default of
runAppmay be to randomly assign a port. From?runApp:My guess is that it does not randomly choose 8180, at least not reliably enough for you to count on that.
The second problem is that network port-forwarding using docker's
-pforwards to the container host, but not to the container'slocalhost(127.0.0.1). So we also should assign a host to your call torunApp. The magic'0.0.0.0'in TCP/IP networking means "all applicable network interfaces", which will include those that you don't know about before hand (i.e., the default routing network interface within the docker container). Thus,When I do that, I'm able to run the container and connect to
http://localhost:8180and that shiny app works. (Granted, I modified the shiny code a little since I don't have your data, but that's tangential.)FYI, if you base your image on
FROM rocker/shiny-verseinstead ofFROM rocker/shiny, you don't need toinstall.packages('tidyverse'), which can be a large savings. Also, with bothrocker/shinyandrocker/shiny-verse, you don't need toinstall.packages('shiny')since it is already included. Two packages saved.Use the built-in shiny-server
The recommended way to use
rocker/shiny-verseis to put your app in/srv/shiny-server/appnamegoeshere, and use the already-functional shiny-server baked in to the docker image.Two benefits, one consequence:
runApp(.)fails, it stops. (Granted, this is governed by restart logic of shiny in the presence of clear errors in the code.)http://localhost:8180/appnamegoeshere. Thehttp://localhost:8180page is a mostly-static landing page to say that shiny-server is working, and it does not by default list all of the apps that are being served by the server.This means that your
Dockerfilecould instead be this:That's it, nothing more required to get everything you need since
CMDis already defined in the parent image. Because shiny-server defaults to port 3838, your run command is nowand your local browser uses
http://localhost:3838/myappfor browsing.(FYI, the order of
RUNand other commands in aDockerfilecan be influential. If, for instance, you change anything before theinstall.packages(.), then when you re-build the image it will have to reinstall those packages. Since we're no longer needing to (re)install"tidyverse"this should be rather minor, but if you stick withrocker/shinyand you have toinstall.packages("tidyverse"), then this can be substantial savings. By putting theRUNandCOPYcommands for this app afterinstall.packages(..), then if we rename the app and/or add more docker commands later, then thatinstall.packagesstep is cached/preserved and does not need to be rerun.)