Production Deployment & Monitoring: Enterprise Shiny Applications

Scalable Deployment with Docker Swarm, ShinyProxy, and Traefik

Master production deployment and monitoring for enterprise Shiny applications using Docker Swarm, ShinyProxy, Traefik load balancing, and comprehensive monitoring solutions for scalable, reliable clinical research platforms.

Tools
Author
Affiliation
Published

May 23, 2025

Modified

June 7, 2025

Keywords

shiny production deployment, enterprise shiny monitoring, scalable shiny deployment, docker swarm shiny, shinyproxy deployment, traefik load balancer

Key Takeaways

Tip
  • Enterprise-Grade Infrastructure: Deploy production-ready Shiny applications using Docker Swarm orchestration with automatic scaling and high availability
  • Professional Load Balancing: Implement Traefik reverse proxy for SSL termination, automatic service discovery, and intelligent traffic routing
  • ShinyProxy Integration: Leverage ShinyProxy for enterprise authentication, multi-tenancy, and containerized application management in clinical environments
  • Comprehensive Monitoring: Establish monitoring and alerting systems that ensure application reliability and support regulatory compliance requirements
  • Production Operations: Master deployment workflows, logging strategies, and maintenance procedures that support 24/7 enterprise operations

Introduction

Production deployment of enterprise Shiny applications requires sophisticated infrastructure that can handle the demanding requirements of clinical research environments: high availability, security, scalability, and regulatory compliance. Unlike development deployments, production systems must support multiple concurrent users, maintain audit trails, and provide the reliability expected in enterprise settings.



This tutorial demonstrates how to deploy our sophisticated Independent Samples t-Test application using an enterprise-grade stack built on Docker Swarm, ShinyProxy, and Traefik. You’ll learn to create a production infrastructure that can scale from departmental use to organization-wide deployment while maintaining the security and compliance standards required for clinical research applications.

By the end of this tutorial, you’ll have a complete production deployment capable of supporting hundreds of concurrent users, with automatic scaling, comprehensive monitoring, and the operational reliability required for mission-critical statistical analysis platforms.

Understanding Enterprise Deployment Architecture

Production Infrastructure Overview

Enterprise Shiny deployment requires a multi-layered architecture that separates concerns while maintaining integration:

flowchart TD
    subgraph "External Access"
        A[Internet/Corporate Network]
        B[SSL Certificate Authority]
    end
    
    subgraph "Load Balancer Layer"
        C[Traefik Reverse Proxy]
        D[SSL Termination]
        E[Service Discovery]
    end
    
    subgraph "Application Layer"
        F[ShinyProxy Manager]
        G[Authentication Service]
        H[Session Management]
    end
    
    subgraph "Container Orchestration"
        I[Docker Swarm Manager]
        J[Worker Node 1]
        K[Worker Node 2]
        L[Worker Node N]
    end
    
    subgraph "Application Containers"
        M[t-Test App Instance 1]
        N[t-Test App Instance 2]
        O[Other Statistical Apps]
    end
    
    subgraph "Data & Storage"
        P[Shared Storage]
        Q[Log Aggregation]
        R[Monitoring Database]
    end
    
    subgraph "Monitoring & Observability"
        S[Prometheus Metrics]
        T[Grafana Dashboards]
        U[Alert Manager]
        V[Log Analysis]
    end
    
    A --> C
    B --> D
    C --> D
    C --> E
    E --> F
    F --> G
    F --> H
    G --> I
    I --> J
    I --> K
    I --> L
    J --> M
    K --> N
    L --> O
    M --> P
    N --> P
    O --> P
    
    I --> Q
    Q --> R
    R --> S
    S --> T
    T --> U
    Q --> V
    
    style A fill:#e1f5fe
    style C fill:#f3e5f5
    style F fill:#e8f5e8
    style I fill:#fff3e0
    style M fill:#fce4ec
    style S fill:#f1f8e9

Why Docker Swarm for Enterprise Deployment

Docker Swarm provides enterprise-grade orchestration with several advantages for Shiny applications:

Enterprise Readiness:

  • Built into Docker Engine (no additional software required)
  • Native load balancing and service discovery
  • Rolling updates with zero downtime
  • Automatic failure recovery and node management

Clinical Research Suitability:

  • Simplified configuration compared to Kubernetes
  • Excellent integration with ShinyProxy
  • Mature ecosystem with proven production deployments
  • Lower operational overhead for small to medium teams

Scalability and Reliability:

  • Automatic container distribution across nodes
  • Health checks and automatic restart capabilities
  • Secrets management for sensitive configuration
  • Integrated networking with overlay networks

Setting Up the Production Environment

Infrastructure Prerequisites

Before deploying our application, let’s establish the infrastructure requirements:

Server Specifications:

# Minimum Production Configuration
Manager Node:
  CPU: 4 cores
  RAM: 8GB
  Storage: 100GB SSD
  Network: 1Gbps
  
Worker Nodes (2-3 minimum):
  CPU: 8 cores  
  RAM: 16GB
  Storage: 200GB SSD
  Network: 1Gbps

# Recommended Production Configuration  
Manager Node:
  CPU: 8 cores
  RAM: 16GB
  Storage: 200GB SSD
  Network: 10Gbps
  
Worker Nodes (3-5):
  CPU: 16 cores
  RAM: 32GB
  Storage: 500GB SSD
  Network: 10Gbps

Network Requirements:

# Required Ports for Docker Swarm
# Manager and Worker Nodes
2377/tcp    # Cluster management communications
7946/tcp    # Communication among nodes  
7946/udp    # Communication among nodes
4789/udp    # Overlay network traffic

# Application Ports
80/tcp      # HTTP traffic (Traefik)
443/tcp     # HTTPS traffic (Traefik)
8080/tcp    # ShinyProxy management (internal)

Docker Swarm Cluster Initialization

Step 1: Initialize Swarm Manager

# On the manager node
sudo docker swarm init --advertise-addr <MANAGER-IP>

# Save the join tokens for workers and additional managers
docker swarm join-token worker
docker swarm join-token manager

Step 2: Add Worker Nodes

# On each worker node (using token from manager)
sudo docker swarm join --token <WORKER-TOKEN> <MANAGER-IP>:2377

# Verify cluster status
docker node ls

Step 3: Create Overlay Networks

# Create networks for different application layers
docker network create --driver overlay --attachable traefik-public
docker network create --driver overlay --attachable shinyproxy-net
docker network create --driver overlay --attachable monitoring-net

# Verify networks
docker network ls

Traefik Load Balancer Configuration

Create a comprehensive Traefik configuration for enterprise load balancing:

# traefik-stack.yml
version: '3.8'

