Hosting Shiny

Chapter 19 Hosting Platforms

This chapter covers how to run your containerized Shiny application on a hosting platform. A hosting platform enables you to deploy your application without worrying about managing a server.

If you are unfamiliar with containers, please refer to Chapter 15.

Hosting platforms are services that provide the infrastructure and environment needed to deploy, manage, and serve applications over the Internet. They offer a range of solutions to meet different needs, from hosting static sites to running dynamic web applications without worrying about the underlying infrastructure. These platforms typically provide features such as automated deployment, scalability, performance optimization, and security measures.

By abstracting the complexities of server management, hosting platforms allow developers to focus on building and improving their applications. In this section, we explain how to get started on different PaaS’s while highlighting the costs and maintenance/support efforts.

Previously, we mentioned Shiny Shiny-Specific PaaS in Sections 7.2.1 and 9. This section focuses on more general PaaS that rely on the use of containerization technology to deploy Shiny applications.

General PaaS (Platform as a Service) refers to cloud providers that provide a managed solution for deploying applications that are not necessarily Shiny such as javascript web applications. General PaaS are often cheaper due to competition among different providers. Most providers provide a core set of features which includes deployment using command line tools, routing traffic to your application with custom domains, and analytics of your application’s usage.

As General PaaS need to support a multitude of applications, the recommended way to deploy applications on these cloud providers is to containerize your Shiny application. With containerization, all the required dependencies for your Shiny application are installed in a separate container. This allows you to deploy your application on different computers and platforms without worrying about compatibility issues related to dependencies. We outline how to containerize your Shiny application in Chapter 17.

Most of the services presented in this section will require familiarity with the command line, as many of the deployment platforms require deployment to their platform with their own command line tools. We recommend reviewing at least Chapter 3.2 if you need a refresher on using the command line.

In this section, we outline how to deploy containerized Shiny applications for Heroku (Section 19.1), DigitalOcean App Platform (Section 19.2), and Fly.io (Section 19.3). The services that we introduce requires a nominal hosting fee for getting setup, as these platforms manage all the backend infrastructure for you. Our introduction to General PaaS is non-exhaustive, and there are many other platforms that support the deployment of containerized Shiny applications that we mention in Section 19.4.

In each of the sections we outline in a box at the beginning some of the considerations you might make before choosing the hosting platforms including: costs, skill level, support, and scale and performance.

19.1 Heroku

Heroku was one of the first cloud platforms developed for deploying web applications and has been in development since 2007. Originally developed for Ruby on Rails applications, it has evolved to be programming language agnostic through the development of buildpacks for programming languages, and the support of containers for application deployment. Heroku applications run in a collection of lightweight Linux containers called dynos.

A Heroku buildpack is a set of scripts to build application source code for deployment on the Heroku platform. Buildpacks are developed both by the Heroku team and the wider community. However, a drawback to the buildpacks is that not all system dependencies might be available for your Shiny application when deploying on Heroku and will require modifications to a buildpack. Therefore, it is recommended to use a Docker-based stack that uses a containerized version (learn more in Chapter 17) of your application to handle all the system dependencies.

Prerequisites for deploying on Heroku include familiarity with git and the command line (see Chapters 3.2 and 3.3). The Heroku dashboard can also be used to define app deployments.

19.1.1 Deployment from the Command Line

Heroku provides a command line tool for deploying to their platform. To install the Heroku command line, follow the instructions here: https://devcenter.heroku.com/articles/heroku-cli#install-the-heroku-cli. Use heroku --version to test if the CLI is ready to be used. Then type heroku login which will prompt you to type in credentials. Next time the CLI will log you in automatically.

In this step, we assume that you have containerized your Shiny application. That means your Shiny application can be run using specifications in a Dockerfile.

The heroku.yml in your application’s root directory is required to deploy to Heroku. The following example heroku.yml specifies the Dockerfile to be used to build the image for the app’s web process:

build:
  docker:
    web: Dockerfile
run:
  web: uvicorn app:app --host 0.0.0.0 --port $PORT

The build section specifies the build process for the application. The docker key specifies that you are using Docker to build the application. The web key indicates that it is a web process. The Dockerfile specifies the location of the Dockerfile relative to your root directory that is used for building a container image.

