Host Shiny Apps with Docker

You learned about Shiny, Docker, how to dockerize Shiny apps, and how to manage dependencies. But why dockerize Shiny apps in the first place? Let's see what Shiny and Docker have in common. Shiny "makes it easy to build interactive web apps straight from R", while Docker is the "de facto standard to build and share containerized apps – from desktop, to the cloud". Their intersection is the web and web applications belong to the web.

This post walks you through a very basic setup on your own virtual machine. In the end, you'll have a dockerized setup serving Shiny apps over HTTPS on your own domain, very similar to the one where we used Caddy to securely host apps on Shiny Server. But here you'll see how to achieve the same with Docker.

Server setup

Spin up a virtual machine with Ubuntu Linux 20.04 operating system to be able to follow along with this tutorial. Log in with ssh then set up an A record pointing to the machine's internet protocol (IPv4) address for your custom domain or subdomain. I use test.analythium.net, change that to your domain. Having the domain set up is important for serving the apps over HTTPS: the transport layer security (TLS) certificate requires a custom domain.

Once inside the server, update/upgrade and install Docker & Docker Compose. Enable the Docker service to restart when the system boots. I use the IPv4 address here, but your custom domain should work too:

export HOST="134.122.40.112"
ssh root@$HOST

apt-get -y update
apt-get -y upgrade

apt-get install -y \
    docker \
    docker-compose

systemctl enable docker

Run Docker images

Pull the two docker images that are going to be served:

docker pull registry.gitlab.com/analythium/shinyproxy-hello/hello:latest
docker pull analythium/covidapp-shiny:minimal

Next start the containers on ports 9000 and 9001:

docker run -d \
    --name=hello \
    --restart=always \
    -p 9000:3838 \
    registry.gitlab.com/analythium/shinyproxy-hello/hello:latest

docker run -d \
    --name=covidapp \
    --restart=always \
    -p 9001:3838 \
    analythium/covidapp-shiny:minimal

What is docker run? It is docker create and docker start in one command. According to the Docker docs: "The docker run command first creates a writeable container layer over the specified image, and then starts it using the specified command [the entrypoint]."

In the command, I used the -d flag to run the container in detached mode and gave names to the containers to reference them easily later. I used the restart policy called "always" which will always restart the container regardless of the exit status. The port mapping with the -p flag must be familiar by now. Last, I specified the name of the Docker image that the container is based on.

If you visit http://$HOST:9000 or http://$HOST:9001 you will see the two apps:

Print out the running containers with the docker ps command:

$ docker ps --format 'table {{.Names}}\t{{.Ports}}'

NAMES      PORTS
covidapp   0.0.0.0:9001->3838/tcp
hello      0.0.0.0:9000->3838/tcp

The containers can be stopped (e.g. docker stop hello) which leaves the changes to the container layer intact, i.e. it can be started again with docker start. docker rm removes the containers. Use docker kill $(docker ps -q) to stop all containers, use docker container prune to remove all stopped containers.

Install Caddy server

Caddy is a powerful open-source web server with automatic HTTPS. Follow the Caddy server docs and install the software:

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo apt-key add -
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy

After installation Caddy is already running. You can now visit your domain, test.analythium.net in my case, to see the Caddy welcome page outlining the next steps:

The Caddy serve welcome page

Configure Caddy

Add a directory /var/www/html if it does not exist and write a barebones index.html file:

mkdir -p /var/www/html

cat > /var/www/html/index.html <<EOL
<!DOCTYPE html>
<html>
  <body>
    <h1>Caddy + Docker + Shiny</h1>
    <ul>
      <li><a href="./hello/">Hello app</a></li>
      <li><a href="./covidapp/">COVID-19 app</a></li>
    </ul>
  </body>
</html> 
EOL

Edit the /etc/caddy/Caddyfile file to contain the following configuration:

{
    email your.name@example.com
}

test.analythium.net {
    root * /var/www/html
    handle_path /hello/* {
            reverse_proxy 0.0.0.0:9000
    }
    handle_path /covidapp/* {
            reverse_proxy 0.0.0.0:9001
    }
    file_server
}

The email is added to the global block at the top of the file. Your email address is used when creating an ACME (Automated Certificate Management Environment) account with your Certificate Authority (CA) and is highly recommended in case there are problems with your certificates. Caddy is using Let's Encrypt as CA by default.

The next block starting with the custom domain is called the site block and it defines how we want to reverse proxy the requests to the apps listening on ports 9000 and 9001.

Restart Caddy with systemctl reload caddy for the changes to take effect. Check the logs using journalctl -u caddy --no-pager | less.

There is only one thing left to do: set up the firewall using the uncomplicated firewall (ufw) utility to control incoming traffic:

ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow http
ufw allow https
ufw --force enable

Visit your domain to see the basic HTML page with the two links that will take you to the two apps:

Simple home page

If you are not using your server any longer, don't forget to destroy it at the end. Also, remove the IPv4 address for your custom domain to prevent a possible hostile domain takeover.

Summary

You completed the setup to host your Shiny apps with Docker over HTTPS using your custom domain. This is a good first step but can get out of hand quickly. You have to manage individual containers and edit the Caddy configuration accordingly. Although this is not much different from the way you manage apps with Shiny Server, you might ask: can I do something better? Yes, you can! As you will see in upcoming posts.

Further reading