services:
  traefik:
    image: traefik:v2.10
    command:
      # Docker Swarm configuration
      - --providers.docker.swarmmode=true
      - --providers.docker.exposedbydefault=false
      - --providers.docker.network=traefik-public
      
      # Entrypoints
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      
      # SSL/TLS configuration
      - --certificatesresolvers.letsencrypt.acme.tlschallenge=true
      - --certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}
      - --certificatesresolvers.letsencrypt.acme.storage=/certificates/acme.json
      
      # API and Dashboard
      - --api.dashboard=true
      - --api.insecure=false
      
      # Logging
      - --log.level=INFO
      - --accesslog=true
      - --accesslog.filepath=/var/log/traefik/access.log
      - --log.filepath=/var/log/traefik/traefik.log
      
      # Metrics for monitoring
      - --metrics.prometheus=true
      - --metrics.prometheus.addentrypointslabels=true
      - --metrics.prometheus.addserviceslabels=true
      
      # Security headers
      - --providers.file.filename=/etc/traefik/dynamic.yml
      
    ports:
      - "80:80"
      - "443:443"
      
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik-certificates:/certificates
      - traefik-logs:/var/log/traefik
      - ./traefik-dynamic.yml:/etc/traefik/dynamic.yml:ro
      
    networks:
      - traefik-public
      - monitoring-net
      
    deploy:
      placement:
        constraints:
          - node.role == manager
      labels:
        # Dashboard configuration
        - traefik.enable=true
        - traefik.docker.network=traefik-public
        
        # Dashboard routing
        - traefik.http.routers.traefik-dashboard.rule=Host(`traefik.${DOMAIN}`)
        - traefik.http.routers.traefik-dashboard.entrypoints=websecure
        - traefik.http.routers.traefik-dashboard.tls.certresolver=letsencrypt
        - traefik.http.routers.traefik-dashboard.service=api@internal
        
        # Dashboard authentication
        - traefik.http.routers.traefik-dashboard.middlewares=dashboard-auth
        - traefik.http.middlewares.dashboard-auth.basicauth.users=${DASHBOARD_AUTH}
        
        # HTTP to HTTPS redirect
        - traefik.http.routers.traefik-http.rule=Host(`traefik.${DOMAIN}`)
        - traefik.http.routers.traefik-http.entrypoints=web
        - traefik.http.routers.traefik-http.middlewares=https-redirect
        - traefik.http.middlewares.https-redirect.redirectscheme.scheme=https
        - traefik.http.middlewares.https-redirect.redirectscheme.permanent=true

volumes:
  traefik-certificates:
  traefik-logs:

networks:
  traefik-public:
    external: true
  monitoring-net:
    external: true

Dynamic Configuration for Security:

# traefik-dynamic.yml
http:
  middlewares:
    security-headers:
      headers:
        frameDeny: true
        sslRedirect: true
        browserXssFilter: true
        contentTypeNosniff: true
        forceSTSHeader: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 31536000
        customRequestHeaders:
          X-Forwarded-Proto: "https"
        customResponseHeaders:
          X-Frame-Options: "DENY"
          X-Content-Type-Options: "nosniff"
          Referrer-Policy: "strict-origin-when-cross-origin"
          Permissions-Policy: "geolocation=(), microphone=(), camera=()"

    rate-limit:
      rateLimit:
        burst: 100
        average: 50

    clinical-security:
      chain:
        middlewares:
          - security-headers
          - rate-limit

tls:
  options:
    default:
      minVersion: "VersionTLS12"
      cipherSuites:
        - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
        - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305"
        - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"

ShinyProxy Enterprise Configuration

ShinyProxy for Clinical Applications

Configure ShinyProxy for enterprise clinical research environments:

# shinyproxy-application.yml
proxy:
  title: Clinical Statistics Platform
  logo-url: file:///opt/shinyproxy/templates/logo.png
  description: Enterprise Statistical Analysis Platform for Clinical Research
  
  # Authentication configuration
  authentication: openid
  openid:
    auth-url: https://auth.${DOMAIN}/auth/realms/clinical/protocol/openid-connect/auth
    token-url: https://auth.${DOMAIN}/auth/realms/clinical/protocol/openid-connect/token
    jwks-url: https://auth.${DOMAIN}/auth/realms/clinical/protocol/openid-connect/certs
    logout-url: https://auth.${DOMAIN}/auth/realms/clinical/protocol/openid-connect/logout
    client-id: ${OIDC_CLIENT_ID}
    client-secret: ${OIDC_CLIENT_SECRET}
    roles-claim: roles
    
  # Authorization
  admin-groups: [administrators, statistical-leads]
  
  # Container backend configuration
  container-backend: docker-swarm
  
  # Docker Swarm settings
  docker:
    internal-networking: true
    
  # Application specifications
  specs:
    - id: independent-t-test
      display-name: Independent Samples t-Test Calculator
      description: Professional t-test analysis with comprehensive reporting
      container-cmd: ["R", "-e", "IndependentTTest::run_app()"]
      container-image: clinical-stats/independent-t-test:${APP_VERSION}
      container-network: shinyproxy-net
      access-groups: [statisticians, clinical-researchers, administrators]
      
      # Resource limits
      container-memory: "2g"
      container-memory-request: "1g"
      container-cpu-request: "0.5"
      container-cpu-limit: "2.0"
      
      # Environment variables
      container-env:
        CLINICAL_ENV: production
        LOG_LEVEL: INFO
        AUDIT_ENABLED: "true"
        
      # Volume mounts for shared data
      container-volumes:
        - /shared/clinical-data:/app/data:ro
        - /shared/reports:/app/reports:rw
        
      # Port configuration
      port: 3838
      
    - id: sample-size-calculator
      display-name: Clinical Trial Sample Size Calculator
      description: Power analysis and sample size determination
      container-cmd: ["R", "-e", "SampleSizeCalculator::run_app()"]
      container-image: clinical-stats/sample-size-calculator:${APP_VERSION}
      container-network: shinyproxy-net
      access-groups: [statisticians, study-designers, administrators]
      container-memory: "1g"
      container-memory-request: "512m"
      port: 3838

  # Usage statistics for monitoring
  usage-stats-url: http://influxdb:8086/write?db=shinyproxy_usage
  usage-stats-username: ${INFLUXDB_USER}
  usage-stats-password: ${INFLUXDB_PASSWORD}

  # Logging configuration
  logging:
    file:
      name: /var/log/shinyproxy/shinyproxy.log
    level:
      root: INFO
      io.undertow: WARN
      org.springframework.web: INFO

server:
  port: 8080
  servlet:
    session:
      timeout: 3600
      cookie:
        secure: true
        http-only: true
        same-site: strict

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: always

ShinyProxy Docker Stack Configuration

# shinyproxy-stack.yml  
version: '3.8'

services:
  shinyproxy:
    image: openanalytics/shinyproxy:${SHINYPROXY_VERSION}
    environment:
      - SPRING_PROFILES_ACTIVE=production
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./application.yml:/opt/shinyproxy/application.yml:ro
      - ./templates:/opt/shinyproxy/templates:ro
      - shinyproxy-logs:/var/log/shinyproxy
      - shared-data:/shared:rw
      
    networks:
      - traefik-public
      - shinyproxy-net
      - monitoring-net
      
    deploy:
      replicas: 2
      placement:
        constraints:
          - node.role == manager
      update_config:
        parallelism: 1
        delay: 10s
        failure_action: rollback
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        
      labels:
        # Traefik configuration
        - traefik.enable=true
        - traefik.docker.network=traefik-public
        
        # HTTP router
        - traefik.http.routers.shinyproxy.rule=Host(`stats.${DOMAIN}`)
        - traefik.http.routers.shinyproxy.entrypoints=websecure
        - traefik.http.routers.shinyproxy.tls.certresolver=letsencrypt
        - traefik.http.routers.shinyproxy.middlewares=clinical-security
        
        # Service configuration
        - traefik.http.services.shinyproxy.loadbalancer.server.port=8080
        - traefik.http.services.shinyproxy.loadbalancer.sticky.cookie=true
        - traefik.http.services.shinyproxy.loadbalancer.sticky.cookie.name=shinyproxy-session
        
        # Health check
        - traefik.http.services.shinyproxy.loadbalancer.healthcheck.path=/actuator/health
        - traefik.http.services.shinyproxy.loadbalancer.healthcheck.interval=30s

volumes:
  shinyproxy-logs:
  shared-data:

networks:
  traefik-public:
    external: true
  shinyproxy-net:
    external: true
  monitoring-net:
    external: true

Application Containerization Strategy

Production Dockerfile for t-Test Application

Create a production-ready container for our statistical application:

# Production Dockerfile for Independent t-Test Application
FROM rocker/r-ver:4.3.0

# Metadata
LABEL maintainer="Clinical Statistics Team <stats@organization.com>"
LABEL version="1.0.0"
LABEL description="Enterprise Independent Samples t-Test Calculator"