The run section indicates how your Shiny application will be run. Under the web key it specifies how to run the web process for your Shiny application. In Python it would be along the lines of:

run:
  web: uvicorn app:app --host 0.0.0.0 --port $PORT

While in R, it would be along the lines of:

run:
  web: R -e "shiny::runApp('/home/app', host = '0.0.0.0', port=as.numeric(Sys.getenv('PORT')))"

To deploy with your Dockerfile, you will have to specify the PORT environment variable and to modify the CMD line of your Dockerfile to take in the PORT environment variable. This is Heroku specifies the a port for the container when deploying your application.

You will now need to initialize a git repository in the root of your Shiny app directory if you haven’t done so already. Only repositories with new commits will deploy once the app is already on Heroku:

# Initialize the local directory as a Git repository
git init -b main

# Add the files and stage them for commit
git add .

# Commit tracked changes and sign-off the message
git commit -s -m "First commit"

Create the Heroku application with the container stack. The command will give you the application URL:

heroku create --stack=container

# Creating app... done, ⬢ morning-plateau-34336, stack is container
# https://morning-plateau-34336.herokuapp.com/ | https://git.heroku.com/morning-plateau-34336.git

A Heroku git remote is also added for the application to track changes. This means that changes will be sent to not just your Git service of choice, i.e. GitHub, but also to Heroku.

You can configure an existing application to use the container stack using heroku stack:set container.

Finally, you can deploy your application to Heroku, replace main with your branch name if it is different. This will trigger a git push and a docker build on a remote Heroku server and docker push to the Heroku container registry:

git push heroku main
# Enumerating objects: 4, done.
# Counting objects: 100% (4/4), done.
# Delta compression using up to 4 threads
# Compressing objects: 100% (3/3), done.
# Writing objects: 100% (4/4), 1.09 KiB | 1.09 MiB/s, done.
# Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
# remote: Compressing source files... done.
# remote: Building source:
# remote: === Fetching app code
# remote:
# remote: === Building web (Dockerfile)
# remote: Sending build context to Docker daemon  3.072kB
# remote: Step 1/3 : FROM registry.gitlab.com/analythium/shinyproxy-hello/hello
# remote: latest: Pulling from analythium/shinyproxy-hello/hello
# remote: 4363cc522034: Pulling fs layer
# ...
# remote: Successfully built 8b891983b438
# remote: Successfully tagged 6dc2e345582f143cd104cf98fec788c65e9a72a6:latest
# remote:
# remote: === Pushing web (Dockerfile)
# remote: Tagged image "6dc2e345582f143cd104cf98fec788c65e9a72a6" as "registry.heroku.com/morning-plateau-34336/web"
# remote: Using default tag: latest
# remote: The push refers to repository [registry.heroku.com/morning-plateau-34336/web]
# remote: cec4817fd20b: Preparing
# ...
# remote: Verifying deploy... done.
# To https://git.heroku.com/morning-plateau-34336.git
#     * [new branch]      main -> main

Open the app in your browser using the heroku open command.

19.1.2 Deployment using the Web Dashboard

Sign up for Heroku and log in. You might have to provide a valid credit card to be able to use the resources. You will also need the heroku command line tool.

Find a Create New App button, click it. You can give the app a name, e.g. heroku-faithful and select a region where the app will be deployed.

Choose the GitHub based deployment. Heroku has a GitHub application that can be used to grant access to your account, so it can pull changes and update the apps accordingly. You’ll need the heroku.yml file in the root of the repository as explained in the previous section. Log in to the heroku account then set the stack to container:

heroku login
# Logging in... done
# Logged in as <your_heroku_email>

heroku stack:set container --app=heroku-faithful
# Setting stack to container... done

Figure 19.1 show the Heroku dashboard where you can connect to the GitHub repository and can enable automatic deploys from a given git branch. You can trigger the deployment manually, this will result in Heroku building the container image and deploying it. Click the Open app button to see the app deployed with a Heroku provided URL, e.g. https://heroku-faithful-45457b65a2b8.herokuapp.com/.

GitHub based deployment in Heroku.

Figure 19.1: GitHub based deployment in Heroku.

