Deploying an R Shiny app on Heroku free tier

[This article was first published on Stories by Tim M. Schendzielorz on Medium, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Continuous Deployment made easy with GitHub Actions and Heroku

This article is a short guide on deploying a Shiny app on Heroku. Familiarity with Docker, Shiny and GitHub is presumed. For an introduction to Docker, see Deploying a Shiny Flexdashboard with Docker.

This article was also published on https://www.r-bloggers.com/.

This article will show you

  • What Heroku is and what you can get for free
  • How to containerize a Shiny app with Docker
  • How to set up GitHub to automatically deploy to Heroku
  • How to set up a custom domain name for your deployed app

Heroku free tier

Heroku (which I am not affiliated with) is a cloud platform service that offers an intuitive way to deploy and manage enterprise grade web apps. It enables easy auto scaling of your apps, however not on Free (and Hobby) tier we will use here. Apps can be managed via CLI or the Heroku dashboard.

Heroku has a plethora of addons, e.g. for logging or messaging. Additionally it provides services you can co-deploy with your apps like databases with just a few clicks or lines in the CLI or in a deploy instruction file, the `heroku.yml`.

With the new heroku.yml file it is possible to deploy multi container apps. Here however we will not use a heroku.yml as the deployment will be managed by GitHub Actions.

Heroku apps run in “dynos” which are containers running on AWS. With the free tier you can run apps in max 5 dynos (100 if you verify) with 512 MB memory each and get 550 (1000 if you verify) dyno hours. On free tier your apps go to “sleep mode” after 30 min and need to be woken up, which usually means your load time on the first request will be 10–30s longer. This behavior saves your free dyno hours. If you want to circumvent this, add a GET Request to your app so that the app will call itself in intervals < 30 min. We`ll see how long this will be possible.

What I like about the Heroku pricing model is that it is per app. That means you can upgrade a single app after testing it on free tier. On paid tiers you get various benefits, like auto scaling, unlimited dyno hours and SSL encryption with custom domains.

Shiny app Docker image

Heroku dynos support a few languages out of the box like Python and GO with provided buildpacks. For R we have to use a Docker container to run in the dyno. Furthermore putting your Shiny app in a Docker container has various advantages such as properly managing dependencies and good portability across systems.

In this example an R Shiny app that provides batch geo-coding of addresses to longitude and latitude is deployed. In the repo you can find and clone the Shiny app, Dockerfile and GitHub Actions YAML: https://github.com/timosch29/geocoding_shiny

For the deployment on Heroku you have to modify the Dockerfile instructions a bit.

# Base image https://hub.docker.com/u/rocker/                       FROM rocker/shiny-verse:4.0.3
LABEL author="Tim M.Schendzielorz [email protected]"
# system libraries of general use                       
# install debian packages                       
RUN apt-get update -qq && apt-get -y --no-install-recommends install \ 
    libxml2-dev \
    libcairo2-dev \
    libpq-dev \ 
    libssh2-1-dev \
    libcurl4-openssl-dev \
    libssl-dev
# update system libraries
RUN apt-get update && \ 
    apt-get upgrade -y && \  
    apt-get clean
# copy necessary files from app folder
# Shiny app 
COPY /shiny_geocode ./app                       
# renv.lock file
COPY /renv.lock ./renv.lock
# install renv & restore packages                       
RUN Rscript -e 'install.packages("renv")'
RUN Rscript -e 'renv::restore()'
# remove install files                       
RUN rm -rf /var/lib/apt/lists/*
# make all app files readable, gives rwe permisssion (solves issue when dev in Windows, but building in Ubuntu)                       RUN chmod -R 755 /app
# expose port (for local deployment only)                       EXPOSE 3838
# set non-root                       
RUN useradd shiny_user
USER shiny_user
# run app on container start (use heroku port variable for deployment)
CMD ["R", "-e", "shiny::runApp('/app', host = '0.0.0.0', port = as.numeric(Sys.getenv('PORT')))"]

In this Dockerfile, renv is used to install the necessary R libraries from a renv.lock file via `RUN Rscript -e ‘renv::restore()’` to have the same libray versions in the container as in the local dev environment.

To run the containerized app on Heroku, two things are necessary. First, the container must run as non-root for security reasons. Make a new user via RUN useradd shiny_user and set via USER shiny_user .

Second, Heroku provides you with a random Port via the PORT host variable for each dyno. To run the Shiny app at this port, use port = as.numeric(Sys.getenv('PORT')) in the runApp command.

Continuous Deployment with GitHub Actions

GitHub Actions provides an straightforward template and instructions from https://github.com/AkhileshNS/heroku-deploy to deploy to Heroku. In the top level include a directory .github/workflows with the following main.yml file in your GitHub repo:

name: heroku_deploy
    on:                         
        push:                           
            branches:
                - master
jobs:                        
    build:                          
    runs-on: ubuntu-latest                        
       steps:
           - uses: actions/checkout@v2        
           - uses: akhileshns/[email protected]
           with: 
               heroku_api_key: ${{secrets.HEROKU_API_KEY}}  
               heroku_app_name: "geocode-shiny"                                 
               heroku_email: ${{secrets.HEROKU_EMAIL}}                                   
               healthcheck: "https://geocode-shiny.herokuapp.com/"                                     
               usedocker: true                                  
               delay: 60                                 
               rollbackonhealthcheckfailed: true
           env: 
              HD_API_KEY: ${{secrets.MAPS_API_KEY}} # Docker env var

Here, we specify the action “heroku_deploy” to happen on a push to the master branch. In the steps, the commit which triggered the action is checked out to be accessed by the workflow and in the next step it is pushed to Heroku, build and deployed.

The parameters heroku_api_key, heroku_app_name and heroku_email are needed. To get them

  1. Make an Heroku account on the website.
  2. Go to your Heroku dashboard or download the CLI tool.
  3. Create a new app with a unique name in the dashboard or via heroku create your_app_name .
  4. Get an API key from your Heroku Account Settings or via heroku auth:token .
  5. Store the two variables for your Heroku account in your GitHub repo Settings->Secrets as HEROKU_API_KEY and HEROKU_EMAIL.

The parameter usedocker: true is needed for deployment of a Docker container. Additionally we use the url (which you will know after the first successful deploy) with healthcheck: true . The healthcheck is delayed for 60s with delay: 60 and the deploy is rolled back to the previous commit when the health check of the app fails via rollbackonhealthcheckfailed: true.

This Shiny app needs a secret API key for an external API to work. It is saved as GitHub secret too and supplied as a Docker environment variable. To set env variables for your apps, prefix them with HD_. This gets stripped off in deployment and is necessary to distinguish between build and deploy variables. DO NOT put any of your secrets directly in files in GitHub repos!

That’s it! Push to the master branch (or any other, you could also use tags to specify commits for deployment in the main.yml ) and check the GitHub Actions tab if the deployment worked. Then get the URL from which your app can be reached in the dashboard or via heroku apps:info -a your_app_name . Add this url to the healthcheck in the main.yml for future deployment versions.

Set up a custom URL for the app

To set up a custom domain for your Heroku app which you own/have access to, you need to:

  1. Verify your Heroku account with a credit card. You will not occur any charges for apps on free tier and additionally would need to opt in for payed service. If you verify, you get more dynos and free dyno hours, too.
  2. Add your domain to the app via dashboard or via the CLI tool with heroku domains:add www.example.com -a your_app_name .
  3. Go to your Domain provider and add a new CNAME record for www.example.com to point to the Heroku DNS target you get via the dashboard or heroku domains -a your_app_name.
  4. Check that the DNS is correctly configured via host www.example.com .

Deploying an R Shiny app on Heroku free tier was originally published in Analytics Vidhya on Medium, where people are continuing the conversation by highlighting and responding to this story.

To leave a comment for the author, please follow the link and comment on their blog: Stories by Tim M. Schendzielorz on Medium.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)