# System dependencies
RUN apt-get update && apt-get install -y \
    libcurl4-openssl-dev \
    libssl-dev \
    libxml2-dev \
    libcairo2-dev \
    libxt-dev \
    libfontconfig1-dev \
    libharfbuzz-dev \
    libfribidi-dev \
    libfreetype6-dev \
    libpng-dev \
    libtiff5-dev \
    libjpeg-dev \
    && rm -rf /var/lib/apt/lists/*

# Create application user for security
RUN groupadd -r shiny && useradd -r -g shiny shiny

# Install R packages
COPY renv.lock /tmp/
RUN R -e "install.packages('renv')"
RUN R -e "renv::restore(lockfile='/tmp/renv.lock')"

# Copy application code
COPY --chown=shiny:shiny . /app
WORKDIR /app

# Install the application package
RUN R -e "devtools::install_local('.', dependencies=FALSE)"

# Create directories for logs and data
RUN mkdir -p /var/log/shiny /app/data /app/reports && \
    chown -R shiny:shiny /var/log/shiny /app/data /app/reports

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:3838/ || exit 1

# Switch to non-root user
USER shiny

# Expose port
EXPOSE 3838

# Set environment variables
ENV SHINY_LOG_STDERR=1
ENV CLINICAL_ENV=production
ENV LOG_LEVEL=INFO

# Start command
CMD ["R", "-e", "IndependentTTest::run_app(host='0.0.0.0', port=3838)"]

Multi-Stage Build for Optimization

# Multi-stage Dockerfile for production optimization
FROM rocker/r-ver:4.3.0 AS builder

# Build dependencies
RUN apt-get update && apt-get install -y \
    libcurl4-openssl-dev \
    libssl-dev \
    libxml2-dev \
    git \
    && rm -rf /var/lib/apt/lists/*

# Install R packages
COPY renv.lock /tmp/
RUN R -e "install.packages('renv')"
RUN R -e "renv::restore(lockfile='/tmp/renv.lock')"

# Build application
COPY . /build
WORKDIR /build
RUN R -e "devtools::build()"

# Production stage
FROM rocker/r-ver:4.3.0

# Runtime dependencies only
RUN apt-get update && apt-get install -y \
    libcurl4-openssl-dev \
    libssl-dev \
    libxml2-dev \
    curl \
    && rm -rf /var/lib/apt/lists/*

# Copy installed packages from builder
COPY --from=builder /usr/local/lib/R/site-library /usr/local/lib/R/site-library

# Copy built application
COPY --from=builder /build/*.tar.gz /tmp/
RUN R -e "install.packages('/tmp/*.tar.gz', repos=NULL, type='source')"

# Application setup
RUN groupadd -r shiny && useradd -r -g shiny shiny
RUN mkdir -p /var/log/shiny /app/data /app/reports && \
    chown -R shiny:shiny /var/log/shiny /app/data /app/reports

HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:3838/ || exit 1

USER shiny
EXPOSE 3838

ENV SHINY_LOG_STDERR=1
ENV CLINICAL_ENV=production

CMD ["R", "-e", "IndependentTTest::run_app(host='0.0.0.0', port=3838)"]

Comprehensive Monitoring Infrastructure

Prometheus Metrics Collection

Deploy Prometheus for metrics collection and monitoring:

# monitoring-stack.yml
version: '3.8'

services:
  prometheus:
    image: prom/prometheus:v2.40.0
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'
      - '--storage.tsdb.retention.time=30d'
      - '--web.enable-lifecycle'
      - '--web.enable-admin-api'
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    networks:
      - monitoring-net
      - traefik-public
    deploy:
      placement:
        constraints:
          - node.role == manager
      labels:
        - traefik.enable=true
        - traefik.docker.network=traefik-public
        - traefik.http.routers.prometheus.rule=Host(`prometheus.${DOMAIN}`)
        - traefik.http.routers.prometheus.entrypoints=websecure
        - traefik.http.routers.prometheus.tls.certresolver=letsencrypt
        - traefik.http.routers.prometheus.middlewares=dashboard-auth
        - traefik.http.services.prometheus.loadbalancer.server.port=9090

  grafana:
    image: grafana/grafana:9.3.0
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
      - GF_SECURITY_ADMIN_USER=admin
      - GF_USERS_ALLOW_SIGN_UP=false
      - GF_INSTALL_PLUGINS=grafana-piechart-panel,grafana-worldmap-panel
    volumes:
      - grafana-data:/var/lib/grafana
      - ./grafana/dashboards:/var/lib/grafana/dashboards:ro
      - ./grafana/provisioning:/etc/grafana/provisioning:ro
    networks:
      - monitoring-net
      - traefik-public
    deploy:
      placement:
        constraints:
          - node.role == manager
      labels:
        - traefik.enable=true
        - traefik.docker.network=traefik-public
        - traefik.http.routers.grafana.rule=Host(`monitoring.${DOMAIN}`)
        - traefik.http.routers.grafana.entrypoints=websecure
        - traefik.http.routers.grafana.tls.certresolver=letsencrypt
        - traefik.http.services.grafana.loadbalancer.server.port=3000

  alertmanager:
    image: prom/alertmanager:v0.25.0
    command:
      - '--config.file=/etc/alertmanager/config.yml'
      - '--storage.path=/alertmanager'
      - '--web.external-url=https://alerts.${DOMAIN}'
    volumes:
      - ./alertmanager.yml:/etc/alertmanager/config.yml:ro
      - alertmanager-data:/alertmanager
    networks:
      - monitoring-net
      - traefik-public
    deploy:
      placement:
        constraints:
          - node.role == manager
      labels:
        - traefik.enable=true
        - traefik.docker.network=traefik-public
        - traefik.http.routers.alertmanager.rule=Host(`alerts.${DOMAIN}`)
        - traefik.http.routers.alertmanager.entrypoints=websecure
        - traefik.http.routers.alertmanager.tls.certresolver=letsencrypt
        - traefik.http.routers.alertmanager.middlewares=dashboard-auth
        - traefik.http.services.alertmanager.loadbalancer.server.port=9093

  node-exporter:
    image: prom/node-exporter:v1.5.0
    command:
      - '--path.rootfs=/host'
    volumes:
      - '/:/host:ro,rslave'
    networks:
      - monitoring-net
    deploy:
      mode: global
      placement:
        constraints:
          - node.platform.os == linux

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:v0.46.0
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
      - /dev/disk/:/dev/disk:ro
    networks:
      - monitoring-net
    deploy:
      mode: global
      placement:
        constraints:
          - node.platform.os == linux

  influxdb:
    image: influxdb:1.8-alpine
    environment:
      - INFLUXDB_DB=shinyproxy_usage
      - INFLUXDB_USER=${INFLUXDB_USER}
      - INFLUXDB_USER_PASSWORD=${INFLUXDB_PASSWORD}
      - INFLUXDB_ADMIN_USER=admin
      - INFLUXDB_ADMIN_PASSWORD=${INFLUXDB_ADMIN_PASSWORD}
    volumes:
      - influxdb-data:/var/lib/influxdb
    networks:
      - monitoring-net
    deploy:
      placement:
        constraints:
          - node.role == manager

volumes:
  prometheus-data:
  grafana-data:
  alertmanager-data:
  influxdb-data:

networks:
  monitoring-net:
    external: true
  traefik-public:
    external: true

Prometheus Configuration

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "alert_rules.yml"

alerting:
  alertmanagers:
    - static_configs:
        - targets:
          - alertmanager:9093

scrape_configs:
  # Prometheus self-monitoring
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  # Traefik metrics
  - job_name: 'traefik'
    static_configs:
      - targets: ['traefik:8080']
    metrics_path: /metrics

  # Node Exporter for system metrics
  - job_name: 'node-exporter'
    dns_sd_configs:
      - names:
          - 'tasks.node-exporter'
        type: 'A'
        port: 9100

  # cAdvisor for container metrics
  - job_name: 'cadvisor'
    dns_sd_configs:
      - names:
          - 'tasks.cadvisor'
        type: 'A'
        port: 8080

  # ShinyProxy application metrics
  - job_name: 'shinyproxy'
    dns_sd_configs:
      - names:
          - 'tasks.shinyproxy'
        type: 'A'
        port: 8080
    metrics_path: /actuator/prometheus
    scrape_interval: 30s

  # Docker Swarm service discovery for dynamic apps
  - job_name: 'docker-swarm'
    dockerswarm_sd_configs:
      - host: unix:///var/run/docker.sock
        role: tasks
        port: 3838
    relabel_configs:
      - source_labels: [__meta_dockerswarm_service_label_app_type]
        regex: 'shiny-app'
        action: keep
      - source_labels: [__meta_dockerswarm_service_name]
        target_label: service_name
      - source_labels: [__meta_dockerswarm_task_container_label_user]
        target_label: user_session

Alert Rules Configuration

# alert_rules.yml
groups:
  - name: clinical_platform_alerts
    rules:
      # High-level service availability
      - alert: ShinyProxyDown
        expr: up{job="shinyproxy"} == 0
        for: 2m
        labels:
          severity: critical
          service: shinyproxy
        annotations:
          summary: "ShinyProxy service is down"
          description: "ShinyProxy has been down for more than 2 minutes on {{ $labels.instance }}"

      - alert: TraefikDown
        expr: up{job="traefik"} == 0
        for: 1m
        labels:
          severity: critical
          service: traefik
        annotations:
          summary: "Traefik load balancer is down"
          description: "Traefik service is unavailable, affecting all application access"

      # Performance and resource alerts
      - alert: HighCPUUsage
        expr: (100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)) > 80
        for: 5m
        labels:
          severity: warning
          service: infrastructure
        annotations:
          summary: "High CPU usage detected"
          description: "CPU usage is above 80% on {{ $labels.instance }} for 5 minutes"

      - alert: HighMemoryUsage
        expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 85
        for: 5m
        labels:
          severity: warning
          service: infrastructure
        annotations:
          summary: "High memory usage detected"
          description: "Memory usage is above 85% on {{ $labels.instance }}"

      - alert: DiskSpaceLow
        expr: (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 < 15
        for: 5m
        labels:
          severity: warning
          service: infrastructure
        annotations:
          summary: "Low disk space"
          description: "Disk space is below 15% on {{ $labels.instance }}"

      # Application-specific alerts
      - alert: ShinyAppHighResponseTime
        expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket{job="shinyproxy"}[5m])) > 5
        for: 3m
        labels:
          severity: warning
          service: shiny_applications
        annotations:
          summary: "High response time for Shiny applications"
          description: "95th percentile response time is above 5 seconds"

      - alert: TooManyActiveUsers
        expr: shinyproxy_active_proxy_count > 50
        for: 2m
        labels:
          severity: warning
          service: capacity
        annotations:
          summary: "High number of active users"
          description: "More than 50 concurrent users - consider scaling"

      - alert: FailedShinyAppStarts
        expr: increase(shinyproxy_app_start_failures_total[5m]) > 3
        for: 1m
        labels:
          severity: warning
          service: shiny_applications
        annotations:
          summary: "Multiple Shiny app start failures"
          description: "{{ $value }} app start failures in the last 5 minutes"

      # Clinical research specific alerts
      - alert: AuditLogWriteFailure
        expr: increase(audit_log_write_failures_total[5m]) > 0
        for: 0m
        labels:
          severity: critical
          service: compliance
        annotations:
          summary: "Audit log write failure"
          description: "Critical: Audit logging has failed - compliance impact"

      - alert: UnauthorizedAccessAttempt
        expr: increase(http_requests_total{status=~"401|403"}[5m]) > 10
        for: 1m
        labels:
          severity: warning
          service: security
        annotations:
          summary: "Multiple unauthorized access attempts"
          description: "{{ $value }} unauthorized access attempts in 5 minutes"

Alertmanager Configuration

# alertmanager.yml
global:
  smtp_smarthost: '${SMTP_HOST}:587'
  smtp_from: 'alerts@${DOMAIN}'
  smtp_auth_username: '${SMTP_USER}'
  smtp_auth_password: '${SMTP_PASSWORD}'

templates:
  - '/etc/alertmanager/templates/*.tmpl'

route:
  group_by: ['alertname', 'service']
  group_wait: 10s
  group_interval: 5m
  repeat_interval: 12h
  receiver: 'clinical-team'
  routes:
    # Critical alerts go to on-call immediately
    - match:
        severity: critical
      receiver: 'critical-alerts'
      group_wait: 0s
      repeat_interval: 5m
      
    # Compliance alerts to regulatory team
    - match:
        service: compliance
      receiver: 'compliance-team'
      group_wait: 0s
      
    # Security alerts to security team
    - match:
        service: security
      receiver: 'security-team'

receivers:
  - name: 'clinical-team'
    email_configs:
      - to: 'clinical-stats@${DOMAIN}'
        subject: '[Clinical Platform] {{ .GroupLabels.alertname }}'
        body: |
          {{ range .Alerts }}
          Alert: {{ .Annotations.summary }}
          Description: {{ .Annotations.description }}
          Severity: {{ .Labels.severity }}
          Service: {{ .Labels.service }}
          Instance: {{ .Labels.instance }}
          {{ end }}

  - name: 'critical-alerts'
    email_configs:
      - to: 'oncall@${DOMAIN}'
        subject: '[CRITICAL] Clinical Platform Alert'
        body: |
          CRITICAL ALERT - Immediate attention required
          
          {{ range .Alerts }}
          Alert: {{ .Annotations.summary }}
          Description: {{ .Annotations.description }}
          Time: {{ .StartsAt }}
          {{ end }}
    slack_configs:
      - api_url: '${SLACK_WEBHOOK_URL}'
        channel: '#critical-alerts'
        color: 'danger'
        title: 'Clinical Platform Critical Alert'
        text: |
          {{ range .Alerts }}
          *{{ .Annotations.summary }}*
          {{ .Annotations.description }}
          {{ end }}

  - name: 'compliance-team'
    email_configs:
      - to: 'compliance@${DOMAIN}'
        subject: '[COMPLIANCE] Clinical Platform Alert'
        body: |
          COMPLIANCE ISSUE DETECTED
          
          {{ range .Alerts }}
          Alert: {{ .Annotations.summary }}
          Description: {{ .Annotations.description }}
          Immediate review required for regulatory compliance.
          {{ end }}

  - name: 'security-team'
    email_configs:
      - to: 'security@${DOMAIN}'
        subject: '[SECURITY] Clinical Platform Alert'
        body: |
          SECURITY ALERT
          
          {{ range .Alerts }}
          Alert: {{ .Annotations.summary }}
          Description: {{ .Annotations.description }}
          {{ end }}

inhibit_rules:
  - source_match:
      severity: 'critical'
    target_match:
      severity: 'warning'
    equal: ['alertname', 'service', 'instance']

Centralized Logging Strategy

ELK Stack for Log Management

Deploy Elasticsearch, Logstash, and Kibana for comprehensive log analysis:

# logging-stack.yml
version: '3.8'

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.5.0
    environment:
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms2g -Xmx2g"
      - xpack.security.enabled=false
      - xpack.security.enrollment.enabled=false
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - elasticsearch-data:/usr/share/elasticsearch/data
    networks:
      - monitoring-net
    deploy:
      placement:
        constraints:
          - node.role == manager
      resources:
        limits:
          memory: 4G
        reservations:
          memory: 3G

  logstash:
    image: docker.elastic.co/logstash/logstash:8.5.0
    volumes:
      - ./logstash/pipeline:/usr/share/logstash/pipeline:ro
      - ./logstash/config:/usr/share/logstash/config:ro
      - traefik-logs:/var/log/traefik:ro
      - shinyproxy-logs:/var/log/shinyproxy:ro
    networks:
      - monitoring-net
    deploy:
      placement:
        constraints:
          - node.role == manager
    depends_on:
      - elasticsearch

  kibana:
    image: docker.elastic.co/kibana/kibana:8.5.0
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
      - SERVER_NAME=logs.${DOMAIN}
    networks:
      - monitoring-net
      - traefik-public
    deploy:
      placement:
        constraints:
          - node.role == manager
      labels:
        - traefik.enable=true
        - traefik.docker.network=traefik-public
        - traefik.http.routers.kibana.rule=Host(`logs.${DOMAIN}`)
        - traefik.http.routers.kibana.entrypoints=websecure
        - traefik.http.routers.kibana.tls.certresolver=letsencrypt
        - traefik.http.routers.kibana.middlewares=dashboard-auth
        - traefik.http.services.kibana.loadbalancer.server.port=5601
    depends_on:
      - elasticsearch

  filebeat:
    image: docker.elastic.co/beats/filebeat:8.5.0
    user: root
    volumes:
      - ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - filebeat-data:/usr/share/filebeat/data
    networks:
      - monitoring-net
    deploy:
      mode: global
    depends_on:
      - elasticsearch

volumes:
  elasticsearch-data:
  filebeat-data:

networks:
  monitoring-net:
    external: true
  traefik-public:
    external: true

Filebeat Configuration for Docker Logs

# filebeat.yml
filebeat.inputs:
  - type: container
    paths:
      - '/var/lib/docker/containers/*/*.log'
    processors:
      - add_docker_metadata:
          host: "unix:///var/run/docker.sock"
      - decode_json_fields:
          fields: ["message"]
          target: ""
          overwrite_keys: true

  - type: log
    paths:
      - '/var/log/traefik/*.log'
    fields:
      service: traefik
      environment: production
    fields_under_root: true

  - type: log
    paths:
      - '/var/log/shinyproxy/*.log'
    fields:
      service: shinyproxy
      environment: production
    fields_under_root: true
    multiline.pattern: '^\d{4}-\d{2}-\d{2}'
    multiline.negate: true
    multiline.match: after

