shinyhub

module
v0.5.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 22, 2026 License: MIT

README

ShinyHub

Self-hosted platform for deploying and managing R Shiny apps. Deploy with a CLI, route traffic through a reverse proxy, log in with OAuth or OIDC, and hibernate idle apps automatically.

Dashboard

Features

  • Deploy from CLI: shinyhub deploy uploads a bundle and brings the app up.
  • Reverse proxy: one URL per app under /app/<slug>/.
  • Hibernation: idle apps are stopped and restarted on demand.
  • Auth: username/password, GitHub OAuth, Google OAuth, or generic OIDC (Okta, Azure AD, Keycloak, Auth0).
  • Access control: public, private, or shared apps; member roles.
  • Per-app env vars & secrets: encrypted at rest with AES-256-GCM.
  • Persistent data dir: each app gets a data/ directory that survives deploys, with shinyhub data push|ls|rm and a UI Data tab.
  • Scheduled jobs + shared data: per-app cron schedules that run as short-lived processes against the bundle, independent of whether the app is serving traffic. One app's data dir can be mounted read-only into another at data/shared/<source-slug>/ to build fetcher → consumer dashboards. See docs/schedules.md.
  • OpenTelemetry tracing: W3C traceparent is propagated through the proxy, every app gets the OTEL_* env vars to export spans to your collector, and a Traces tab surfaces recent slow/failed proxy spans with deep-links into your backend. See docs/tracing.md.
  • Audit log: 27 action types recorded for admin review.
  • Container isolation (optional): run each app inside a Docker container with CPU and memory limits.
  • Per-app replicas: set replicas: N and ShinyHub boots N backends for the app on the same host, sticky-session load-balanced and recovered independently on crash.
  • Fleet reconcile: declare a whole set of apps in one shinyhub-fleet.toml and shinyhub fleet plan / apply converges the server to match, kubectl-apply style, with ownership markers, pruning, and a read-only dashboard surface. See docs/fleet.md.
  • Single binary, SQLite, no external deps.

Quick start

Install from PyPI

ShinyHub is published to PyPI. If you have uv or pip:

uv tool install shinyhub      # installs shinyhub into an isolated tool venv
# or, one-shot without installing:
uvx shinyhub deploy ./my-app --slug demo
# or, with pip:
pip install shinyhub
mkdir -p ./data
cp shinyhub.yaml.example ./shinyhub.yaml
# Edit shinyhub.yaml: set auth.secret to the output of `openssl rand -hex 32`

docker run -d \
  --name shinyhub \
  -p 8080:8080 \
  -v "$PWD/shinyhub.yaml:/etc/shinyhub/shinyhub.yaml:ro" \
  -v "$PWD/data:/data" \
  -e SHINYHUB_ADMIN_USER=admin \
  -e SHINYHUB_ADMIN_PASSWORD=change-me \
  ghcr.io/rvben/shinyhub:latest

The mount target /data matches the example YAML's ./data/... paths (database, bundles, app data dir all land there).

Open http://localhost:8080, log in with the admin credentials you set.

Binary
curl -fsSL https://raw.githubusercontent.com/rvben/shinyhub/main/scripts/install.sh | sh
# Or download from https://github.com/rvben/shinyhub/releases

cp shinyhub.yaml.example shinyhub.yaml
# Set auth.secret to a 32-byte random value.

SHINYHUB_CONFIG=./shinyhub.yaml \
  SHINYHUB_ADMIN_USER=admin SHINYHUB_ADMIN_PASSWORD=change-me \
  shinyhub serve
From source
git clone https://github.com/rvben/shinyhub.git
cd shinyhub
go build -o bin/shinyhub ./cmd/shinyhub

Configuration

See shinyhub.yaml.example — every key is documented inline. Environment variables (prefixed SHINYHUB_) override YAML; see the example file for the full list.

Minimum required:

  • auth.secret — random 32+ character string. Generate with openssl rand -hex 32. The server refuses to start with the placeholder value.

Environment variables & secrets

Every app has its own key-value environment store. Non-secret values are stored plaintext; values marked --secret are encrypted at rest with AES-256-GCM (key derived from SHINYHUB_AUTH_SECRET via HKDF-SHA256) and can never be read back through the API or UI.

CLI
shinyhub env set demo AWS_REGION=eu-west-1
shinyhub env set demo AWS_SECRET_ACCESS_KEY --secret --stdin    # value from stdin
shinyhub env set demo LOG_LEVEL=debug --restart                 # restart the app after setting
shinyhub env ls  demo
shinyhub env rm  demo OLD_VAR

Keys must match [A-Z_][A-Z0-9_]*. Values are capped at 64 KiB each, with at most 100 keys per app.

UI

Open an app's Settings modal and switch to the Environment tab to list, add, edit, and delete variables. Secret values are masked in the list and write-only once created.

Reserved prefix

