└── README.md /README.md: -------------------------------------------------------------------------------- 1 | 2 | Scaling Shiny apps using Docker 3 | ================ 4 | 5 | In this tutorial, we are going to create a Docker image for your Shiny App and upload it to Google Cloud without dying trying. 6 | 7 | ## Intro 8 | 9 | Sometimes the standard version of the Shiny free servers are not enough to deploy our beautiful creations with R. 10 | The most annoying limitation is the 5 active instances limit. 11 | For test environments maybe it wasn't bad, but once the 12 | application reaches more professional areas where probably more 13 | people use the app at the same time is more difficult. 14 | In this short tutorial we will take a Shiny application to a 15 | Docker repository and from there we will deploy to a service of 16 | Google Cloud with more RAM and CPUs available. 17 | 18 | ## Ingredients 19 | 20 | - A Shiny app, .Rmd, flexdashboard (as in this example), 21 | etc. 22 | - Docker and a Docker account to upload the image to a repository. 23 | - Google Cloud account and active basic APIs (the process is 24 | quite intuitive). 25 | 26 | ## Installing docker 27 | 28 | You can install Docker from the official page, downloading and executing the installer as any program. 29 | On OSX, as is my case, after downloading the 30 | .dmg is installed as follows from the terminal: 31 | 32 | ``` bash 33 | sudo hdiutil attach Docker.dmg 34 | sudo /Volumes/Docker/Docker.app/Contents/MacOS/install 35 | sudo hdiutil detach /Volumes/Docker 36 | ``` 37 | 38 | ## Creating the Dockerfile 39 | 40 | The Dockerfile is a set of lines of text that configure 41 | the packages that Docker is going to use to generate the environment of our 42 | application and it is where we will also give “commands” to execute the app. 43 | 44 | In my case, I created a folder called "Docker" with two elements: 45 | a folder called “R” where everything related to the application is 46 | Shiny and the “Dockerfile” file itself (so, without dots, or extensions, 47 | no weird stuff). 48 | 49 | In my case, the Dockerfile contains the following lines, 50 | commented point by point to better understand: 51 | 52 | ``` bash 53 | FROM rocker/shiny:latest # Shiny and R environment are installed 54 | 55 | RUN apt-get update -qq && apt-get -y --no-install-recommends install \ # Installing dependencies 56 | libgdal-dev \ # They quite depend of the packages you are using in the app 57 | libproj-dev \ # 58 | libgeos-dev \ 59 | default-libmysqlclient-dev \ 60 | libmysqlclient-dev \ 61 | libudunits2-dev \ 62 | netcdf-bin \ 63 | libxml2-dev \ 64 | libcairo2-dev \ 65 | libsqlite3-dev \ 66 | libpq-dev \ 67 | libssh2-1-dev \ 68 | unixodbc-dev \ 69 | libcurl4-openssl-dev \ 70 | libssl-dev 71 | 72 | RUN apt-get install pandoc # I had some Pandoc problems but this line fixed it 73 | 74 | RUN apt-get update && \ # Updating packages and cleaning 75 | apt-get upgrade -y && \ 76 | apt-get clean 77 | 78 | # Installing R packages 79 | # notice that we are already executing R COMMANDS inside Docker 80 | 81 | RUN R -e "install.packages(pkgs=c('shiny','tidyverse', 82 | 'flexdashboard', 'sf', 'ggalluvial', 83 | 'plotly', 'rmarkdown'), repos='https://cran.rstudio.com/')" 84 | 85 | RUN R -e "install.packages(pkgs=c('devtools'), 86 | repos='https://cran.rstudio.com/')" 87 | 88 | RUN R -e "devtools::install_github('rstudio/leaflet')" 89 | 90 | RUN mkdir /root/app # Creating the app folder 91 | 92 | COPY R /root/shiny_save # Copies the R folder into shiny_save 93 | 94 | EXPOSE 3838 #Very important: assign a communication port to the app. 95 | #Later we will not be able to do anything if the port is not exposed 96 | 97 | 98 | # The app is run (in this case, being a flexdashboard 99 | # I have to run it with rmarkdown, otherwise it would be shiny::runApp(...)). 100 | # As an extra argument I pass the open port. 101 | 102 | CMD ["R", "-e", "rmarkdown::run('/root/shiny_save/dash_v2.Rmd', 103 | shiny_args=list(host='0.0.0.0', port=3838))"] 104 | ``` 105 | 106 | ## Image building 107 | 108 | Once we have the dockerfile and the folder with all the files 109 | that the app needs to run, we proceed to build the Docker image 110 | itself. For this, we open the console and execute: 111 | 112 | ``` bash 113 | docker login 114 | ``` 115 | 116 | It will ask us for the Docker username and password. We write it and then: 117 | 118 | ``` bash 119 | docker build -t appname . 120 | ``` 121 | 122 | That command will build an image called “appname” with the parameters 123 | of the dockerfile (for that the period at the end). This image contains the 124 | R environment and all the code that we have used, within a system 125 | Linux. 126 | This is why it probably takes a long time to run the first 127 | time (it takes me approximately 3 hours), obviously depending on the 128 | app complexity. If we make modifications to the R code of the 129 | application and push the changes (executing the same parameters), 130 | the construction takes much less time since most of the layers of the 131 | environment are the same and are not changed. This is something that I love about 132 | Docker. The work is divided into "layers", so if you correct a 133 | small part of the code, the build and push of the app take seconds. 134 | 135 | ``` bash 136 | docker tag appname user/appname:latest 137 | ``` 138 | 139 | We “tag” the app within the public repository that we are about to create. 140 | It is like saying that we are “selecting” it to upload it. The 141 | repository will be named user/appname. The last part “:latest” is 142 | a name that we can put to identify the latest version of the 143 | app. We can modify it for each version. 144 | 145 | ``` bash 146 | docker push user/appname:latest 147 | ``` 148 | 149 | The image is pushed to the repository, just as if it were GitHub. 150 | 151 | ## Deploy to Google Cloud 152 | 153 | Once the project is created, we proceed to open the command console 154 | within the same Google website. The first thing we must do is 155 | log in with our Docker credentials. 156 | 157 | ``` bash 158 | docker login 159 | ``` 160 | 161 | Later, we “bring” the Docker image to our project. 162 | Google Cloud with the latest changes. 163 | 164 | ``` bash 165 | docker pull user/appname:latest 166 | ``` 167 | 168 | Then, analogously to what we did in Docker, the image is "tagged" 169 | push, the only thing is that this time we don't push to the Docker repo but to the 170 | Google Cloud image container. Replace PROJECT_ID with the ID of 171 | your project. 172 | 173 | ``` bash 174 | docker tag user/appname:latest gcr.io/PROJECT_ID/user/appname:latest 175 | ``` 176 | 177 | Finally, we push the image. 178 | 179 | ``` bash 180 | docker push gcr.io/PROJECT_ID/user/appname:latest 181 | ``` 182 | 183 | ## Create the service instance 184 | 185 | We are almost done. After pushing the image, in the Google tab 186 | Cloud Run click “Create Service”. This will enable us a machine 187 | virtual to do the deploy and that we can share the app to a lot 188 | of people. 189 | 190 | Where it says "Url of the image" put select and the 191 | image options. The image “appname” will appear with the 192 | latest version. We use that and fill in the parameters with what 193 | need the project. 194 | 195 | **IMPORTANT:** In the part below where it says “CONTAINER”, 196 | Fill in the “Container port” field with port 3838 or the 197 | that you have chosen in the construction of the Dockerfile, since without this 198 | Google will mis-route the port generating errors. 199 | 200 | You wait a few seconds for Google to create the instance and that's it! It's already the 201 | app deployed to Google Cloud Run. Anyone will be able to access it 202 | through the URL that shows you above the panel. 203 | --------------------------------------------------------------------------------