You can view the build logs and check the metrics for the app in the dashboard (Fig. 19.2). Application logs are also available after clicking the More button and selecting it from the drop-down menu.

Monitoring metrics in Heroku.

Figure 19.2: Monitoring metrics in Heroku.

19.1.3 Custom Domain and HTTPS

Adding custom domain and setting TLS certificates for HTTPS access can be done in the app dashboard after the app is successfully deployed (Fig. 19.3). Navigate to Settings and configure the SSL Certificate using your own certificate or by selecting Let’s Encrypt for Automated Certificate Management. Next, click the Add domain button. Provide the domain name here and click Next. The next page will reveal the URL target that you can copy, that will look like sheltered-meadowlark-i66adwh7uy89vnhycaw2inzo.herokudns.com. Use this value in your DNS settings to add a CNAME record pointing to this URL.

Managing custom domains and TLS certificates in Heroku.

Figure 19.3: Managing custom domains and TLS certificates in Heroku.

19.1.4 Scaling

We will use the Heroku CLI to scale the app and enable session affinity:

heroku features:enable http-session-affinity --app=heroku-faithful
# Enabling http-session-affinity for ⬢ heroku-faithful... done

Session affinity uses a cookie to maintain information about a user’s session. Once the feature is enabled, the Heroku router will start adding an HTTP cookie named heroku-session-affinity to every new request and response. This cookie contains no private application information. Based on this cookie value, the Heroku router will be able to determine the appropriate dyno for each request.

The least expensive dyno type that we can use with automatic certificate management is the basic (with 500 MB RAM). This, however, does not allow horizontal scaling. Change dyno type to allow scaling to >1 replicas, for this you need at least the Standard-1X (with 500 MB RAM, web=standard-1x) or Standard-2X (1 GB RAM, web=standard-2x) dyno type:

heroku ps:type web=standard-1x --app=heroku-faithful
# Scaling dynos on ⬢ hello-shiny... done
# === Dyno Types
# type  size         qty  cost/hour  max cost/month
# ────  ───────────  ───  ─────────  ──────────────
# web   Standard-1X  1    ~$0.035    $25
# === Dyno Totals
# type         total
# ───────────  ─────
# Standard-1X  1

Scale the number of web dynos to 2 or more:

heroku ps:scale web=2 --app=heroku-faithful
# Scaling dynos... done, now running web at 2:Standard-1X

You can now refresh your browser, and open a new incognito window to see how a new user would connect. Go to the logs in the dashboard (under the More button). You’ll see the web.1 and the web.2 replicas listed. Trying this with the load balancing app should pass the sticky session test. You can see how the app failing if you remove session affinity with heroku features:disable http-session-affinity --app=heroku-faithful and run the tests again.

The dyno type and replication can be changed according to demand. The standard dyno types (1X and 2X) also allow preboot to be turned on (heroku features:enable preboot --app=heroku-faithful). This old instances are not shut down before new instances can receive traffic, this eliminates cold start behavior when deploying new versions of the web app.

You can destroy the app with heroku apps:destroy --confirm=heroku-faithful --app=heroku-faithful.

Multiple Apps in the Same Heroku Instance

One can use the rocker/shiny image that has Shiny Served Open Source included, and add multiple apps under different paths as explained in Section 17.6.6. This way you can host multiple apps using a single instance (dyno).

19.1.5 Summary

Heroku is a popular platform to host apps of any kind at scale without managing infrastructure. Shiny apps are best hosted through Docker images. Heroku can be seen as the containerized version of shinyapps.io. However, the free tier appears to include more apps and free hours. Once on the paid tier, payment applies to each app separately. Upgrade as demand dictates, you can also switch back to the free tier once a predictable surge is over.

19.2 DigitalOcean App Platform

The DigitalOcean App Platform (DOAP) can publish applications from your GitHub repository, or publish a container image you have already uploaded to a Docker registry. The platform supports GitOps (git push-based deployment), and vertical and horizontal scaling.

In spirit and most practical aspects, the DOAP is similar to Heroku in the sense that you can use Buildpacks or Dockerfiles to build applications. There is no native Buildpack for R. But as we explain in Chapter 17, Shiny apps can be containerized. This gives us a way to deploy to DOAP seamlessly.