processors:
  - add_host_metadata:
      when.not.contains.tags: forwarded
  - add_cloud_metadata: ~

output.elasticsearch:
  hosts: ['elasticsearch:9200']
  index: "clinical-platform-%{+yyyy.MM.dd}"

setup.template.name: "clinical-platform"
setup.template.pattern: "clinical-platform-*"
setup.template.settings:
  index.number_of_shards: 1
  index.number_of_replicas: 0

logging.level: info
logging.to_files: true
logging.files:
  path: /var/log/filebeat
  name: filebeat
  keepfiles: 7
  permissions: 0644

Logstash Pipeline Configuration

# logstash/pipeline/clinical-platform.conf
input {
  beats {
    port => 5044
  }
}

filter {
  # Parse Traefik access logs
  if [service] == "traefik" and [message] =~ /^[\d\.]+/ {
    grok {
      match => { 
        "message" => "%{IPORHOST:client_ip} - - \[%{HTTPDATE:timestamp}\] \"%{WORD:method} %{URIPATHPARAM:request} HTTP/%{NUMBER:http_version}\" %{NUMBER:response_code} %{NUMBER:bytes} \"%{DATA:referrer}\" \"%{DATA:user_agent}\" %{NUMBER:request_time}ms"
      }
    }
    
    date {
      match => [ "timestamp", "dd/MMM/yyyy:HH:mm:ss Z" ]
    }
    
    mutate {
      convert => { 
        "response_code" => "integer"
        "bytes" => "integer"
        "request_time" => "integer"
      }
    }
  }
  
  # Parse ShinyProxy logs
  if [service] == "shinyproxy" {
    grok {
      match => { 
        "message" => "%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{DATA:thread} %{DATA:class} - %{GREEDYDATA:log_message}"
      }
    }
    
    date {
      match => [ "timestamp", "ISO8601" ]
    }
    
    # Extract user session information
    if [log_message] =~ /user/ {
      grok {
        match => { "log_message" => ".*user (?<user_id>[^\s]+).*" }
      }
    }
    
    # Extract application information
    if [log_message] =~ /app/ {
      grok {
        match => { "log_message" => ".*app (?<app_id>[^\s]+).*" }
      }
    }
  }
  
  # Parse container logs
  if [container] {
    mutate {
      add_field => { "container_name" => "%{[container][name]}" }
      add_field => { "container_id" => "%{[container][id]}" }
      add_field => { "image_name" => "%{[container][image]}" }
    }
    
    # Parse JSON logs from Shiny applications
    if [container_name] =~ /shiny/ {
      json {
        source => "message"
        target => "shiny_log"
      }
    }
  }
  
  # Add environment and service tags
  mutate {
    add_field => { "environment" => "production" }
    add_field => { "platform" => "clinical-statistics" }
  }
  
  # Remove sensitive information
  mutate {
    remove_field => [ "host", "agent", "ecs", "log", "input" ]
  }
}

