A lightweight, WebSocket-enabled proxy server for hosting multiple Shiny applications with automatic health monitoring, session management, and resource cleanup.
🎯 Perfect for:
- Hosting multiple Shiny apps behind a single server
- Development environments with multiple projects
- Small-scale production deployments
- R Markdown and Quarto dashboard hosting
# Install from GitHub
remotes::install_github("lab1702/tinyshinyserver")# Clone and install locally
git clone https://github.com/lab1702/tinyshinyserver.git
cd tinyshinyserver
Rscript -e "devtools::install('.')"- R (≥ 4.0)
- Pandoc (for R Markdown apps)
- Quarto CLI (optional, for Quarto dashboards)
The package automatically installs required R dependencies:
Core: shiny, callr, jsonlite, later, httr, digest, httpuv, websocket, openssl Async: future Docs: rmarkdown, quarto Utils: logger
Optional (for examples): DT, plotly, dplyr, flexdashboard
# Load the package
library(tinyshinyserver)# Copy included example apps to your directory
examples_path <- system.file("examples", package = "tinyshinyserver")
file.copy(examples_path, ".", recursive = TRUE)# Start with the example configuration
start_tss(config = "examples/config.json")🌐 Main Interface: http://localhost:3838
⚙️ Management Dashboard: http://localhost:3839
📱 Individual Apps: http://localhost:3838/proxy/{app_name}/
# Package overview and getting started
?tinyshinyserver
# Main function help
?start_tss
# Configuration format reference
?config-format- Multi-App Hosting: Host multiple Shiny applications on different ports behind a single proxy
- Resident & On-Demand Apps: Choose between always-running apps for immediate access or on-demand apps that start when accessed and stop immediately when unused
- WebSocket Support: Full WebSocket proxy with session affinity for real-time Shiny apps
- Real-time Status Updates: Landing page shows live app status and connection counts with auto-refresh
- Management Interface: Professional web-based dashboard for monitoring and controlling all applications
- Real-time Monitoring: Live connection tracking with IP addresses and user agents
- Individual App Control: Restart specific applications without affecting others
- Health Monitoring: Automatic health checks with app restart on failure
- Memory Management: Built-in connection cleanup and resource management
- Startup Reliability: Intelligent port availability checking with retry logic for slow-starting applications
- Graceful Shutdown: Multiple shutdown options including web-based controls
- Dark Mode Support: Automatic theme detection for better user experience
- R Markdown Support: Native support for interactive R Markdown documents with
runtime: shiny - Quarto Support: Full support for interactive Quarto dashboards with
server: shiny - Binary Asset Support: Handles images, fonts, and other binary files correctly
- Comprehensive Logging: Structured logging with per-app log files
The server uses a JSON configuration file. Here's a minimal example:
{
"apps": [
{
"name": "sales",
"path": "./examples/sales",
"resident": true
}
],
"starting_port": 3001,
"proxy_port": 3838,
"proxy_host": "127.0.0.1",
"management_port": 3839,
"log_dir": "./logs"
}📖 See ?config-format for complete configuration reference
Recommended: Use the management interface at http://localhost:3839 and click "Shutdown Server".
Alternative methods:
- Press
Ctrl-Cin the R console - API call:
curl -X POST http://localhost:3839/api/shutdown
✅ Graceful shutdown closes all connections and cleans up resources.
The package includes four example applications:
| App | Type | Description |
|---|---|---|
| sales | Single-file Shiny | Simple dashboard with sample data |
| inventory | Multi-file Shiny | Interactive tables (ui.R + server.R) |
| reports | R Markdown | Flexdashboard with runtime: shiny |
| dashboard | Quarto | Modern dashboard with server: shiny |
Example configuration (from examples/config.json):
{
"apps": [
{
"name": "sales",
"path": "./examples/sales",
"resident": true
},
{
"name": "inventory",
"path": "./examples/inventory"
},
{
"name": "reports",
"path": "./examples/reports",
"resident": false
},
{
"name": "dashboard",
"path": "./examples/dashboard",
"resident": true
}
],
"starting_port": 3001,
"proxy_port": 3838,
"management_port": 3839,
"log_dir": "./logs"
}Apps are automatically assigned ports starting from starting_port:
sales→ port 3001inventory→ port 3002reports→ port 3003dashboard→ port 3004
The system skips reserved ports (proxy_port, management_port) automatically.
The server supports two application life cycle modes controlled by the resident configuration option:
- Always Running: Started when the server starts and keep running continuously
- Immediate Response: No startup delay when users access the app
- Higher Resource Usage: Consumes memory and CPU even when unused
- Best For: Frequently accessed apps, apps with long startup times, production apps requiring immediate availability
- Start on Access: Only started when a user first accesses the app (HTTP request or WebSocket connection)
- Immediate Shutdown: Stopped immediately when the last connection closes
- Resource Efficient: Only consumes resources when actively being used
- Startup Delay: Users may experience a brief delay on first access while the app starts
- Best For: Infrequently used apps, development/testing environments, resource-constrained servers
- Resident apps: Show as "running" (green) or "stopped" (red) in the management interface
- On-demand apps: Show as "dormant" (blue) when stopped normally, "running" (green) when active
Example Configuration:
{
"apps": [
{
"name": "critical-dashboard",
"path": "./apps/dashboard",
"resident": true // Always running for immediate access
},
{
"name": "occasional-report",
"path": "./apps/reports",
"resident": false // Starts only when needed
},
{
"name": "dev-prototype",
"path": "./apps/prototype"
// "resident" defaults to false
}
]
}| Option | Description | Default |
|---|---|---|
apps |
Array of Shiny applications to host | Required |
apps[].name |
Application identifier for URLs | Required |
apps[].path |
Relative path to app directory | Required |
apps[].resident |
Keep app running continuously (true) or start on-demand (false) | false |
starting_port |
Starting port for auto-assignment | Required |
log_dir |
Directory for log files | Required |
proxy_port |
Port for the proxy server | 3838 |
proxy_host |
Host interface for proxy server (localhost, 127.0.0.1, 0.0.0.0, ::1, ::) | "127.0.0.1" |
management_port |
Port for the management interface | 3839 |
restart_delay |
Seconds to wait before restarting failed apps | 5 |
health_check_interval |
Seconds between health checks | 10 |
The proxy_host option controls which network interface the proxy server binds to:
"localhost"or"127.0.0.1"- Binds to localhost only (default, most secure)"0.0.0.0"- Binds to all network interfaces (allows external access)"::1"- IPv6 localhost"::"- All IPv6 interfaces
"0.0.0.0" makes the server accessible from external networks. Only use this if you understand the security implications and have proper firewall rules in place.
For production deployments requiring SSL/TLS and authentication, Caddy Server provides a simple solution:
# Caddyfile
myapp.example.com {
reverse_proxy localhost:3838
basicauth {
username password_hash
}
}
manage.myapp.example.com {
reverse_proxy localhost:3839
basicauth {
admin admin_password_hash
}
}This configuration automatically handles SSL certificates via Let's Encrypt and adds HTTP basic authentication.
Important: When using Caddy, keep proxy_host set to "localhost" or "127.0.0.1" in your config.json to ensure the server only accepts connections from Caddy, not directly from external clients.
The landing page at http://localhost:3838 provides an overview of all hosted applications with real-time status information.
- Live Status Badges: Visual indicators showing if each app is running, dormant, stopped, or crashed
- Connection Counts: Real-time display of active connections per application
- Auto-refresh: Status updates automatically every 5 seconds
- Color-coded Indicators: Green for running, blue for dormant, red for stopped, yellow for crashed apps
- Connection Status: Shows server connectivity with automatic timeout detection
- Smart Clickable Tiles: App accessibility based on status
- Running apps: Immediately clickable ("Click to open [app name]")
- Dormant apps: Clickable for on-demand starting ("Click to start and open [app name]")
- Stopped/Crashed apps: Disabled with helpful tooltips explaining next steps
- Server down: All tiles disabled when server is unreachable
- Visual Feedback: Disabled apps are dimmed with "not-allowed" cursor
- Clean Interface: Modern, responsive design that works on all devices
- Dark Mode Support: Automatically adapts to your system's theme preference
- System Details: Display of R version, platform, and server configuration
- Session Info: Technical details about the running R environment
The landing page uses the same status API as the management interface, ensuring consistency between all monitoring views.
The management interface provides a professional web-based dashboard for monitoring and controlling your Shiny server.
Visit http://localhost:3839 to access the management dashboard.
🔒 Security: The management interface is restricted to localhost only for security.
- Total applications count
- Running applications count
- Active connections count
- Real-time auto-refresh (every 5 seconds)
- Enhanced Status Monitoring: See if each app is running, dormant, stopped, or crashed
- App Type Display: Shows whether apps are "Resident" (always-on) or "On-Demand"
- Connection Counts: View active connections per application
- Process Information: See process IDs and ports for each app
- Smart Restart Controls:
- Running/Stopped/Crashed apps: Show restart button
- Dormant apps: No restart button (start automatically on access)
- Helpful messages: "This app will start automatically when accessed"
- Resource Details: Monitor app paths and configuration
- Real-time Connections: See all active WebSocket connections
- Client Information: IP addresses and user agents
- Session Details: Connection timestamps, duration, and last activity
- Browser Detection: Identify client browsers and operating systems
- Graceful Shutdown: Shutdown the entire server with confirmation dialog
- Safety Features: Multiple confirmation prompts prevent accidental shutdowns
- Clean Termination: Properly closes all connections and terminates all processes
The management interface exposes a REST API for programmatic access:
| Endpoint | Method | Description |
|---|---|---|
/api/status |
GET | System overview (apps, connections) |
/api/apps |
GET | Detailed application status |
/api/connections |
GET | Active connection details |
/api/apps/{name}/restart |
POST | Restart specific application |
/api/shutdown |
POST | Graceful server shutdown |
Example usage:
# Get system status
curl http://localhost:3839/api/status
# Restart the sales app
curl -X POST http://localhost:3839/api/apps/sales/restart
# Shutdown server
curl -X POST http://localhost:3839/api/shutdownThe management interface automatically adapts to your system's theme preference:
- Light Mode: Clean, professional appearance for daytime use
- Dark Mode: Comfortable viewing for low-light environments
- Automatic Detection: Uses CSS
prefers-color-schememedia query
apps/myapp/
├── app.R # Single-file Shiny app
└── [other files]
apps/myapp/
├── ui.R # UI definition
├── server.R # Server logic
└── [other files]
apps/myapp/
├── report.Rmd # R Markdown with runtime: shiny
└── [other files]
apps/myapp/
├── dashboard.qmd # Quarto document with server: shiny
└── [other files]
The project includes four example applications demonstrating different application types:
- Single-file Shiny app demonstrating basic dashboard
- Simple plot with sample sales data
- Multi-file Shiny app (ui.R + server.R)
- Interactive table with dynamic data generation
- R Markdown flexdashboard with
runtime: shiny - Interactive charts, KPIs, and data tables
- Demonstrates plotly integration and responsive design
- Quarto dashboard with
server: shiny - Professional dashboard layout with sidebar controls
- Real-time filtering, interactive plots, and data tables
- Demonstrates modern dashboard design with
format: dashboard
The server includes automatic memory management features:
- Connection Cleanup: Removes stale connections after 30 minutes of inactivity
- Queue Limits: Limits pending message queues to 100 messages per connection
- Process Cleanup: Removes dead process objects from memory
- File Handle Management: Ensures proper cleanup of log file handles
Cleanup runs automatically every 5 minutes and logs activity for monitoring.
logs/server.log- Main server logslogs/{app_name}_output.log- Per-app stdout logslogs/{app_name}_error.log- Per-app stderr logs
INFO- Normal operationsWARN- Warning conditions (e.g., queue limits reached)ERROR- Error conditions requiring attention
For development, you can run apps directly:
# Standard Shiny app
R -e "shiny::runApp('apps/sales', port = 3001)"
# R Markdown app
R -e "rmarkdown::run('apps/reports/report.Rmd', shiny_args = list(port = 3003, host = '127.0.0.1'))"
# Quarto app
R -e "quarto::quarto_serve('apps/dashboard/dashboard.qmd', port = 3004, host = '127.0.0.1')"- Create your app in
apps/{app_name}/ - Add configuration entry to
config.json(only name and path required) - Restart the server
The server will automatically assign the next available port to your new application.
Proxy Server:
- Health check:
GET /health- Returns{"status": "healthy"} - Apps status:
GET /api/apps- Returns detailed application status (used by landing page)
Management Interface:
- System status:
GET /api/status- Returns overall system health - Apps status:
GET /api/apps- Returns detailed application status - Connections:
GET /api/connections- Returns active connection details
tinyshinyserver/
├── R/ # R source code
│ ├── start_tss.R # Main exported function
│ ├── config.R # Configuration management
│ ├── handlers.R # HTTP request routing
│ ├── process_manager.R # Application lifecycle
│ └── ... # Other core modules
├── inst/
│ ├── examples/ # Example apps & config
│ │ ├── sales/ # Simple Shiny app
│ │ ├── inventory/ # Multi-file Shiny app
│ │ ├── reports/ # R Markdown dashboard
│ │ ├── dashboard/ # Quarto dashboard
│ │ └── config.json # Example configuration
│ └── templates/ # HTML templates & CSS
├── man/ # Documentation (.Rd files)
├── DESCRIPTION # Package metadata
└── NAMESPACE # Exported functions
The server uses a multi-process architecture:
┌─────────────────────┐
│ Management Server │
│ (Port 3839) │
│ │
│ • System Status │
│ • App Control │
│ • Connection Info │
│ • Graceful Shutdown│
└─────────────────────┘
┌─────────────────────┐
│ Proxy Server │
│ (Port 3838) │
│ │
│ ┌─WebSocket────────┤
│ │ Handler │
│ │ │
│ │ ┌─HTTP──────────┤
│ │ │ Handler │
└──┼──┼───────────────┘
│ │
│ └── HTTP Requests ──┐
│ │
└── WebSocket ─────────┼───┐
Messages │ │
▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Sales │ │Inventory │ │ Reports │ │Dashboard │
│(Port │ │(Port │ │(Port │ │(Port │
│ 3001) │ │ 3002) │ │ 3003) │ │ 3004) │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
- Management Server: Web-based monitoring and control interface
- HTTP Handler: Routes requests to appropriate backend apps
- WebSocket Handler: Manages real-time connections with session affinity
- Process Manager: Monitors and restarts failed applications
- Memory Manager: Cleans up stale connections and resources
- Connection Tracker: Records client information and session details
Apps won't start
- Check that the app directory exists and contains valid Shiny code
- Verify ports aren't already in use:
netstat -an | findstr :3838 - Check app-specific error logs in
logs/{app_name}_error.log - Use
?start_tssfor configuration help
WebSocket connection failures
- Ensure backend app is running and healthy
- Check for firewall issues blocking WebSocket connections
- Verify session affinity is working correctly
- Monitor logs for WebSocket connection messages
Management interface not accessible
- Verify server is running: check R console output
- Access via http://localhost:3839 (not external IP)
- Ensure no firewall is blocking localhost connections
- Check logs for management server startup messages
# Check server logs (created after starting)
list.files("logs", pattern = "\\.(log|txt)$")
# Monitor main server log
tail -f logs/server.log # Linux/macOS
Get-Content logs/server.log -Wait # PowerShellFor production deployment, consider:
- Adding authentication (see Caddy reverse proxy example)
- SSL/TLS encryption for external access
- Firewall rules and network segmentation
- Regular security updates
- Rate limiting on management endpoints
Built-in security features:
- Management interface restricted to localhost only
- Input validation for configuration files
- Process isolation between applications
Contributions are welcome! Please see our contribution guidelines:
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes and add tests
- Test thoroughly:
devtools::check() - Submit a pull request
# Clone and setup for development
git clone https://github.com/lab1702/tinyshinyserver.git
cd tinyshinyserver
# Install with dependencies
devtools::install_deps()
devtools::load_all()
# Run checks
devtools::check()This project is licensed under the MIT License - see the LICENSE file for details.
- 📖 Documentation: Use
?tinyshinyserver,?start_tss,?config-format - 🐛 Bug Reports: GitHub Issues
Built with ❤️ for the R and Shiny community