19.2.1 Prerequisites

You must sign up for a DigitalOcean account and set up the appropriate billing information. After logging in, choose the ‘Apps’ option in the dashboard. After clicking ‘Launch Your App’, you are presented with source options such as: Docker Hub and GitHub. This screen can be seen in Figure 19.4. We will delve into GitHub and Docker Hub in the subsequent sections. Docker Hub allows deployment from a container registry while GitHub allows deployment from a code repository.

DOAP source selection options.

Figure 19.4: DOAP source selection options.

19.2.2 Docker Hub Deployment

After choosing the Docker Hub deployment option, you will need to specify the Docker Hub repository image location similar to the screen in Figure 19.5. To have an image on Docker Hub, you first must build and publish an image as explained in Chapter 16.4.1.

Docker Hub configuration in DOAP.

Figure 19.5: Docker Hub configuration in DOAP.

Once an image has been specified, you will have to define the parameters for running your image. This includes specifying the type of application which is ‘Web Services’ in the case of Shiny, the run command, the port that your application runs on in the container, and any environment variables for configuring your application.

Once the deployment parameters have been defined, you will need to specify the name you want DigitalOcean to identify your application as, as well as the region you want your Shiny app hosted in.

Finally, you must specify a plan, which at a minimum is Basic as you are deploying a non-static application. You can also specify the number of replicates for horizontal scaling.

After clicking launch, you will see the app deployed on a subdomain: app-name-hash.ondigitalocean.app.

Updating the App

You can edit the source image tag in your app’s settings. Once the new version is tagged and pushed to the Docker registry, just edit the tag and the app will be redeployed to the App Platform.

19.2.3 GitHub Deployment

By linking with GitHub, you can enable the automatic deployment of your application to the DigitalOcean App Platform. To get started, you must define in the source repo: /.do/app.yaml. The .do folder contains the app specification (app.yaml) file that defines how the Shiny app should be deployed.

name: app-platform-shiny
services:
- dockerfile_path: Dockerfile
  github:
    repo: h10y/faithful
    branch: main
    deploy_on_push: true
  name: app-platform-shiny

Choose the GitHub deployment option when deploying your application. Follow the prompts and install the GitHub app for your personal account or the organization of your choice. Select ‘All repositories’ or ‘Only select repositories’ as appropriate. Finally, click ‘Save’.

Now go back to the Apps control panel, you will see a screen similar to Figure 19.6. Select the repository from the dropdown menu, specify the branch you wish to deploy. If you want to deploy both a development and a production version of the same repository, you can create multiple apps with the same repo but using different branches.

GitHub repository selection screen in DOAP.

Figure 19.6: GitHub repository selection screen in DOAP.

Leave ‘autodeploy’ checked if you want to trigger new deployments when the code changes, then click ‘Next’. Review the app settings inferred from the Dockerfile, i.e. the HTTP port, etc. Type in the name of the service, select the data region, then click ‘Next’. The final step lets you define the performance of the app and the corresponding pricing.

After a few minutes, you should see the green checks besides the build and deployment log entries. Building the image might take some time, depending on the number of dependencies in your app. Follow the app URL from the control panel to see the app online served over HTTPS.

If you make any changes to your GitHub repository, then a new version of the app will be deployed. One potential issue with this GitHub integration is it does not depend on passing tests before deployment. If you want to test your changes before deployment, here are the steps to follow:

  1. Push changes to the development branch,
  2. Run automated tests for the development branch, e.g. using GitHub actions,
  3. Set up the production branch as a protected branch, so that merging pull requests require passing tests.

19.2.4 Programmatic Deployment

DigitalOcean also offers a command line tool with a YAML specification file for deploying applications to the DigitalOcean App Platform. Once you have deployed an app, you can find the YAML specification file for your deployed app under the Settings tab in apps. Most of the info in the spec file is self-explanatory and relates to all the settings defined when deploying from the browser GUI. To use the app spec from the command line you have to install the doctl command-line tool and follow the steps described in the docs to use the API token to grant doctl access to your DigitalOcean account.

19.2.5 Logs