Keys starting with SHINYHUB_ are reserved for platform variables (SHINYHUB_APP_DATA, future additions) and will be rejected with 422.

Caveat: rotating SHINYHUB_AUTH_SECRET

The encryption key is derived from SHINYHUB_AUTH_SECRET. Rotating that secret invalidates every stored secret; the affected apps will fail to read their secret values until the variables are re-set via the CLI or UI.

When to use env vars vs persistent data
You want to... Use
Configure a cloud bucket URL / DB URL / API endpoint Env var (non-secret)
Pass a password / API key / private key string Env var (secret)
Ship a Parquet / DuckDB / SQLite file the app reads Persistent data dir (see below)
Let the app write uploads / cache / session data Persistent data dir (see below)

Persistent data dir

Every deployed app gets its own directory at <storage.app_data_dir>/<slug>/. The path is exposed to the app process two ways:

  • SHINYHUB_APP_DATA env var (absolute path).
  • ./data/ symlink inside the app's working directory (or a Docker bind mount to /app-data plus a symlink at <workdir>/data when running under runtime.mode: docker).

The data dir survives deploys, restarts, and rollbacks. It is removed only when the app itself is deleted. Recreating an app with the same slug starts with a fresh, empty data dir.

Deploy bundles must not contain a data/ entry — the server rejects bundles where the first segment is a file, directory, or symlink named data (a 422 with the offending path). Push data in separately:

shinyhub data push <slug> ./seed.parquet
shinyhub data push <slug> ./big.csv --dest datasets/2026.csv --restart
shinyhub data ls   <slug>
shinyhub data rm   <slug> stale.csv

The same operations are available from the UI under Settings → Data.

Auth

