Deploying an R Shiny app on Heroku free tier

Continuous Deployment made easy with GitHub Actions and Heroku

Image for post
Image for post

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

This article will show you

  • 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 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

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:

The deployed app can be reached at

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

# Base image                       FROM rocker/shiny-verse:4.0.3LABEL author="Tim M.Schendzielorz"# 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 \
# 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 = '', 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

name: heroku_deploy
- master
runs-on: ubuntu-latest
- uses: actions/checkout@v2
- uses: akhileshns/heroku-deploy@v3.6.8
heroku_api_key: ${{secrets.HEROKU_API_KEY}}
heroku_app_name: "geocode-shiny"
heroku_email: ${{secrets.HEROKU_EMAIL}}
healthcheck: ""
usedocker: true
delay: 60
rollbackonhealthcheckfailed: true
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

  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 -a your_app_name .
  3. Go to your Domain provider and add a new CNAME record for 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 .

Analytics Vidhya

Analytics Vidhya is a community of Analytics and Data…

Sign up for Analytics Vidhya News Bytes

By Analytics Vidhya

Latest news from Analytics Vidhya on our Hackathons and some of our best articles! Take a look

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Thanks to Team AV

Tim M. Schendzielorz

Written by

Analytics Vidhya

Analytics Vidhya is a community of Analytics and Data Science professionals. We are building the next-gen data science ecosystem

Tim M. Schendzielorz

Written by

Analytics Vidhya

Analytics Vidhya is a community of Analytics and Data Science professionals. We are building the next-gen data science ecosystem

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store