The dashboard (similar to Figure 19.7) of your deployed app allows you to inspect the deployment and runtime logs under the “Runtime Logs” tab. You can even access the container through the console under the “Console”. Under the “Insights” tab, you can also check CPU and memory consumption.

Dashboard of deployed application in DOAP.

Figure 19.7: Dashboard of deployed application in DOAP.

19.2.6 Custom Domains

You can add a custom domain using a CNAME record with your DNS provider. Go to your app’s settings tab, find the ‘Add Domain’ button. You will be presented with a screen similar to Figure 19.8. Follow the prompts and copy the app’s current URL. Go to your DNS provider and add a CNAME record, and save. After some time the custom domain will be live and listed as ‘Active’ under settings.

Custom domain configuration in DOAP.

Figure 19.8: Custom domain configuration in DOAP.

19.2.7 Summary

DigitalOcean App Platform allows you to deploy containerized applications from a container image hosted in a container registry, through a container image specified in a code repository, or programmatically. It can be an easier to setup alternative to Heroku for hosting containerized applications.

The next chapter will cover another alternative container hosting platform named “Fly.io” which offers the option of multi-region availability for your hosted Shiny application.

19.3 Fly.io

Fly.io is an application hosting platform. The platform is ideal for hosting worldwide apps with low latency. Fly.io enables the launch of containerized applications with the command line.

19.3.1 Prerequisites

First of all, you’ll need Docker installed on your local machine.

This hands-on intro from the Fly documentation is a good place to start to make sure you have everything installed. First, you need to install flyctl, that is the command-line utility that lets you work with Fly. Follow instructions for your operating system of choice.

Sign up for a Fly account. Set up email/password combination or use your GitHub social login. You will also be prompted for credit card payment information, required for charges outside the free tier on Fly (see pricing info, you can run 3 apps for free). Without a credit card, you will not be unable to create a new application on Fly.

Now you can sign in with flyctl:

flyctl auth login

19.3.2 Creating a Fly.io App

We will use the Docker image from the analythium/shinyproxy-hello GitHub project. If you have another Docker image with a Shiny app in it, feel free to use a different DOCKER_IMAGE. Once the image tag is specified, you have to pull the image to your local machine:

export DOCKER_IMAGE="ghcr.io/h10y/faithful/py-shiny:main"
docker pull $DOCKER_IMAGE

With that app pulled, you can go ahead and create a Fly app from the existing Docker image. You’ll be prompted to select the account and the data center region:

flyctl launch --image $DOCKER_IMAGE

# Creating app in /Users/Peter
# Using image ghcr.io/h10y/faithful/py-shiny:main
# ? Select organization: <organization_name>
# ? Select region: sea (Seattle, Washington (US))
# Created app cool-smoke-1972 in organization <organization_name>
# Wrote config file fly.toml

There are quite a few regions to select from, you are offered the one closest to you based on your IP address.

Open and edit the newly created fly.toml (see the Fly docs for detailed config options). Leave all the contents except for the internal_port that you should change from 8080 to 3838 because this is the port exposed in the Docker image:

    ...
    [[services]]
    ...
      internal_port = 3838
    ...

If you are using a different image, make sure you set this to the port that the app is running at.

19.3.3 Deploying to Fly.io

Use the flyctl deploy command to deploy the app:

flyctl deploy

# Deploying cool-smoke-1972
# ==> Validating app configuration
# --> Validating app configuration done
# Services
# TCP 80/443 ⇢ 3838
# Searching for image 'ghcr.io/h10y/faithful/py-shiny:main' locally...
# image found: sha256:b11b4100...
# ==> Pushing image to fly
# The push refers to repository [registry.fly.io/cool-smoke-1972]
# e86054b4a909: Pushed
# ...
# e749bc5a8d9b: Pushed
# deployment-1636607519: digest: sha256:33b633191... size: 3245
# --> Pushing image done
# Image: registry.fly.io/cool-smoke-1972:deployment-1636607519
# Image size: 1.2 GB
# ==> Creating release
# Release v2 created
# 
# You can detach the terminal anytime without stopping the deployment
# Monitoring Deployment
# 
# 1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 passing]
# --> v0 deployed successfully