output {
  elasticsearch {
    hosts => ["elasticsearch:9200"]
    index => "clinical-platform-%{+YYYY.MM.dd}"
  }
  
  # Debug output
  if [level] == "ERROR" or [response_code] >= 500 {
    stdout { 
      codec => rubydebug 
    }
  }
}

Deployment Automation and CI/CD

GitHub Actions Workflow

Create automated deployment pipeline:

# .github/workflows/production-deployment.yml
name: Production Deployment

on:
  push:
    branches: [ main ]
    tags: [ 'v*' ]
  pull_request:
    branches: [ main ]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: clinical-stats/independent-t-test

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup R
        uses: r-lib/actions/setup-r@v2
        with:
          r-version: '4.3.0'
          
      - name: Install dependencies
        run: |
          R -e "install.packages('renv')"
          R -e "renv::restore()"
          
      - name: Run tests
        run: |
          R -e "devtools::test()"
          
      - name: Run R CMD check
        run: |
          R -e "devtools::check()"

  security-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          format: 'sarif'
          output: 'trivy-results.sarif'
          
      - name: Upload Trivy scan results
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: 'trivy-results.sarif'

  build-and-push:
    needs: [test, security-scan]
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Log in to Container Registry
        uses: docker/login-action@v2
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
          
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v4
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            
      - name: Build and push Docker image
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy-staging:
    needs: build-and-push
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    environment: staging
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Deploy to staging
        run: |
          echo "Deploying to staging environment"
          # Add staging deployment commands
          
      - name: Run integration tests
        run: |
          echo "Running integration tests"
          # Add integration test commands

  deploy-production:
    needs: [build-and-push, deploy-staging]
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/v')
    environment: production
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup SSH
        uses: webfactory/ssh-agent@v0.7.0
        with:
          ssh-private-key: ${{ secrets.PRODUCTION_SSH_KEY }}
          
      - name: Deploy to production
        run: |
          ssh -o StrictHostKeyChecking=no deploy@${{ secrets.PRODUCTION_HOST }} << 'EOF'
            cd /opt/clinical-platform
            export APP_VERSION=${{ github.ref_name }}
            docker stack deploy -c docker-compose.production.yml clinical-platform
            
            # Wait for deployment to complete
            echo "Waiting for services to be ready..."
            sleep 30
            
            # Verify deployment
            docker service ls
            
            # Run health checks
            curl -f https://stats.${{ secrets.DOMAIN }}/actuator/health || exit 1
          EOF
          
      - name: Notify deployment success
        if: success()
        run: |
          curl -X POST -H 'Content-type: application/json' \
            --data '{"text":"✅ Production deployment successful: ${{ github.ref_name }}"}' \
            ${{ secrets.SLACK_WEBHOOK_URL }}
            
      - name: Notify deployment failure
        if: failure()
        run: |
          curl -X POST -H 'Content-type: application/json' \
            --data '{"text":"❌ Production deployment failed: ${{ github.ref_name }}"}' \
            ${{ secrets.SLACK_WEBHOOK_URL }}

