Based on one of our customer needs: If you have created one or more Shiny application and want to put them simple to production for multiple and / or selective users ShinyProxy is my favourite way to deploy these Shiny apps.
Scope
ShinyProxy allows you to run Shiny application at scale and with added authentication, authorization and customer separation.
Shiny allows us to:
- scale to a bunch of concurrent users
- fully decouple the Docker Container instances per End-User
- authentication and authorization (with AWS Cognito, Auth0 or username / password tupels)
- allocate resources (CPU, memory limits) per configured Shiny application
- usage statistics for monitoring and accounting
We setup the ShinyProxy running on a Ubuntu 18.04 LTS machine running docker.
As the right configuration with Nginx, Let’s Encrypt, Docker and ShinyProxy was somehow tricky, we like to share our setup and configuration notes:
- Single Node Docker Host (we are using Ubuntu 18.04)
- Docker Community Editon
- Docker Compose for start and handle multiple containers
- ShinyProxy to managed and handle the Shiny Application Docker Containers.
So let’s start
Basic installation of Docker and Docker Compose
Installation of Docker and Docker Compose Script:
#!/bin/bash
# Installing Docker Community Edition
sudo apt-get remove docker docker-engine docker.io
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=$(([ $(uname -m) = "x86_64" ] && echo "amd64" ) || echo $(uname -m))] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt-get update
sudo apt-get install -y docker-ce
# Managing Docker as a non-root user
sudo usermod -aG docker $USER
# Configuring Docker to start on boot
sudo systemctl enable docker
sudo systemctl start docker
# Install docker-compose
sudo curl -L https://github.com/docker/compose/releases/download/1.23.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
Configuration of Docker to be accessible by tcp on localhost
#!/bin/bash
echo "### Configure docker to be access also by tcp (needed for shiny-proxy) ###"
sudo mkdir -p /etc/systemd/system/docker.service.d/
cat <<EOF > override.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -D -H tcp://127.0.0.1:2375 -H unix://
EOF
sudo cp override.conf /etc/systemd/system/docker.service.d/
sudo systemctl daemon-reload
sudo systemctl restart docker
Docker-Compose
We use docker-compose to spawn multiple docker containers and start als shinyproxy container:
version: "2"
services:
nginx:
restart: always
image: nginx
container_name: nginx
ports:
- "80:80"
- "443:443"
volumes:
- "/etc/nginx/conf.d"
- "/etc/nginx/vhost.d"
- "/usr/share/nginx/html"
- "./volumes/proxy/certs:/etc/nginx/certs:ro"
labels:
- com.centurylinklabs.watchtower.enable=true
nginx-gen:
restart: always
image: jwilder/docker-gen
container_name: nginx-gen
volumes:
- "/var/run/docker.sock:/tmp/docker.sock:ro"
- "./volumes/proxy/templates/nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl:ro"
volumes_from:
- nginx
entrypoint: /usr/local/bin/docker-gen -notify-sighup nginx -watch -wait 5s:30s /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
labels:
- com.centurylinklabs.watchtower.enable=true
letsencrypt-nginx-proxy-companion:
restart: always
image: jrcs/letsencrypt-nginx-proxy-companion
container_name: letsencrypt-nginx-proxy-companion
volumes_from:
- nginx
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./volumes/proxy/certs:/etc/nginx/certs:rw"
environment:
- NGINX_DOCKER_GEN_CONTAINER=nginx-gen
labels:
- com.centurylinklabs.watchtower.enable=true
# Watchtower is a process for watching your Docker containers and automatically
# updating and restarting them whenever their base image is refreshed.
watchtower:
restart: always
image: v2tec/watchtower
container_name: watchtower
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
environment:
- WATCHTOWER_NOTIFICATIONS=slack
- WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL=https://hooks.slack.com/services/<CHANGEME>
# Look for new images every midnight and Removes old images
command:
--label-enable --schedule "0 0 9,15 * * *" --cleanup
labels:
- com.centurylinklabs.watchtower.enable=true
### PRUNUX APPLICATIONS ###
shinyproxy-demo:
restart: always
image: openanalytics/shinyproxy-demo
container_name: shinyproxy-demo
environment:
- VIRTUAL_HOST=shinyproxy-demo.prunux.ch
- VIRTUAL_NETWORK=nginx-proxy
- VIRTUAL_PORT=8080
- LETSENCRYPT_HOST=shinyproxy-demo.prunux.ch
- LETSENCRYPT_EMAIL=not_an_valid_email_address@prunux.ch
labels:
- com.centurylinklabs.watchtower.enable=true
shinyproxy:
restart: always
image: prunux/shinyproxy:0.1
container_name: shinyproxy
ports:
- 18080:18080
networks:
- default
- prunux-shiny-net
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- VIRTUAL_HOST=shiny-apps.prunux.ch
- VIRTUAL_NETWORK=nginx-proxy
- VIRTUAL_PORT=18080
- LETSENCRYPT_HOST=shiny-apps.prunux.ch
- LETSENCRYPT_EMAIL=not_an_valid_email_address@prunux.ch
networks:
prunux-shiny-net:
external:
name: prunux-shiny-net
ShinyProxy Docker Container Configuration and Build
And we build the shinyproxy container with to following settings and script
proxy:
title: prunux.ch Application Entry Page
logo-url: https://www.prunux.ch/images/logo.svg
landing-page: /
heartbeat-rate: 10000
heartbeat-timeout: 60000
port: 18080
authentication: simple
admin-groups: admins
users:
- name: picard
password: SizeTheTime
groups: admins
- name: riker
password: IAmTheNumber1
groups: admins
- name: borg
password: ResistanceIsFutile
- name: quark
password: MoneyMoneyMoney
docker:
internal-networking: true
specs:
- id: 01_hello
display-name: Hello Application
description: Application which demonstrates the basics of a Shiny app
container-cmd: ["R", "-e", "shinyproxy::run_01_hello()"]
container-image: openanalytics/shinyproxy-demo
container-network: prunux-shiny-net
- id: 02_prunuxstat
display-name: Prunux Statistics Application
description: Prunux Statistics Application
container-cmd: ["R", "-e", "shiny::runApp('/root/app_prunux_statistics')"]
container-image: prunux/shinystat:0.22
container-network: prunux-shiny-net
- id: 09_tabsets
container-cmd: ["R", "-e", "shinyproxy::run_06_tabsets()"]
container-image: openanalytics/shinyproxy-demo
container-network: prunux-shiny-net
access-groups: admins
logging:
file:
shinyproxy.log
Script the building of the ShinyProxy Docker Container:
#!/bin/bash
docker build -t prunux/shinyproxy:0.1 .
docker save --output prunux-shinyproxy-0.1.tar prunux/shinyproxy:0.1
rsync -av --compress --progress prunux-shinyproxy-0.1.tar ubuntu@shiny-apps.prunux.ch:/tmp/
Script the import of the ShinyProxy Docker Container:
#!/bin/bash
cat prunux-shinyproxy-0.1.tar | docker import - prunux/shinyproxy:0.1
Finally … it run’s!
Test with your HTTPS URL and debug with the Docker Logs and ShinyProxy Logs (i.e. open https://shiny-apps.prunux.ch in your webbrowser).
Enjoy your unicorn ride of running Shiny Applications with ShinyProxy, Let’s Encrypt, Docker and Docker-Compose!