As a result of the deploy command, the image is pushed into the Fly registry. Watch the process from the command line. If you quit the process, you can still check the app status:

flyctl status

# App
#   Name     = cool-smoke-1972
#   Owner    = <organization_name>
#   Version  = 0
#   Status   = running
#   Hostname = cool-smoke-1972.fly.dev
# 
# Deployment Status
#   ID          = 1c31f05d-b84c-d5da-4613-9679727495e5
#   Version     = v0
#   Status      = successful
#   Description = Deployment completed successfully
#   Instances   = 1 desired, 1 placed, 1 healthy, 0 unhealthy
# 
# Instances
# ID       PROCESS VERSION REGION DESIRED STATUS  HEALTH CHECKS      RESTARTS CREATED
# 00d78cfc app     0       sea    run     running 1 total, 1 passing 0        5m8s ago

Your app is up and running when the status changes to successful. Inspect the container logs with flyctl logs.

The output will give the Hostname (app_name.fly.dev where app_name is the app name from the TOML configuration file). Copy the Hostname and paste in the browser tab, or use flyctl open to visit the app.

19.3.4 Metrics and Logs

If you go to the Fly.io dashboard and click on your app name, you’ll see a menu with the app overview with the status, green means the app is healthy (Fig. 19.9).

The Fly.io dashboard for the app.

Figure 19.9: The Fly.io dashboard for the app.

The Metrics tab shows HTTP response times, concurrency (meaningful only if you scaled the app to more than 1 replica), data transfer speed, memory usage, etc. (Fig. 19.10). You can access logs for up to 2 days. You can also manage TLS certificates here, view activity and settings.

Metrics in the Fly.io dashboard.

Figure 19.10: Metrics in the Fly.io dashboard.

19.3.5 Setting up a Custom Domain

It is recommended to use an A or AAAA record if you set up an apex domain (like example.com). Get the IPv4 and IPv6 addresses and add those to your DNS.

flyctl ips list

# TYPE ADDRESS             REGION CREATED AT
# v4   213.188.220.171     global 5m47s ago
# v6   2a09:8280:1::6:11cc global 5m47s ago

This means creating an A or AAAA record with your domain name provider to point to the IPs listed by the command above. Give a few minutes to the name servers. Once the records are propagated, you should be able to access the app at your custom domain.

19.3.6 Adding TLS/SSL certificate

You might have noticed the crossed lock symbol when loading your Shiny app, indicating that the app is served over HTTP.

Let’s add TLS certificate to the domain name:

export DOMAIN_NAME="your.domain.com" # change domain here

flyctl certs create $DOMAIN_NAME
# Your certificate for your.domain.com is being issued.
# Status is Awaiting certificates.

Wait for a bit, issuing certificates might take a couple of minutes. Once it is done, you should see that a certificate has been issued:

flyctl certs show $DOMAIN_NAME
# The certificate for fly-shiny.analythium.net has been issued.
# Hostname                  = your.domain.com
# DNS Provider              = googledomains
# Certificate Authority     = Let's Encrypt
# Issued                    = ecdsa,rsa
# Added to App              = 24 minutes ago
# Source                    = fly

If you visit your app URL now, the lock symbol looks good, the Shiny app is now served over HTTPS

19.3.7 Autoscaling in Multiple-Regions

Fly.io allows the deployment of your application across data centers located worldwide including: Amsterdam, Atlanta, Paris, Dallas, Parsippany, Frankfurt, Sao Paulo, Hong Kong, Ashburn, Los Angeles, London, Chennai, Tokyo, Chicago, Santiago, Seattle, Singapore, Sunnyvale, Sydney, Toronto, Bathurst.

Fly.io makes sure users are directed to the geographically closest location to ensure performance is as best as can be. Use the flyctl to list the regions for the deployed app. Depending on your location, you will see different regions listed. I deployed the app from Toronto (yyz). If you are in doubt about what the 3-letter codes mean, these are airport codes, so just search for them.

flyctl regions list

# Region Pool:
# yyz
# Backup Region:
# bhs
# ewr