Production Deployment Script

#!/bin/bash
# deploy-production.sh

set -euo pipefail

# Configuration
STACK_NAME="clinical-platform"
COMPOSE_FILE="docker-compose.production.yml"
BACKUP_DIR="/opt/backups/$(date +%Y%m%d_%H%M%S)"

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

log() {
    echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}"
}

warn() {
    echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}"
}

error() {
    echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}"
    exit 1
}

# Pre-deployment checks
pre_deployment_checks() {
    log "Running pre-deployment checks..."
    
    # Check if running as root or with sudo
    if [[ $EUID -eq 0 ]]; then
        error "Do not run this script as root"
    fi
    
    # Check Docker Swarm status
    if ! docker info | grep -q "Swarm: active"; then
        error "Docker Swarm is not active"
    fi
    
    # Check required environment variables
    required_vars=("DOMAIN" "APP_VERSION" "GRAFANA_PASSWORD" "INFLUXDB_PASSWORD")
    for var in "${required_vars[@]}"; do
        if [[ -z "${!var:-}" ]]; then
            error "Required environment variable $var is not set"
        fi
    done
    
    # Check available disk space
    available_space=$(df / | awk 'NR==2 {print $4}')
    if [[ $available_space -lt 10485760 ]]; then  # 10GB in KB
        error "Insufficient disk space. At least 10GB required."
    fi
    
    # Verify external dependencies
    log "Checking external dependencies..."
    if ! curl -s -f https://api.github.com/repos/openanalytics/shinyproxy > /dev/null; then
        warn "GitHub API not accessible. Continuing with deployment."
    fi
    
    log "Pre-deployment checks completed successfully"
}

# Create backup of current state
create_backup() {
    log "Creating backup of current deployment..."
    
    mkdir -p "$BACKUP_DIR"
    
    # Backup configuration files
    cp -r /opt/clinical-platform/config "$BACKUP_DIR/"
    
    # Export current service configurations
    docker service ls --format "table {{.Name}}\t{{.Mode}}\t{{.Replicas}}\t{{.Image}}" > "$BACKUP_DIR/services.txt"
    
    # Backup volumes (if configured)
    if docker volume ls | grep -q "clinical-platform"; then
        log "Backing up Docker volumes..."
        docker run --rm -v clinical-platform_shared-data:/data -v "$BACKUP_DIR":/backup alpine tar czf /backup/volumes.tar.gz -C /data .
    fi
    
    log "Backup created at $BACKUP_DIR"
}

# Deploy the stack
deploy_stack() {
    log "Deploying $STACK_NAME stack..."
    
    # Pull latest images
    log "Pulling latest images..."
    docker-compose -f "$COMPOSE_FILE" pull
    
    # Deploy stack with rolling update
    docker stack deploy -c "$COMPOSE_FILE" "$STACK_NAME" --with-registry-auth
    
    log "Stack deployment initiated"
}

# Wait for services to be ready
wait_for_services() {
    log "Waiting for services to be ready..."
    
    local max_attempts=60
    local attempt=0
    
    while [[ $attempt -lt $max_attempts ]]; do
        local ready_services=0
        local total_services=0
        
        while read -r service replicas; do
            total_services=$((total_services + 1))
            if [[ "$replicas" == *"/"* ]]; then
                local current=$(echo "$replicas" | cut -d'/' -f1)
                local desired=$(echo "$replicas" | cut -d'/' -f2)
                if [[ "$current" == "$desired" ]] && [[ "$current" != "0" ]]; then
                    ready_services=$((ready_services + 1))
                fi
            fi
        done < <(docker service ls --filter label=com.docker.stack.namespace="$STACK_NAME" --format "{{.Name}} {{.Replicas}}")
        
        if [[ $ready_services -eq $total_services ]] && [[ $total_services -gt 0 ]]; then
            log "All $total_services services are ready"
            return 0
        fi
        
        log "Services ready: $ready_services/$total_services. Waiting..."
        sleep 10
        attempt=$((attempt + 1))
    done
    
    error "Services did not become ready within expected time"
}

# Run health checks
run_health_checks() {
    log "Running health checks..."
    
    local health_checks=(
        "https://stats.$DOMAIN/actuator/health"
        "https://monitoring.$DOMAIN/api/health"
        "https://traefik.$DOMAIN/ping"
    )
    
    for url in "${health_checks[@]}"; do
        log "Checking $url..."
        if curl -f -s -m 30 "$url" > /dev/null; then
            log "✓ $url is healthy"
        else
            error "✗ Health check failed for $url"
        fi
    done
    
    # Check ShinyProxy can start an app
    log "Testing ShinyProxy app startup..."
    local test_response=$(curl -s -w "%{http_code}" -o /dev/null "https://stats.$DOMAIN/app/independent-t-test")
    if [[ "$test_response" == "200" ]] || [[ "$test_response" == "302" ]]; then
        log "✓ ShinyProxy app startup test passed"
    else
        warn "ShinyProxy app startup test returned HTTP $test_response"
    fi
    
    log "Health checks completed"
}

# Post-deployment tasks
post_deployment() {
    log "Running post-deployment tasks..."
    
    # Clean up old images
    log "Cleaning up old Docker images..."
    docker image prune -f
    
    # Update monitoring dashboards
    log "Updating monitoring dashboards..."
    # Add dashboard update logic here
    
    # Send notification
    if [[ -n "${SLACK_WEBHOOK_URL:-}" ]]; then
        curl -X POST -H 'Content-type: application/json' \
            --data "{\"text\":\"✅ Clinical Platform deployment completed successfully. Version: $APP_VERSION\"}" \
            "$SLACK_WEBHOOK_URL" || warn "Failed to send Slack notification"
    fi
    
    log "Post-deployment tasks completed"
}

# Rollback function
rollback() {
    error "Deployment failed. Initiating rollback..."
    
    if [[ -d "$BACKUP_DIR" ]]; then
        log "Rolling back to previous configuration..."
        # Implement rollback logic here
        warn "Manual rollback may be required"
    fi
}

# Main deployment process
main() {
    log "Starting deployment of Clinical Platform v$APP_VERSION"
    
    # Set up error handling
    trap rollback ERR
    
    pre_deployment_checks
    create_backup
    deploy_stack
    wait_for_services
    run_health_checks
    post_deployment
    
    log "Deployment completed successfully!"
    log "Dashboard: https://monitoring.$DOMAIN"
    log "Application: https://stats.$DOMAIN"
}

# Run main function
main "$@"

Common Questions About Production Deployment

Docker Swarm advantages:

Docker Swarm is often the better choice for enterprise Shiny applications because it offers simpler configuration, excellent integration with ShinyProxy, lower operational overhead, and faster deployment cycles. It provides enterprise-grade features like service discovery, load balancing, and rolling updates without the complexity of Kubernetes.

When to choose Kubernetes:

Consider Kubernetes only if you need advanced features like custom resource definitions, complex multi-tenant isolation, or integration with existing Kubernetes infrastructure. For most clinical research and pharmaceutical applications, Docker Swarm provides sufficient scalability and reliability.

Practical considerations:

Docker Swarm requires less specialized knowledge, has lower resource overhead, and provides faster time-to-production. The ShinyProxy team has extensive Docker Swarm experience and provides better support for Swarm deployments.

Application-level metrics:

Track user session counts, application startup times, failed authentication attempts, and active concurrent users. These metrics directly impact user experience and indicate system performance issues.

Compliance and audit metrics:

Monitor audit log write success rates, unauthorized access attempts, data export activities, and session duration. These metrics are crucial for regulatory compliance and security auditing.

Infrastructure metrics:

Focus on CPU and memory usage per node, disk space availability, network latency between services, and Docker container health status. These provide early warning of capacity and performance issues.

Business metrics:

