Rodar docker run num app funciona para uma pessoa. Mas e quando cinquenta usuários acessam ao mesmo tempo, cada um precisando de sua própria sessão isolada, com login e sem que um trave o outro? É aqui que entra o ShinyProxy: uma plataforma open-source que dá, a cada usuário, seu próprio contêiner Docker do app, com autenticação, isolamento e escalabilidade — de graça. Este módulo fecha a trilha de deploy, transformando a imagem de implantação com Docker num serviço multiusuário de produção.
Implantar apps Shiny em produção com ShinyProxy: entender seu modelo de um-contêiner-por-sessão, configurar o application.yml (apps, imagens Docker, autenticação), integrar com a imagem golem+Docker dos módulos anteriores, e comparar com as alternativas de deploy (Shiny Server, Posit Connect).
O problema de escala do Shiny
Um processo Shiny é, essencialmente, single-threaded por sessão. Num servidor simples (Shiny Server open-source), todos os usuários compartilham o mesmo processo R por app, então:
- Uma tarefa pesada de um usuário pode degradar a experiência dos outros.
- Não há isolamento: um erro grave ou um vazamento de memória afeta todos.
- Autenticação e gestão de recursos são limitadas.
O promises/future de programação em Shiny ameniza o bloqueio dentro de um processo, mas não dá isolamento real entre usuários. Para isso, a solução arquitetural é dar a cada usuário seu próprio processo isolado — e a forma mais limpa de isolar processos é… um contêiner por usuário. É exatamente o que o ShinyProxy faz.
O modelo do ShinyProxy: um contêiner por sessão
Quando um usuário abre o app pelo ShinyProxy, ele inicia um novo contêiner Docker daquele app só para aquela sessão. Quando o usuário sai, o contêiner é destruído.
Usuário A ──▶ ShinyProxy ──▶ Contêiner Docker A (app isolado)
Usuário B ──▶ ShinyProxy ──▶ Contêiner Docker B (app isolado)
Usuário C ──▶ ShinyProxy ──▶ Contêiner Docker C (app isolado)
As consequências, todas positivas para produção:
- Isolamento total: o crash ou o consumo de memória de um usuário não afeta os demais — cada um está no seu contêiner.
- Escalabilidade: dá para distribuir os contêineres por vários servidores (com Docker Swarm ou Kubernetes).
- Autenticação integrada: LDAP, OpenID Connect, SAML, Keycloak — o login acontece no ShinyProxy, antes do app (a abordagem segura de notificações, validação e segurança).
- Reprodutibilidade: usa a mesma imagem Docker de implantação com Docker, então "funciona no proxy" é garantido por construção.
Pré-requisitos e arquitetura
ShinyProxy é uma aplicação Java que conversa com o Docker (ou Kubernetes) para orquestrar os contêineres. A pilha mínima:
- Docker instalado no servidor (o ShinyProxy chama a API dele).
- Java (a JVM que roda o ShinyProxy) — geralmente via o próprio contêiner do ShinyProxy.
- A imagem Docker do seu app (a que construímos em implantação com Docker), disponível localmente ou num registry.
- Um arquivo
application.ymlque descreve tudo.
O coração da configuração: application.yml
Todo o comportamento do ShinyProxy vive num único YAML. Aqui está um exemplo comentado que serve dois apps:
proxy:
title: Portal de Dashboards
port: 8080 # porta em que o ShinyProxy escuta
# --- AUTENTICAÇÃO (aqui: usuários simples; em produção: LDAP/OIDC) ---
authentication: simple
admin-groups: administradores
users:
- name: ana
password: senha_hash
groups: analistas
- name: chefe
password: senha_hash
groups: administradores
# --- DOCKER: como o proxy fala com o Docker ---
docker:
internal-networking: true
# --- OS APPS: cada 'specs' é um app, ligado a uma imagem Docker ---
specs:
- id: dashboard-vendas
display-name: Dashboard de Vendas
description: Vendas por região e período
container-image: meu-dashboard:1.0 # a imagem construída com Docker!
container-env: # variáveis de ambiente (Aula 5/9)
GOLEM_CONFIG_ACTIVE: production
access-groups: [analistas, administradores] # quem pode ver este app
- id: dashboard-rh
display-name: Dashboard de RH
container-image: dashboard-rh:2.1
access-groups: [administradores] # só admins
# porta interna que o app expõe DENTRO do contêiner (o EXPOSE do Dockerfile)
spring:
application:
name: ShinyProxy
Os campos que você mais ajusta:
container-image— liga o app à imagem Docker construída em implantação com Docker. Esse é o elo entre os dois módulos.access-groups— a autorização, tema de notificações, validação e segurança: quais grupos de usuários enxergam cada app. Autenticação diz quem é;access-groupsdiz o que pode ver.container-env— injeta as variáveis de ambiente (config e segredos) no contêiner de cada sessão, exatamente como o-edodocker run.authentication—simplepara testes; em produção,ldap,openid,samloukeycloak.
A porta interna do app precisa bater
Um ponto que causa muita confusão: o ShinyProxy precisa saber em que porta dentro do contêiner o app escuta. Em implantação com Docker, fixamos shiny.port = 3838 e EXPOSE 3838. O ShinyProxy assume 3838 por padrão para apps Shiny; se você usou outra porta, precisa declará-la:
- id: dashboard-vendas
container-image: meu-dashboard:1.0
port: 3838 # a porta que o app escuta DENTRO do contêiner
A cadeia completa de portas: o usuário acessa a porta do ShinyProxy (8080) → o ShinyProxy encaminha para a porta interna do contêiner da sessão (3838) → dentro do contêiner, o app escuta em 0.0.0.0:3838 (por isso o shiny.host='0.0.0.0' do Dockerfile era essencial). Se qualquer elo dessa cadeia não bater, o app "não carrega".
Rodando o ShinyProxy
O jeito mais limpo é rodar o próprio ShinyProxy como um contêiner Docker, dando a ele acesso ao Docker do host (para que possa criar os contêineres das sessões):
docker run -d \
-p 8080:8080 \
-v /var/run/docker.sock:/var/run/docker.sock \ # acesso ao Docker do host
-v $(pwd)/application.yml:/opt/shinyproxy/application.yml \ # a config
openanalytics/shinyproxy:latest
# Acesse o portal em http://localhost:8080
O volume /var/run/docker.sock é o que permite ao ShinyProxy pedir ao Docker do host que crie e destrua os contêineres de cada sessão. Sem ele, o ShinyProxy não consegue orquestrar nada.
Escalando com Kubernetes
Para produção de larga escala — centenas de usuários, alta disponibilidade — o ShinyProxy roda sobre Kubernetes em vez do Docker de uma única máquina. A troca é praticamente só de configuração: em vez do backend docker, usa-se o backend kubernetes, e o cluster distribui os contêineres de sessão por vários nós, com balanceamento e recuperação automática de falhas.
proxy:
container-backend: kubernetes # em vez de 'docker'
kubernetes:
namespace: shinyproxy
# ... configs do cluster ...
A grande sacada arquitetural: nada muda no seu app. A mesma imagem golem+Docker que roda no seu laptop roda no Kubernetes de produção. Toda a complexidade de escala fica na plataforma (ShinyProxy + K8s), não no código R — que é justamente o objetivo de toda a trilha de engenharia deste curso.
Panorama das opções de deploy
Para fechar, um mapa das alternativas e quando cada uma faz sentido:
| Opção | Custo | Isolamento | Autenticação | Quando usar |
|---|---|---|---|---|
runApp local | grátis | nenhum | nenhuma | desenvolvimento |
| Shiny Server (open source) | grátis | processo compartilhado | nenhuma nativa | uso interno leve |
| ShinyProxy | grátis | contêiner por sessão | LDAP/OIDC/SAML | multiusuário, isolamento, controle total |
| Posit Connect | pago | gerenciado | integrada, robusta | empresas que querem solução gerenciada |
| shinyapps.io | freemium | gerenciado | básica/paga | protótipos e apps pequenos na nuvem |
A escolha equilibra custo, esforço de operação e requisitos. ShinyProxy é o ponto ótimo quando você precisa de isolamento e autenticação sérios sem custo de licença, e tem disposição para operar a infraestrutura. Posit Connect troca dinheiro por conveniência (deploy com um clique, sem gerenciar Docker). Este curso te deixou apto ao caminho mais poderoso e portável — Docker + ShinyProxy — do qual os demais são simplificações.
O Shiny é single-threaded por sessão, e num servidor compartilhado isso significa que um usuário pesado afeta os outros, sem isolamento real. O ShinyProxy resolve dando a cada sessão seu próprio contêiner Docker: isolamento total, escalabilidade, autenticação integrada e reprodutibilidade, já que usa a mesma imagem que o app de produção. É uma aplicação Java que orquestra Docker ou Kubernetes, e precisa de duas coisas: a imagem do app e um application.yml. Nesse arquivo, specs define os apps, container-image liga cada um à sua imagem, access-groups cuida da autorização, container-env injeta configuração e segredos, e authentication integra LDAP, OIDC ou SAML. Um detalhe operacional derruba muita gente: a cadeia de portas precisa bater, do usuário à porta do proxy (8080) e dela à porta interna do contêiner (3838, com shiny.host='0.0.0.0'). O próprio ShinyProxy roda como contêiner, montando /var/run/docker.sock para poder criar e destruir os contêineres de sessão. Para larga escala, troca-se o backend docker por kubernetes sem mudar o app. E o panorama de opções, do mais simples ao mais gerenciado: runApp em desenvolvimento, Shiny Server para uso interno leve, ShinyProxy quando se quer isolamento e autenticação sem custo de licença, Posit Connect como solução gerenciada paga, e shinyapps.io para nuvem simples.
Exercícios
-
Qual a diferença fundamental entre servir um app com Shiny Server (open source) e com ShinyProxy?
✓ Resposta:No Shiny Server open source, todos os usuários de um app compartilham o mesmo processo R (por app), sem isolamento: uma tarefa pesada ou um erro de um usuário pode degradar ou derrubar a experiência dos demais, e não há autenticação nativa robusta. No ShinyProxy, cada sessão de usuário roda em seu próprio contêiner Docker, isolado dos outros — crash, memória e CPU de um usuário não afetam os demais —, com autenticação integrada (LDAP/OIDC/SAML) e escalabilidade horizontal. Em resumo: processo compartilhado vs. contêiner por sessão.
-
No
application.yml, o que faz o campocontainer-imagee por que ele conecta este módulo ao anterior?✓ Resposta:container-imageespecifica qual imagem Docker o ShinyProxy deve instanciar quando um usuário abre aquele app (ex.:meu-dashboard:1.0). Ele conecta diretamente a implantação com Docker porque essa é exatamente a imagem construída lá — o app golem conteinerizado. O ShinyProxy não "conhece" R nem Shiny: ele apenas orquestra contêineres da imagem que você indicou. Por isso a reprodutibilidade do Docker se propaga ao deploy: se a imagem roda comdocker run, roda no ShinyProxy do mesmo jeito. -
Explique a diferença entre
authenticationeaccess-groupsno ShinyProxy, relacionando com os conceitos de notificações, validação e segurança.✓ Resposta:authenticationcuida da autenticação — "quem é você?" — validando as credenciais do usuário (viasimple, LDAP, OpenID, SAML etc.) antes de qualquer app carregar.access-groupscuida da autorização — "o que você pode ver/fazer?" — restringindo quais grupos de usuários têm acesso a cada app (specs). É exatamente a distinção de notificações, validação e segurança: autenticação identifica; autorização controla permissões. No ShinyProxy, um usuário pode se autenticar com sucesso mas não ver um app cujoaccess-groupsnão inclui seu grupo. -
Por que o
shiny.host = '0.0.0.0'configurado no Dockerfile é essencial para o ShinyProxy funcionar?✓ Resposta:Porque o ShinyProxy encaminha as requisições do usuário para o app dentro do contêiner da sessão, através da rede Docker. Se o app escutasse apenas em
127.0.0.1(localhost do contêiner), ele recusaria conexões vindas de fora do próprio processo — inclusive as do ShinyProxy — e o app "não carregaria". Comshiny.host = '0.0.0.0', o app escuta em todas as interfaces do contêiner, aceitando as conexões encaminhadas pelo proxy. É o mesmo motivo pelo qual o-pdodocker runprecisava disso; na cadeia usuário→proxy→contêiner, esse é o elo final. -
Você precisa migrar de um único servidor para um cluster que suporte centenas de usuários com alta disponibilidade. O que muda no ShinyProxy e — crucialmente — o que muda no seu app R?
✓ Resposta:No ShinyProxy, troca-se o backend de contêineres de
dockerparakubernetes(container-backend: kubernetes+ configs do cluster), e o Kubernetes passa a distribuir os contêineres de sessão por vários nós, com balanceamento de carga e recuperação automática de falhas. No app R, não muda nada: a mesma imagem golem+Docker de implantação com Docker é usada. Essa é a grande vantagem arquitetural da trilha — a complexidade de escala vive na plataforma (ShinyProxy + K8s), não no código R, que permanece idêntico do laptop ao cluster de produção.