Build Scalable R Shiny Apps with Azure and Docker.
Azure makes the transition from prototype to production as seamless as possible. This tutorial provides the necessary ingredients to get started including a sample app with a pre-made dockerfile found in this GitHub Repo.
In today’s data-driven environments, the ability to create web-based, reactive, and highly customizable analytic dashboards continues to provide a great deal of value for many organizations. And while solutions like PowerBI and Tableau are great products that go a long way toward filling that gap, there are often situations in which finer-grained customization and control are the only way to provide a workable solution.
Both R and Python have developed web application frameworks that allow for virtually unlimited statistical customization, rapid prototyping, seamless integration with powerful backends, and a myriad of visualizations for creative storytelling. For those using R, the R Studio Shiny server platform is commonly used to create web-available applications. However, one key challenge faced by many who have developed novel solutions in Shiny has been the issue of sharing the application with stakeholders on stable servers that allow for automatic scaling. In this tutorial, I hope to share how this challenge can be overcome using Docker and Azure.
The lynchpin of this approach is Docker. Docker bundles application code inside a container and deploys it to servers in isolated environments that can be easily managed. More on Docker containers can be found here.
While this particular tutorial is focused on deploying R Shiny applications with Docker in an Azure environment, it can be readily adopted for Python or anything that can be “Dockerized” — i.e., literally any compatible web application or API.
This tutorial does not explain how to set up a login page for the app, secure the app behind a firewall, or set up a DevOps workflow using Azure Pipelines and GitHub, but all of these operations are possible to implement with relative ease.
Approach Rationale
Our team consists of mostly data scientists who would prefer to spend time developing analytic solutions for our clients rather than programming infrastructure. We were in the market for a platform as a service (PAAS) offering, and we landed on Azure App Service. It handles much of the infrastructure development and maintenance, including traffic management across instances, automatic backups, auto scaling, networking, custom domains, staging slots, integration with GitHub for setting up a DevOps workflow and much more. This option allowed us to maximize our time crafting client solutions and letting Microsoft handle much of the production demands of managing applications at an enterprise scale.
Let’s Get Started! Necessary Prerequisites
I created a GitHub repository to provide the minimum components necessary to launch a Shiny app to the cloud. We will be working with the out-of-the-box Old Faithful Geyser application, but I assure you, the application can be as complicated as you need. This simply provides minimal scaffolding for cloud deployment. Below are three prerequisite steps:
1. You will need to download the latest version of Docker Desktop and ensure your system meets all the minimum requirements. This is especially important for Windows users because Microsoft is constantly evolving and adding upgrades to make integration with a Linux environment easier for engineers and developers. For Windows users, I also recommend using the latest Ubuntu distribution for your WSL2 backend when downloading updates. Ensure Docker is running appropriately before moving on.
2. If you don’t have an Azure account, you’ll need to create one and then create a Container Registry, which provides a place to store your Docker images. Each Docker image will represent a fully-contained app that a computer can pick up and run.
Once Docker desktop is working and you have an Azure space, download VS Code, which is a very convenient code editor that will remove a lot of the pain of building, running, and pushing Docker images. This step is not necessary for deployment but will be used for this tutorial where possible.
Setting up the folder:
Clone the repository deploy_shiny_app_to_the_cloud to your local PC either through GitHub desktop or through VS Code. Once it has been downloaded locally, open the folder, and VS Code will recognize this as your current directory.
Once you open the project folder deploy_shiny_app_to_the_cloud, VS Code will likely recognize the Dockerfile and ask if you would like to install the Docker extension; say “Yes” and install. If VS Code does not prompt you to add the extension, install by clicking on the “Extensions” option on the left-hand panel.
For Windows users, it’s a little different as you will need to plug into your Windows subsystem for Linux (WSL2) by hitting the green button on the bottom left before opening the folder.
Choose the option: “Remote-WSL: Open Folder in WSL…”
And choose the folder deploy_shiny_app_to_the_cloud. For Windows users, the green icon on the bottom left should now look something like this:
From the perspective of VS, you’re now operating in a Linux environment
Building the Docker Image
Below I’ve highlighted two ways to build the Docker image; one using the traditional command line and one using VS Code, which will simply autofill the command line instructions.
Using the terminal, we could use:
$ Docker build -t deployshinyapptothecloud .
Where the deployshinyapptothecloud is the name you want to give the app.
Or using VS Code, simply right-click on the Dockerfile and press ‘Build Image.’
Once the image is built, you can run the image in a container. Again, we can use the command line or use VS Code.
Using the command line:
$ docker run -d –rm -p3838:3838 deployshinyapptothecloud
Then in your browser, navigate to “localhost:3838” to see your app up and running locally in a self-isolated container.
Or Using VS code, navigate to your docker plugin, simply right click on the latest image and hit ‘Run Interactive’ and then navigate to “localhost:3838.”
Off to the Cloud!
This part could be done in the terminal as well, but I prefer using VS Code for convenience. Click on ‘Connect Registry’ nestled underneath the Registries section of the Docker Extension and select Azure.
Then follow the prompts to sign into your Azure account. It will automatically take you to an Azure login page and ask for your username and password.
Once signed in, we need to push that image to our Azure repository.
Again, we can use the terminal and execute the following command to push the image to our cloud space:
Docker push [name of your registry].azurecr.io/ deployshinyapptothecloud:latest
Or using VS Code, right-click on the image tag (latest) and select “Push..” and choose the Azure Registry you made when you set up your account.
Creating a Server to Host the App
Once the image has been pushed to your account, log in and navigate to your Azure Container Registry and select “Repositories” in the left-hand blade.
Click on the image to open another window. Click on the three dots and select ‘Deploy to web app.’
Make sure to choose Linux operating system and create a new App Service Plan in the location nearest you. The setup should look something like this.
Navigate to the New App Service Plan
Once the resources are created, go to the App Service Plan you just made, and you will find the URL that will take you to your new application in the upper right corner. It takes about 10–15 minutes, even after the resource is created, for the server to pull the image and make the connections. After about 10–15 minutes, click on your new URL and see your app on your very own cloud server!
Oh, the options!
You will quickly discover just how many point-and-click options there are to explore. Truth be told, I don’t believe I’ve even come close to exploiting all the options that are available, and more seem to be added every month. But I would like to draw your attention to a few I think are valuable when thinking about your apps’ purpose.
- Scale
Scaling up means you want your Docker container to be hosted on a larger server. Click this option to see the many possibilities (and cost) associated with increasing the performance of your application.
Scaling out means you want to instantiate more servers to handle more requests. Use this option to handle bursts of traffic based on memory consumption or even time-of-day scaling to handle working hours vs. non-working hours.
2. Troubleshooting
Not everything works as expected. It’s nice to understand what’s going on and to consider how to troubleshoot issues. To do this, you can look at the log streams of your app and the container to gain clues.
How to Make Changes to the App
When you make changes locally to your Shiny application, simply rebuild the image and push back to Azure using the steps already described. Your Azure App Service plan will automatically pull the latest image to host. As I mentioned earlier, it’s possible to sync the workflow to a GitHub repository, whereby the simple act of committing changes to a branch can trigger the whole process from start to finish.
Costs and Service:
In terms of cost, I would place this option in the mid-to-higher range because it’s a PAAS offering, and Azure handles much of the engineering for you. This still made the most sense for our company because what we might pay in higher computing costs, we certainly save in labor and time.
As of this writing, prices range from $12 to $450 per month for a single machine, not including any horizontal scaling, which could easily climb into the thousands of dollars with a large server getting replicated to handle traffic burst. We’ve found that a $150 per month server can handle about 20–25 concurrent users on a standard Shiny App with a SQL backend, and a fair amount of analytics, including dynamic mapping, building custom regression models, and computing fairly complex measures before running into serious performance issues.
Summary
This tutorial has, hopefully, shown how easy it can be to take a locally developed application, wrap it in a Docker container, and host it on Azure. For the sake of brevity and clarity, this article did not get into setting up a DevOps workflow, securing the app behind a firewall, creating a unique URL, or connecting the app to a login page. I plan on sharing those steps in later articles or videos.