Track application usage patterns, peak usage times, most-used statistical functions, and user geographic distribution. This data supports capacity planning and feature prioritization decisions.

Rolling update strategy:

Configure Docker Swarm with multiple replicas and rolling update settings that deploy one instance at a time. Use health checks to verify each new instance before proceeding to the next.

Session persistence:

Implement sticky sessions in Traefik to ensure users aren’t disrupted during deployments. Use shared storage for session data and reports so users can resume work on any instance.

Blue-green deployment:

For critical updates, maintain parallel environments and switch traffic gradually. This approach provides instant rollback capability and zero user impact.

Database and data considerations:

Ensure shared data volumes use network storage that’s accessible to all nodes. Plan database migrations carefully and use database versioning to maintain compatibility during updates.

Data protection requirements:

Clinical data requires encryption at rest and in transit, with proper key management. Implement network segmentation to isolate statistical applications from other systems, and ensure all communications use TLS 1.2 or higher.

Audit and compliance:

Maintain comprehensive audit logs for all user actions, data access, and system changes. Implement tamper-evident logging with digital signatures and ensure log retention meets regulatory requirements (typically 7-25 years for clinical data).

Access control:

Use strong authentication (preferably multi-factor) integrated with organizational identity providers. Implement role-based access control with principle of least privilege, and regularly review and revoke access permissions.

Vulnerability management:

Establish regular security scanning for containers and dependencies, implement automated patching for non-breaking updates, and maintain an incident response plan specific to clinical data environments.

Test Your Understanding

You’re designing a production Docker Swarm cluster for a clinical research organization that needs to support 200 concurrent users across multiple statistical applications. Design the optimal cluster architecture including node configuration, network topology, and resource allocation strategy.

Consider:

  • High availability requirements
  • Security isolation needs
  • Scalability for future growth
  • Regulatory compliance requirements
  • Think about manager vs worker node roles and distribution
  • Consider network segmentation for security
  • Remember resource requirements for statistical applications
  • Think about backup and disaster recovery needs

Optimal Docker Swarm Architecture for Clinical Research:

Cluster Configuration:

# 5-Node Production Cluster
Manager Nodes: 3
  - Primary Manager: 16 cores, 32GB RAM, 500GB SSD
  - Secondary Managers: 8 cores, 16GB RAM, 200GB SSD
  - Geographic distribution across data centers
  
Worker Nodes: 3+ (scalable)
  - Statistical Workers: 16 cores, 32GB RAM, 1TB SSD
  - Monitoring Worker: 8 cores, 16GB RAM, 500GB SSD
  - Dedicated for high-memory statistical computations

Network Topology:

# Overlay Networks for Isolation
traefik-public:
  - Internet-facing traffic only
  - SSL termination and load balancing
  
clinical-apps:
  - Statistical applications
  - Isolated from internet
  - Access via traefik only
  
monitoring-internal:
  - Monitoring and logging services
  - No external access
  
data-backend:
  - Database and storage services
  - Highest security isolation

Resource Allocation Strategy:

# Service Constraints and Limits
ShinyProxy:
  replicas: 2
  placement: manager nodes
  memory: 2GB per replica
  
Statistical Apps:
  memory_limit: 4GB per container
  cpu_limit: 2 cores per container
  max_containers_per_node: 8
  
Monitoring:
  dedicated_worker: true
  memory: 8GB total allocation
  storage: network-attached for persistence

Security and Compliance Features:

  • Encrypted overlay networks with automatic key rotation
  • Network policies preventing direct container-to-container communication
  • Dedicated nodes for sensitive workloads
  • Backup strategy with encrypted off-site storage
  • Audit logging to immutable storage systems

Scalability Design:

  • Horizontal scaling: Add worker nodes as needed
  • Vertical scaling: Manager nodes can handle 10+ worker nodes
  • Auto-scaling triggers based on resource utilization
  • Geographic distribution for disaster recovery

This architecture supports regulatory requirements while providing room for growth and maintaining high availability.

Design a comprehensive monitoring and alerting strategy for your production clinical statistics platform. Your solution should address different stakeholder needs and compliance requirements.

Create alert rules for:

  1. Technical operations team
  2. Clinical research leadership
  3. Regulatory compliance officer
  4. End users

Include specific metrics, thresholds, and escalation procedures.

  • Consider different severity levels and response times
  • Think about regulatory and audit requirements
  • Remember that false alarms reduce trust in monitoring
  • Consider business impact vs technical metrics

Comprehensive Monitoring and Alerting Strategy:

1. Technical Operations Team Alerts:

# Critical Infrastructure (15-minute response)
- ShinyProxy service unavailable (> 2 minutes)
- Traefik load balancer down (> 1 minute)  
- Manager node offline (immediate)
- Disk space < 10% (any node)
- Memory usage > 90% (sustained 5 minutes)

# Warning Level (1-hour response)
- CPU usage > 80% (sustained 10 minutes)
- Container restart frequency > 3 per hour
- SSL certificate expiring < 30 days
- Failed application deployments
- Network latency > 500ms between nodes

# Automated Actions:
- Auto-scale worker nodes when CPU > 70%
- Restart failed containers automatically
- Rotate logs when disk usage > 80%

2. Clinical Research Leadership Alerts:

# Business Impact (4-hour response)
- Platform unavailable > 10 minutes
- User sessions failing > 25% rate
- Statistical analysis failures > 10 per hour
- Report generation delays > 2 hours

# Capacity Planning (24-hour response)  
- Peak concurrent users approaching limits
- Weekly analysis volume trends
- Application usage patterns and adoption

# Executive Summary (Weekly):
- Platform availability percentage
- User growth and engagement metrics  
- Cost per analysis and resource utilization
- Feature usage and adoption rates

3. Regulatory Compliance Officer Alerts:

# Immediate Response (< 30 minutes)
- Audit log write failures (any occurrence)
- Unauthorized access attempts > 5 per hour
- Data export activities outside business hours
- Failed authentication > 20 attempts per user
- System configuration changes without approval

# Compliance Monitoring (Daily review)
- User access review requirements
- Failed backup operations  
- Security scan results and vulnerabilities
- Data retention policy violations
- Change control process deviations

# Audit Trail Verification (Weekly)
- Log integrity verification results
- User activity anomaly detection
- Data lineage tracking completeness
- Electronic signature validation status

4. End User Notifications:

# Service Announcements:
- Planned maintenance windows (48-hour notice)
- New feature releases and training
- Performance optimization updates
- Security policy changes

# User Experience:
- Session timeout warnings (5-minute notice)
- Long-running analysis progress updates
- Report generation completion notifications
- Application availability status page

# Self-Service Alerts:
- Personal usage quota approaching limits
- Shared project collaboration invitations
- Analysis result sharing notifications
- Account security and password expiration

Escalation Matrix:

Severity_1_Critical:
  initial_response: 15_minutes
  escalation_1: 30_minutes (team_lead)
  escalation_2: 1_hour (department_head)
  escalation_3: 2_hours (executive_team)

Severity_2_High:
  initial_response: 1_hour
  escalation_1: 4_hours (team_lead)
  escalation_2: 8_hours (department_head)

Severity_3_Medium:
  initial_response: 8_hours
  escalation_1: 24_hours (team_lead)

Severity_4_Low:
  initial_response: 24_hours
  escalation_1: 72_hours (team_lead)

Alert Fatigue Prevention:

  • Dynamic thresholds based on historical patterns
  • Intelligent alert grouping and correlation
  • Automatic alert suppression during maintenance
  • Regular review and tuning of alert sensitivity
  • Machine learning-based anomaly detection for reduced false positives

This strategy balances technical reliability with business needs while maintaining regulatory compliance.

Your clinical statistics platform handles critical research data and must maintain high availability. Design a comprehensive disaster recovery plan that addresses both technical infrastructure failures and data protection requirements.