My backup regions are ewr (Parsippany, NJ US) and bhs Bathurst, Australia. If for any reason, the app can’t be deployed in Toronto, Fly will try to spin it up in one of the backup regions. You can build your own region pool. For example, flyctl regions add ord cdg would add Chicago and Paris to the region pool:

flyctl regions add ord cdg

# Region Pool:
# cdg
# ord
# yyz
# Backup Region:
# ams
# bhs
# ewr
# lhr
# vin

Use flyctl regions remove ord to remove ord from the region pool. Once you defined the regions where you want your apps to run, it is time to set how many instances your app will have. But when do you need a new instance and what is an instance in this case?

Instances and concurrency

Fly.io uses a virtualization technology called Firecracker microVMs to run multi-tenant container services. So an instance is a microVM. Think of it as a portion of a shared virtual CPU and 256 MB memory units. These units are scaled up and down.

In your fly.toml configuration file you can find a section about concurrency with the following defaults:

    ...
      [services.concurrency]
        hard_limit = 25
        soft_limit = 20
        type = "connections"
    ...

This concurrency setting determines the microVM capacity. The default is 20 concurrent connections. When a user connects, the request is sent to the nearest microVM with capacity. If the existing VMs are at capacity, Fly launches more in the busiest regions. Idle microVMs are shut off. The room between the soft and hard limits gives time for new instances to be brought to life.

Scale count

The scale count is the number of instances. A scale count of 1 (default) means that 1 instance of the app is running in 1 of the regions in the region pool. Scale count refers to horizontal scaling.

You can increase the scale count with the flyctl scale count command. flyctl scale count 2 tells us to run 2 instances of your application. You can see your current scaling parameters with flyctl scale show:

flyctl scale show
# VM Resources for cool-smoke-1972
#         VM Size: shared-cpu-1x
#       VM Memory: 256 MB
#           Count: 2
#  Max Per Region: Not set

The scaled app is placed in different regions in the region pool. Use the flyctl status command to list instances and which regions they are running in:

flyctl status
# App
#   Name     = cool-smoke-1972
#   Owner    = <organization_name>
#   Version  = 3
#   Status   = running
#   Hostname = cool-smoke-1972.fly.dev
#
# Instances
# ID       PROCESS VERSION REGION DESIRED STATUS  HEALTH CHECKS      RESTARTS CREATED
# 535327bc app     3       yyz    run     running 1 total, 1 passing 0        3m7s ago
# 0c0bd76e app     3       cdg    run     running 1 total, 1 passing 0        4h55m ago

Scaling virtual machines

The physical servers that the microVM instances are running on have 8–32 CPU cores and 32–256 GB of RAM. Multiple virtual machines (VMs) share the same physical box. The number of cores and amount of memory available in the virtual machine can be set for all application instances using the flyctl scale vm command.

flyctl platform vm-sizes
# NAME             CPU CORES MEMORY
# shared-cpu-1x    1         256 MB
# dedicated-cpu-1x 1         2 GB
# dedicated-cpu-2x 2         4 GB
# dedicated-cpu-4x 4         8 GB
# dedicated-cpu-8x 8         16 GB

You can change the type of your VMs by adding the required size name to fly scale vm or using the fly scale memory to directly set the VM’s memory allocation. This type of scaling is referred to as vertical scaling.

Autoscaling

Autoscaling means that the Fly system will create at least the minimum number of application instances across the regions in your region pool. New instances will be created based on traffic up to the maximum count.

By default, autoscaling is disabled and scale count is in effect. There are two types of auto-scaling models: Standard and Balanced. Here is what the Fly docs say:

  • Standard: Instances of the application, up to the minimum count, are evenly distributed among the regions in the pool. They are not relocated in response to traffic. New instances are added where there is demand, up to the maximum count.
  • Balanced: Instances of the application are, at first, evenly distributed among the regions in the pool up to the minimum count. Where traffic is high in a particular region, new instances will be created there and then, when the maximum count of instances has been used, instances will be moved from other regions to that region.

The command flyctl autoscale standard will turn on the Standard autoscaling plan. If you want the Balanced plan with min/max counts of 3 and 5, use flyctl autoscale balanced min=3 max=10. The command flyctl autoscale disable will disable autoscaling.

When you are done, use flyctl destroy $APP_NAME to destroy the deployed application. Don’t forget to remove the A or CNAME record from your DNS settings too.

19.3.8 Summary

Fly.io is a quick and easy way for deploying containerized Shiny app to a PaaS for free. There are some features that set Fly.io apart from other similar platforms. If you need multi-region availability and really flexible scaling for your app, Fly can give that to you at a reasonably low cost and with no ops involved.

19.4 Other Providers

We have outlined the processes for deploying your Shiny application using platforms such as Heroku, DigitalOcean App Platform, and Fly.io in this chapter. These platforms simplify deployment by handling networking and application execution, while also providing analytics to monitor your Shiny application’s performance. The platforms that we have outlined are not comprehensive, and there are several other deployment options you may want to explore:

  • shinyapps.io offers native support for the deployment of Python Shiny and R Shiny applications. It is the only platform to do so. We cover how to use shinyapps.io in Sections 7.2.1 and 9.

  • Posit Connect Cloud natively supports Python and R Shiny applications as explained in Section 9.2.1.

  • Ploomber natively supports deploying R Shiny applications as explored in Section 9.2.2 and also supports R and Python Shiny deployment via Dockerfiles.

  • Hugging Face Spaces offers a service for deploying applications, requiring Shiny apps to be containerized with a Dockerfile. Detailed information on deploying Shiny apps (both R and Python) can be found in their documentation at https://huggingface.co/docs/hub/en/spaces-sdks-docker-shiny.

  • Northflank provides a deployment service similar to Heroku, with the option to build applications using buildpacks or deploy containerized Shiny applications.

  • webapp.io uses a unique specification called a “Layerfile” (similar to Dockerfiles) to deploy applications. Once a Layerfile is committed to a repository, it’s used to deploy your application on the webapp.io platform.

  • porter allows you to deploy applications directly from a GitHub repository or Docker registry, supporting both buildpacks and Dockerfiles for deployment.

  • Coolify simplifies deployment using a format similar to Docker Compose, providing an easy way to deploy your containerized application on their platform.

  • AWS Elastic Beanstalk supports Shiny application deployment using Dockerfiles, providing a scalable cloud environment for your app.

  • Railway allows you to deploy Shiny applications via Dockerfiles, streamlining the process of getting your app online.

  • Koyeb supports both buildpacks and Dockerfiles for deploying Shiny applications, offering flexibility in how you package your app.

  • Render is another platform that supports Shiny application deployment using Dockerfiles, providing a reliable and easy-to-use deployment solution.

  • Qovery allows you to deploy Shiny applications using either buildpacks or Dockerfiles by acting as a middleware between a cloud provider. It has support to create deployments on AWS, GCP, and Scaleway. Qovery can also manage deployments on Kubernetes clusters.

    Almost every platform offers a container deployment option. A possible alternative to a containerized application is buildpacks or nixpacks which define the dependencies needed for your application and are used to build container images of your application. However, buildpacks and nixpacks require configuration that is not covered in this book.

19.5 Summary

We have explained multiple options to host your Shiny application. We have shown how to deploy on Heroku, DigitalOcean App Platform, and Fly.io. We have also provided alternative platforms to deploy to.

A common theme among these platforms is the use of buildpacks and containerization to deploy Shiny applications while varying in different costs, and abilities to deploy from code repositories so you may want to explore them further to find the best fit for your Shiny application (Table 19.1).

Table 19.1 summarizes the providers in the previous Chapter 19.4. The “Platform” column are all the platforms that support hosting Shiny apps. The subsequent columns outline the features of each platform. The “Shiny” column refer to native Shiny application support, while “Container”, “Buildpack”, “Registry”, “Nixpack” denote the platforms support for using container images, building from buildpacks, pulling from container registries, and building from nixpacks.

Table 19.1: Platform-as-a-Service (PaaS) offerings that can be used to host Shiny applications.
Platform Shiny Container Registry Buildpack Nixpack
shinyapps.io R, Python
Posit Connect Cloud R, Python
Ploomber R
Heroku
DigitalOcean App Platform
Fly.io
Hugging Face Spaces
Northflank
webapp.io
porter
Coolify
AWS Elastic Beanstalk
Railway
Koyeb
Render
Qovery