Last month I was at the lovely EdinbR meetup to present my guide to self-hosting the amazing Shiny server. Some kind folks have been asking for the notes so that they can do it themselves, which seems fair, so here it is!

The Why

Let’s start with my slides before we get to the how-to. There are fantastic resources for hosting Shiny out there, so why would you roll your own?

Option 1: ShinyApps

For those wanting something easy and free / low-cost, then there’s ShinyApps which has a free tier, and then scaling costs if you want more. It’s a great way to try Shiny out, and I encourage those taking their first steps to try it! However, it has two drawbacks that may matter.

Firstly, there’s time. ShinyApps has a limited number of of “active hours” per month, and at the free tier it’s only 15, or ~1hr/day. That’s perfect for testing, but not if you are building an app for a team to use. Even at $40/month you only get 500 hours, so if your app is in constant use, that’s 16hrs/day - fine if your team is in one timezone, but mine isn’t :)

Second is privacy - by definition ShinyApps is public. You can protect it with a password, but that’s not the same as being on the company intranet. If you have sensitive data, that’s not going to fly.

Option 2 - RSConnect

If ShinyApps doesn’t suit, perhaps RSConnect will? It’s an amazing resource for teams, and it’s on-premise, so no privacy concerns or time constraints! Slight downside though, it’s $15,000/year! Unless you qualify for discounts, of course, but I’m fairly sure we don’t :)

Option 3 - Self-host

However if, like me, you are reasonably happy with the command line, and have $5/month for a basic VM, you can have 100% uptime Shiny for just a little effort - it’s the usual cost/effort/time triangle. Disclaimer: a $5 / month VM isn’t going to have the resource to handle many users, but it will always be available. Scaling might be a future post :P

The HowTo

OK, let’s get to it. We’re going to cover:

  • Basic install of Shiny
  • Useful configuration options
  • Apache frontend
  • SSL certs
  • Deploying apps

I’m also going to assume a few things:

  • Basic understanding of system administration
  • Ubuntu VM (CentOS/Debian/Fedora etc should all work in similar ways)
    • I’m using a lowest-tier DigitalOcean VM
  • Shell access (no shared CPanel stuff)
  • SSH access
  • x86 chip (Shiny isn’t packaged for ARM, although you can run it from source)

Basic install of Shiny

Starting from our shell on our clean VM, we just need to follow the Shiny server admin guide. Get a beverage, this takes about 20 min!

# Get root
sudo -i
# Update package lists
apt-get update
# Install R itself, ~242 packages
apt-get install r-base
# Install the Shiny runtime - ~17 packages
R -e "install.packages('shiny', repos='https://cran.rstudio.com/')"
# Install RMarkdown so the demo app works - ~12 packages
R -e "install.packages('rmarkdown', repos='https://cran.rstudio.com/')"

# Install Shiny Server
cd /tmp
apt-get install gdebi-core
# Go to https://rstudio.com/products/shiny/download-server/ for the current DEB
wget https://download3.rstudio.org/ubuntu-14.04/x86_64/shiny-server-1.5.12.933-amd64.deb
gdebi /tmp/shiny-server-1.5.12.933-amd64.deb

That’s it! If we check the process list, we can see the server is running, and it’s port is listed in the network stats:

# pgrep shiny -alf
19228 /opt/shiny-server/ext/node/bin/shiny-server /opt/shiny-server/lib/main.js

# ss -lntp | grep 3838
LISTEN     0      128         :::3838                    :::*                  

Finally, we can hit http://YOUR-IP:3838 and Shiny should load with a couple of example apps. Success!

Useful configuration options

OK, so far all we’ve is Read The Fine Manual, not too impressive. Certainly not worth a blog post. Let’s go further - now we’ll add some of the more useful configuration options. Shiny Server has many options that might be useful - some are only available in the paid-for version however.

We’ll add two options available in the free version - apps running from user’s home directories (good for dev work), and a directory of “production” apps that are ready to go.

Here’s the diff on a “default” Shiny install:

--- opt/shiny-server/config/default.config	2019-09-09 17:52:06.000000000 +0000
+++ /etc/shiny-server/shiny-server.conf	2018-11-09 10:18:53.920680178 +0000
@@ -1,21 +1,39 @@
-# Instruct Shiny Server to run applications as the user "shiny"
-run_as shiny;
+# Tell Shiny Server that we want to run as the user whose 
+# home directory we find the application in.
+run_as :HOME_USER: shiny;
 
 # Define a server that listens on port 3838
 server {
   listen 3838;
 
   # Define a location at the base URL
+  location /~ {
+         
+  # Allow users to host their own apps in ~/ShinyApps
+    user_dirs;
+  
+  # Optionally, you can restrict the privilege of hosting Shiny applications
+  # only to members of a particular Linux group.
+  # members_of shinyUsers;
+  }
+
+  location /apps {
+    site_dir /srv/shiny-server/apps/;
+    log_dir /var/log/shiny-server/apps;
+    directory_index on;
+  }
+
+  # Define a location at the base URL
   location / {
 
     # Host the directory of Shiny Apps stored in this directory
-    site_dir /srv/shiny-server;
+    site_dir /srv/shiny-server/main;
 
     # Log all Shiny output to files in this directory
-    log_dir /var/log/shiny-server;
+    log_dir /var/log/shiny-server/main;
 
     # When a user visits the base URL rather than a particular application,
     # an index of the applications available in this directory will be shown.
-    directory_index on;
+    directory_index off;
   }
 }

Make these changes and then run systemctl restart shiny-server.service to restart Shiny Server.

The run_as and user_dirs additions mean that any user can host Shiny apps in ~/ShinyApps and these are accessible at http://YOUR_IP:3838/~/USER/APP. As a concrete example, let’s copy a working sample app from Shiny itself:

mkdir /home/shiny/ShinyApps
cp -r /srv/shiny-server/sample-apps/hello /home/shiny/ShinyApps
vi ~shiny/ShinyApps/hello/ui.R   # Change a string so you know it's this app

Now navigate to http://YOUR_IP:3838/~/shiny/hello and you should see your app! This is great for “hidden” apps you’re working on (as there’s no directory listing) and for teams to work on things (as anyone with an account can host apps).

The rest of the changes make for a “production” area with a directory listing. Let’s set it up, again using the hello app:

mkdir /srv/shiny-apps/  
cp -r /srv/shiny-server/sample-apps/hello /srv/shiny-apps/
vi /srv/shiny-apps/hello/ui.R    # Make a different change this time

Now if you go to http://YOUR_IP:3838/apps you’ll get a directory listing with hello in it - clicking that will take you to the copy of hello we just created. This is super for the more “official” apps you create, that are for a wider audience, and should be writiable to be just anyone (perhaps, some signoff process is appropriate first :P).

Also notice how neither of these deployments required restarting Shiny Server itself - it’s happy to serve newly-deployed apps as and when they are created, with no downtime to existing apps.

Apache frontend

So far so good, but we can go further. No one can ever remembers what port something runs on, and in any case it’s not wise to expose Shiny to the internet, so let’s put an Apache proxy on it.

(Note, you can use any webserver for this, I’m just most familiar with Apache. The process will be the same for other webservers)

Start by installing Apache (well, yeah, obviously):

apt-get install apache2

Then we need to create a config for our Shiny proxy. Apache numbers files, so we’ll create /etc/apache2/sites-available/25-shiny.conf

<VirtualHost *:80>
  ServerName whatever.you.like.for.now.org

  ## Vhost docroot
  DocumentRoot "/var/www/vhosts/shiny/htdocs"

  ## Directories
  <Directory "/var/www/vhosts/shiny/htdocs">
    Options Indexes FollowSymLinks MultiViews
    AllowOverride None
    Require all granted
  </Directory>

  ## Logging
  ErrorLog "/var/log/apache2/shiny_server-http_error.log"
  ServerSignature Off
  CustomLog "/var/log/apache2/shiny_server-http_access.log" combined 

  ## Proxy rules
  ProxyRequests Off
  ProxyPreserveHost Off
  ProxyPass / http://localhost:3838/ nocanon
  ProxyPassReverse / http://localhost:3838/

</VirtualHost>

The only really interesting part is the Proxy* directives, which tell Apache to proxy traffic on port 80 (first line) to port 3838. Let’s enable it:

a2enmod proxy_http
a2dissite 000-default
a2ensite 25-shiny.conf
systemctl restart apache2

Excellent. If all is well, you should be able to drop the port on any of the earlier test links now - e.g. http://YOUR_IP/apps. Result!

SSL certs

Now that we have Apache, we can also add SSL to our Shiny Server. This is 2019, and there’s really no excuse for not using SSL on your sites - LetsEncrypt makes this trivial.

We’ll start with installing certbot which is the LetsEncrypt commandline tooling:

apt-get install certbot

Now, the part I can’t demonstrate - you’ll need a domain name for your host, which means you need make changes to your DNS (or access to someone who can). At the very least, you need to make an “A” or “AAAA” record for your server’s IP to your chosen hostname. Once done, you can check it with dig:

# dig @8.8.8.8 -t A YOUR_NAME +short
YOUR_IP

Great. Now we’ll get a cert for YOUR_NAME and set up Apache. For brevity, we’ll use the standalone Certbot plugin, which requires us to stop Apache - there are better ways but this post is already huge :P

systemctl stop apache2                                   
certbot certonly -d YOUR_NAME --standalone
a2enmod ssl

Certbot will run it’s own webserver for a moment and ask the LetsEncrypt servers to verify the domain, before writing out a certificate. Now we create the config, in /etc/apache2/sites-available/25-shiny-ssl.conf:

<VirtualHost *:443>
  ServerName YOUR_NAME

  ## Vhost docroot
  DocumentRoot "/var/www/vhosts/shiny/htdocs"

  ## Directories
  <Directory "/var/www/vhosts/shiny/htdocs">
    Options Indexes FollowSymLinks MultiViews
    AllowOverride None
    Require all granted
  </Directory>

  ## Logging
  ErrorLog "/var/log/apache2/shiny_server-https_error_ssl.log"
  ServerSignature Off
  CustomLog "/var/log/apache2/shiny_server-https_access_ssl.log" combined 

  ## Proxy rules
  ProxyRequests Off
  ProxyPreserveHost Off
  ProxyPass / http://localhost:3838/ nocanon
  ProxyPassReverse / http://localhost:3838/

  ## SSL directives
  SSLEngine on
  SSLCertificateFile      "/etc/letsencrypt/live/YOUR_NAME/fullchain.pem"
  SSLCertificateKeyFile   "/etc/letsencrypt/live/YOUR_NAME/privkey.pem"
  SSLCertificateChainFile "/etc/letsencrypt/live/YOUR_NAME/chain.pem"
</VirtualHost>

We also want to redirect traffic from port 80 to port 443 so that we only use HTTPS. Let’s create a new file for that, in /etc/apache2/sites-available/25-shiny-redirect.conf:

<VirtualHost *:80>
  ServerName YOUR_NAME

  ## Vhost docroot
  DocumentRoot "/var/www/vhosts/shiny/htdocs"

  ## Directories
  <Directory "/var/www/vhosts/shiny/htdocs">
    Options Indexes FollowSymLinks MultiViews
    AllowOverride None
    Require all granted
  </Directory>

  ## Logging
  ErrorLog "/var/log/apache2/shiny-server_error.log"
  ServerSignature Off
  CustomLog "/var/log/apache2/shiny-server_access.log" combined 

  ## Redirect rules
  Redirect  / https://YOUR_NAME/

</VirtualHost>

Now we can enable these, and disable the old config that served port 80:

a2ensite 25-shiny-ssl 25-shiny-redirect
a2dissite 25-shiny
apache2ctl configtest    # We've made a lot of changes so check, should return OK
systemctl start apache2

If all has gone well, then going to https://YOUR_NAME/apps should work (and display a ‘green padlock’), and going to http://YOUR_NAME/apps should redirect to the HTTPS version. Perfect.

And now we’re done! Almost…

Deploying apps

In my talk I also covered deploying apps to your self-hosted Shiny Server via rsync and my DeployR package. But this post is huge, so I’ll do a separate post on DeployR soon. Stay tuned!

Conclusion

Give yourself a high-five if that worked, it’s a lot of config. But now you’ve got a self-hosted Shiny Server that is suitable for you and your team to use 247, as well as shell access so you can use cron, the {pins} package, run databases… it’s all yours! Well done!