Your solution should include:

  • Recovery Time Objectives (RTO) and Recovery Point Objectives (RPO)
  • Backup strategies for different data types
  • Geographic distribution and failover procedures
  • Testing and validation processes
  • Consider regulatory requirements for clinical data
  • Think about different types of disasters (hardware, software, natural, cyber)
  • Remember that statistical analyses may take hours to complete
  • Consider the impact on ongoing research studies

Comprehensive Disaster Recovery Plan for Clinical Statistics Platform:

Recovery Objectives:

Critical Services (RTO: 1 hour, RPO: 15 minutes):
  - User authentication and access
  - Core statistical applications
  - Audit logging systems
  - Data integrity verification

Important Services (RTO: 4 hours, RPO: 1 hour):
  - Report generation systems
  - Historical analysis archives
  - Monitoring and alerting
  - User training materials

Standard Services (RTO: 24 hours, RPO: 4 hours):
  - Development environments
  - Performance analytics
  - Non-critical documentation
  - Archived project data

Multi-Tier Backup Strategy:

Tier_1_Hot_Backup (Continuous):
  - Real-time database replication to secondary site
  - Docker volume synchronization every 15 minutes
  - Configuration file versioning with Git
  - Automated failover for critical services

Tier_2_Warm_Backup (Hourly):
  - Application state snapshots
  - User session data backups
  - Analysis result archives  
  - System configuration exports

Tier_3_Cold_Backup (Daily):
  - Full system image backups
  - Long-term data archives
  - Compliance documentation
  - Disaster recovery testing data

Tier_4_Offsite_Archive (Weekly):
  - Encrypted archives to cloud storage
  - Regulatory compliance copies
  - Business continuity documentation
  - Recovery procedure validation

Geographic Distribution:

Primary_Site (Production):
  location: "Primary Data Center"
  services: "All production workloads"
  capacity: "100% normal operations"
  
Secondary_Site (Hot_Standby):
  location: "Secondary Data Center (100+ miles)"
  services: "Critical services only"
  capacity: "60% reduced capacity"
  failover_time: "< 1 hour"
  
Tertiary_Site (Cold_Standby):
  location: "Cloud provider (different region)"
  services: "Essential services only"
  capacity: "30% minimal operations"
  failover_time: "< 24 hours"

Automated Failover Procedures:

# Automated Health Monitoring
#!/bin/bash
monitor_primary_site() {
  while true; do
    if ! curl -f -m 30 https://stats.primary.domain/health; then
      log_alert "Primary site health check failed"
      
      # Verify with secondary checks
      if ! ping -c 3 primary.domain; then
        log_alert "Primary site network unreachable"
        initiate_failover "network_failure"
      fi
      
      # Check specific services
      check_service_health
      
      if [[ $failed_services -gt 2 ]]; then
        initiate_failover "service_failure"
      fi
    fi
    
    sleep 30
  done
}

initiate_failover() {
  local failure_type=$1
  
  # 1. Notify stakeholders
  send_emergency_notification "Initiating failover due to: $failure_type"
  
  # 2. Promote secondary site
  promote_secondary_to_primary
  
  # 3. Update DNS records
  update_dns_to_secondary
  
  # 4. Verify services
  verify_secondary_site_health
  
  # 5. Notify completion
  send_notification "Failover completed. Services restored."
}

Data Protection Strategy:

Clinical_Data:
  encryption: "AES-256 at rest and in transit"
  replication: "Synchronous to secondary site"
  retention: "25 years per regulatory requirements"
  access_control: "Role-based with audit trail"
  backup_frequency: "Continuous with 15-minute RPO"

Analysis_Results:
  versioning: "Git-based with digital signatures"
  backup_frequency: "Real-time to multiple locations"
  integrity_verification: "Cryptographic checksums"
  archive_policy: "Automated based on project lifecycle"

System_Configuration:
  version_control: "Infrastructure as Code (IaC)"
  backup_frequency: "Every configuration change"
  rollback_capability: "Automated to any previous version"
  testing: "Validation in staging environment"

User_Data:
  encryption: "End-to-end encryption"
  backup_frequency: "Hourly incremental"
  recovery_testing: "Monthly validation"
  privacy_compliance: "GDPR and HIPAA aligned"

Testing and Validation:

Monthly_Tests:
  - Backup restoration verification
  - Failover procedure dry runs
  - Data integrity checks
  - Recovery time measurement

Quarterly_Tests:
  - Full disaster recovery simulation
  - Geographic failover testing
  - Business continuity validation
  - Staff training and procedures review

Annual_Tests:
  - Complete infrastructure rebuild
  - Regulatory compliance audit
  - Third-party security assessment
  - Business impact analysis update

Continuous_Monitoring:
  - Automated backup verification
  - Real-time replication monitoring
  - Performance baseline tracking
  - Capacity planning updates

Regulatory Compliance Integration:

Documentation_Requirements:
  - Change control for all DR procedures
  - Validation evidence for recovery testing
  - Risk assessment updates
  - Business impact analysis maintenance

Audit_Trail:
  - All DR activities logged immutably
  - Digital signatures for procedure changes
  - Timeline documentation for incidents
  - Compliance reporting automation

Validation_Protocol:
  - DR procedure qualification
  - Recovery testing documentation
  - Performance verification evidence
  - Regulatory submission support

This comprehensive plan ensures business continuity while maintaining regulatory compliance and protecting critical clinical research data.

Conclusion

Production deployment and monitoring of enterprise Shiny applications requires sophisticated infrastructure orchestration that balances scalability, security, and regulatory compliance. The Docker Swarm-based architecture we’ve implemented provides enterprise-grade reliability while maintaining the operational simplicity needed for clinical research environments.

The comprehensive monitoring and alerting system ensures that your statistical analysis platform can support mission-critical research activities with the reliability and transparency required for regulatory compliance. The integration of Traefik, ShinyProxy, and comprehensive observability tools creates a production-ready platform that can scale from departmental use to organization-wide deployment.

Your enterprise Shiny application is now equipped with the infrastructure foundation necessary to support clinical research, pharmaceutical development, and regulatory submissions with the reliability and compliance standards expected in these critical environments.

Next Steps

Based on what you’ve learned about production deployment and monitoring, here are the recommended paths for completing your enterprise development journey:

Immediate Next Steps (Complete These First)

  • Regulatory & Clinical Applications - Learn specific regulatory requirements and compliance considerations for clinical research platforms
  • Scaling & Long-term Maintenance - Master strategies for scaling your platform and maintaining enterprise systems long-term
  • Practice Exercise: Deploy the complete production stack for your t-test application including monitoring, logging, and disaster recovery capabilities

Building on Your Foundation (Choose Your Path)

For Regulatory and Compliance Focus:

For Advanced Operations:

For Infrastructure Mastery:

Long-term Goals (2-4 Weeks)

  • Build and deploy a complete clinical research platform with multiple statistical applications
  • Implement enterprise-grade monitoring and compliance systems that support regulatory submissions
  • Establish expertise in production DevOps for statistical computing environments
  • Lead enterprise-scale deployments for clinical research and pharmaceutical applications

Explore More Enterprise Development Articles

Note

Here are more articles from the Enterprise Development series to help you build production-ready statistical applications.

placeholder

placeholder
No matching items
Back to top

Reuse

Citation

BibTeX citation:
@online{kassambara2025,
  author = {Kassambara, Alboukadel},
  title = {Production {Deployment} \& {Monitoring:} {Enterprise} {Shiny}
    {Applications}},
  date = {2025-05-23},
  url = {https://www.datanovia.com/learn/tools/shiny-apps/enterprise-development/deployment-monitoring.html},
  langid = {en}
}
For attribution, please cite this work as:
Kassambara, Alboukadel. 2025. “Production Deployment & Monitoring: Enterprise Shiny Applications.” May 23, 2025. https://www.datanovia.com/learn/tools/shiny-apps/enterprise-development/deployment-monitoring.html.