Deploying an RShiny webapp to Azure
Data scientists often inhabit their own realm, seemingly disconnected from the world of microservices, clouds and DevOps. This divergence can be attributed to the intricate nature of handling large-scale data computations, which may not always align with the deployment of modern cloud solutions. However, there are instances where the need arises to deploy a relatively lightweight RShiny app to the Cloud capitalising on the advantages offered by the contemporary landscape.
But is there a seamless way to execute an RShiny app in the Cloud, circumventing the installation of the R Servers on virtual machines and the execution of resource-intensive, costly operations?
Within the confines of this article, I shall elucidate a method to deploy an RShiny web application to Azure App Service, harnessing the potential of Docker and Azure Container Registry.
The Use-Case
The RShiny application would need to deal with input data and visualise it. The data would be stored in a PostgreSQL database. The application would then run in an App Service container from a Docker image (could also be deployed to Kubernetes), stored in Azure Container Registry.
The Deployment
As we’re focusing on the deployment step in this article, we are not going to focus on the application structure much. Though some points may be highlighted for better understanding:
- The application is a simple straightforward one, asking to insert your name into the
textInput
field, select one to many options fromcheckboxGroupInput
and press the ‘Submit’actionButton
. - The backend has a single
observeEvent
function, which is collecting the input and sending it to the database.
Database connection
We’re using Azure Database for PostgreSQL, hence the application requires a PostgreSQL driver to connect to a remote DB.
Here is a good recipe of setting up a DB connection inside your R code on the Net: https://techcommunity.microsoft.com/t5/azure-database-for-postgresql/r-azure-database-for-postgresql/ba-p/805676.
One important thing to be aware of at this point is that exposing your connection credentials may be a potential security threat.
Luckily, Azure App service gives the opportunity to set up environment variables, which will be available for the application to consume in the container runtime. R is not the simplest language to set up the container runtime environment variables consumption, but it is in general possible with some additional steps.
# connection string credentials, to be later sourced from environment variables.
pgServerName <- Sys.getenv("PG_SERVER_NAME")
dbName <- Sys.getenv("DB_NAME")
pgUserName <- Sys.getenv("PG_USER")
pgPassword <- Sys.getenv("PG_PASSWORD")
tableName <- Sys.getenv("PG_TABLE_NAME")
It is important to remind here, that the function Sys.getenv()
does not source all environment variables from the runtime. Despite common expectations, running an R app inside a container with the environment variables set in its runtime, R will not recognise them.
To make R see and recognise container runtime environment variables, they need to be added to .Renviron
file, located in /home/.Renviron
.
You can add it to the Dockerfile as a RUN command:
RUN RIMPORT=$(env) && echo $RIMPORT > /home/.Renviron
Dockerfile
It is crucial to sort out the correct ports when writing Dockerfile and deploying an Rshiny app to Azure.
1. Explicitly specify on which port and host the Shiny server should be exposed:
RUN echo "local(options(shiny.port = 3838, shiny.host = '0.0.0.0'))" > /usr/local/lib/R/etc/Rprofile.site
2. Navigate Docker to expose the port, which you’ve specified in the previous step (in our example, 3838):
EXPOSE 3838
The complete Dockerfile is presented below:
FROM rocker/shiny-verse:latest # the best option when using RShiny and tidyverse packages
RUN apt-get update && apt-get install -y --no-install-recommends \
sudo \
libcurl4-gnutls-dev \
libcairo2-dev \
libxt-dev \
libssl-dev \
libssh2-1-dev \
&& rm -rf /var/lib/apt/lists/*
RUN Rscript -e 'install.packages(c("RPostgres"))' # different R base images have different R tools pre-configured, such as Rscript.
RUN echo "local(options(shiny.port = 3838, shiny.host = '0.0.0.0'))" > /usr/local/lib/R/etc/Rprofile.site # Making RShiny exposing itself on port 3838, specifying the host is also important for a Web App.
RUN addgroup --system app \
&& adduser --system --ingroup app app
RUN RIMPORT=$(env) && echo $RIMPORT > /home/.Renviron # fetching the environment variables from container environment and uploading them to R
RUN mkdir /root/runApp
COPY app /root/app
WORKDIR /home/app
EXPOSE 3838
CMD ["R", "-e", "shiny::runApp('/root/app')"]
Pushing image to Azure
The images can be securely stored and managed in the Cloud, using the Azure Container Registry service (further ACR). Azure provides detailed documentation on how to create an instance of ACR (which is highly recommended at that point of the tutorial):
- https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-azure-cli — using the
az cli
, - https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-portal — using the Azure Portal,
- https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-docker-cli?tabs=azure-cli — good tutorial on pushing and pulling the images.
Once the Dockerfile is ready, run the docker build
command:
docker build -t yourcontainerregistry.azurecr.io/r-shiny-app:1.0.0 .
Your docker image is ready to be pushed to ACR. However, the login is required first:
az login
az acr login -n yourcontainerregistry.azurecr.io
Now the image is ready to be pushed to the registry:
docker push yourcontainerregistry.azurecr.io/r-shiny-app:1.0.0
Azure App Service
To deploy an application to Azure App Service, an App Service Plan has to be created first. You can find the how-to guide from Microsoft here: https://docs.microsoft.com/en-us/azure/app-service/overview-hosting-plans.
Next, proceed to App Service creation. When creating it from the portal, make sure to specify the following parameters (as described in the previous steps):
- Basics -> Instance Details -> Publish -> Select “Docker Container”, since the image has been pushed to ACR in the previous step
- Basics -> App Service Plan -> select the one, created previously
- Docker -> Options -> Single Container
- Docker -> Image Source -> Azure Container Registry and select the image and its version
- Docker -> ACR Options -> Registry -> Insert the registry name here
If you’d like to have a better overview of your app’s performance, enable Application Insights in the consecutive Settings screen. When the setup is complete, press “Review + create”. However, it is important to note at this point, that the Application Insights may incur higher costs.
Detailed App Service documentation can be found here: https://docs.microsoft.com/en-us/azure/app-service/overview-hosting-plans.
Once the Web App is deployed, several environment variables are still to be configured. This can easily be done through the UI by selecting “Configuration” on the left menu blade, and adding a new Application Setting (equivalent to environment variable). Click “Save” when all environment variables are added and restart the App.
To finalise the deployment process, an Azure App Service-specific environment variable has to be set.
- Set
WEBSITES_PORT
environment variable to the port, on which the application is exposed. Add the following setting:
WEBSITES_PORT=3838
More details on pre-defined Azure settings can be found here: https://docs.microsoft.com/en-us/azure/app-service/configure-custom-container?pivots=container-linux.
2. Finall, add the settings, corresponding to environment variables required to build a database connection string (which are sensitive):
PG_SERVER_NAME = <your-db-servername>
DB_NAME = <your-db-name>
PG_USER = <admin-user>
PG_PASSWORD = <password>
PG_TABLE_NAME = <your-table-name>
The app is up and running now!