PUT and DELETE on /api/apps/:slug/data/*path require app manager rights or platform admin / operator.

GET /api/apps/:slug/data requires the app's owner, an explicit member (any role), or a platform admin / operator. Public or shared visibility alone is not enough — file listings can leak business intent (q4-revenue.parquet) and are kept off the public surface even when the app itself is public.

Quota

storage.app_quota_mb caps the combined on-disk footprint of the app's deploy bundles plus the data dir. The check runs on every PUT and is overwrite-aware: replacing a 100 MB file with a 50 MB one always succeeds. Set to 0 to disable.

Concurrent writes

The persistent dir is safe for any number of concurrent readers. For concurrent writers, use a real database (Postgres/MySQL); local SQLite or DuckDB in read-write mode does not survive multi-process writes.

Branding

ShinyHub ships a white-label mode that lets operators customize the front door without touching the core platform. All branding fields are optional. With no branding: block, / and /login serve the built-in catalog and login page unchanged.

YAML config

Add a branding: block to shinyhub.yaml (see shinyhub.yaml.example for the full commented example):

branding:
  site_title: "Example Shiny Platform"
  assets_dir: /etc/shinyhub/assets
  logo: logo.svg               # filename in assets_dir, or an absolute http(s):// URL
  favicon: favicon.ico         # filename in assets_dir, or an absolute http(s):// URL
  theme:
    primary_color: "#0a7d8c"   # CSS hex (#rgb or #rrggbb); sets --brand-primary
  landing_page: landing.html   # filename in assets_dir; replaces the stock catalog at /
  footer_links:
    - { label: "Support",   url: "mailto:support@example.com" }
    - { label: "Community", url: "https://example.com/community" }
Fields
Field Description
site_title Replaces the <title> tag in the SPA shell.
assets_dir Directory that backs all local asset references. Required when any field references a local file.
logo Brand logo: a filename inside assets_dir or an absolute http(s):// URL.
favicon Favicon: a filename inside assets_dir or an absolute http(s):// URL.
theme.primary_color CSS hex color (#rgb or #rrggbb). Injected as the --brand-primary CSS variable.
landing_page Filename inside assets_dir that replaces the stock app catalog at /. /login always serves the SPA shell.
footer_links List of { label, url } objects. URLs accept http, https, mailto, or absolute /path.

assets_dir is validated at startup: the directory must exist and every referenced local file must resolve inside it (symlink-aware containment check).

Environment overrides

Each scalar field can be set or overridden via an environment variable. The footer_links list has no env override and must be set in YAML.

Env var Config field
SHINYHUB_BRANDING_SITE_TITLE branding.site_title
SHINYHUB_BRANDING_ASSETS_DIR branding.assets_dir
SHINYHUB_BRANDING_LOGO branding.logo
SHINYHUB_BRANDING_FAVICON branding.favicon
SHINYHUB_BRANDING_PRIMARY_COLOR branding.theme.primary_color
SHINYHUB_BRANDING_LANDING_PAGE branding.landing_page
Asset serving

Local files registered via logo and favicon are served from an explicit allow-list at /branding/<basename>. The asset handler accepts only a bare basename (no subdirectory segments) and looks it up in the map, so path traversal and symlink tricks are blocked at the handler level.

Operator landing pages should reference these assets with the full /branding/ prefix:

<img src="/branding/logo.svg" alt="Logo">

Relative paths in operator HTML resolve against /, not /branding/, so the prefix must be explicit (or add a <base href="/branding/"> element).

The landing_page file is served directly at / (replacing the stock catalog) and is NOT exposed under /branding/. It is served as trusted same-origin platform HTML. Only trusted operators should author it - it is not sandboxed.

Endpoints
Endpoint Auth Description
GET /.shinyhub/branding.json None (always public) Returns the active branding object, or {} when branding is not configured.
GET /.shinyhub/apps.json Optional Anonymous: public apps only. Admin/operator: all apps. Other authenticated users: apps visible to them (public, shared, owned, or member). Returns minimal {slug, name, visibility} objects. Identity is resolved from the browser session cookie only; callers presenting only an Authorization header are treated as anonymous.

Some reverse proxies block dot-prefixed paths. Ensure requests to /.shinyhub/ pass through to ShinyHub unmodified.

Architecture

Deployment history

┌────────────┐    HTTPS    ┌──────────────────┐
│  Browser   │────────────▶│    ShinyHub      │
└────────────┘             │                  │
                           │  ┌────────────┐  │
┌────────────┐    CLI      │  │  API + UI  │  │
│  shinyhub  │────────────▶│  ├────────────┤  │
│  deploy    │             │  │   Proxy    │──┼──▶  app processes
└────────────┘             │  ├────────────┤  │     (native or Docker)
                           │  │   SQLite   │  │
                           │  └────────────┘  │
                           └──────────────────┘

Components:

  • cmd/shinyhub — single binary: shinyhub serve (HTTP + proxy + lifecycle) and developer subcommands (deploy, login, apps, env, data, …).
  • internal/cli — developer subcommand implementations.
  • internal/api — chi-routed HTTP handlers.
  • internal/process — native or Docker app process lifecycle.
  • internal/proxy — reverse proxy.
  • internal/db — SQLite store.

Status

v0.2.x line — single-node, self-hosted. Used in production by the maintainer. No SLA. Issues and PRs welcome. See CHANGELOG.md for the current release.

Directories

Path Synopsis
cmd
shinyhub command
internal
api
backup
Package backup implements first-class snapshot and restore of all ShinyHub durable state: the SQLite database, deployed app bundles, and per-app data dirs.
Package backup implements first-class snapshot and restore of all ShinyHub durable state: the SQLite database, deployed app bundles, and per-app data dirs.
bundle
Package bundle defines the single source of truth for which files may appear inside a deploy bundle.
Package bundle defines the single source of truth for which files may appear inside a deploy bundle.
cli
data
Package data implements the persistent per-app storage area exposed via `PUT/DELETE/GET /api/apps/:slug/data[/*path]` and the `shinyhub data` CLI.
Package data implements the persistent per-app storage area exposed via `PUT/DELETE/GET /api/apps/:slug/data[/*path]` and the `shinyhub data` CLI.
db
fleet
Package fleet implements the pure, I/O-free core of the fleet reconcile layer: manifest parsing, source-form classification, and the desired-vs- observed diff.
Package fleet implements the pure, I/O-free core of the fleet reconcile layer: manifest parsing, source-form classification, and the desired-vs- observed diff.
logging
Package logging wires a process-wide slog.Logger based on env config.
Package logging wires a process-wide slog.Logger based on env config.
proxytrust
Package proxytrust centralises the trust gate that controls whether X-Forwarded-* request headers are honoured.
Package proxytrust centralises the trust gate that controls whether X-Forwarded-* request headers are honoured.
schedulespec
Package schedulespec defines schedule validation rules shared between the HTTP API and the deploy-manifest application path.
Package schedulespec defines schedule validation rules shared between the HTTP API and the deploy-manifest application path.
secrets
Package secrets encrypts and decrypts small values (env var secrets) using AES-256-GCM with a key derived from the server's auth secret via HKDF-SHA256.
Package secrets encrypts and decrypts small values (env var secrets) using AES-256-GCM with a key derived from the server's auth secret via HKDF-SHA256.
slug
Package slug holds the canonical slug-validation regex shared by the API server, CLI, and any future surface that needs to validate or display the rules.
Package slug holds the canonical slug-validation regex shared by the API server, CLI, and any future surface that needs to validate or display the rules.
storage
Package storage provides on-disk lifecycle helpers shared by the API and CLI.
Package storage provides on-disk lifecycle helpers shared by the API and CLI.
tracing
Package tracing implements lightweight W3C-trace-context propagation through the reverse proxy and a per-app ring buffer of recent slow/error proxy spans for the UI's Traces tab.
Package tracing implements lightweight W3C-trace-context propagation through the reverse proxy and a per-app ring buffer of recent slow/error proxy spans for the UI's Traces tab.
ui

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL