rguides

Deploying Shiny Apps to shinyapps.io, Connect, and Docker

Deploying Shiny apps turns local prototypes into live, shareable dashboards, and R’s ecosystem gives you several clear paths to do it. You’ve built a Shiny app locally and now it’s time to share it. Deployment is the final step in the Shiny development workflow, and R makes it relatively straightforward. In this tutorial we’ll cover the three most popular deployment platforms: shinyapps.io, Posit Connect (formerly RStudio Connect), and Docker-based deployments.

Each platform makes a different trade-off. shinyapps.io is the fastest path to a public URL and is the right choice when you want zero infrastructure. Posit Connect is the enterprise option with authentication, scheduled reports, and centralised access control. Docker gives you total control: any cloud, any reverse proxy, any auth layer you bring yourself. By the end of this tutorial you should be able to deploy the same app to all three and decide which fits your situation. See the pkgdown sites tutorial for related Posit-stack deployment patterns.

Prerequisites

Before deploying your app, ensure you have:

  • A working Shiny app in a standalone script (app.R) or as a folder with app.R and www/ assets
  • An account on your chosen deployment platform
  • The rsconnect package installed:
install.packages("rsconnect")

Installing rsconnect gives you the command-line tools for deployment, but you still need to link it to a hosting account. The next section walks through connecting to shinyapps.io, where you’ll authenticate once and then deploy with a single function call.

Deploying to shinyapps.io

shinyapps.io is the easiest way to deploy Shiny apps. It’s a hosted service by Posit (formerly RStudio) that handles all server infrastructure for you.

Step 1: connect your account

First, authorize rsconnect to access your shinyapps.io account:

library(rsconnect)
rsconnect::setAccountInfo(name = "your-account-name", 
                          token = "YOUR_TOKEN", 
                          secret = "YOUR_SECRET")

You get these credentials from your shinyapps.io dashboard under the “Tokens” tab.

Step 2: deploy your app

Deploying is a single function call: rsconnect::deployApp() takes the path to your app directory and handles uploading files, installing dependencies, and launching the server. The first deployment uploads all your app’s dependencies, which may take a minute. Subsequent deployments are faster since rsconnect caches unchanged files and only uploads what has been modified.

library(shiny)
rsconnect::deployApp("path/to/your/app")

# For an app.R in your current directory, use "."
rsconnect::deployApp(".")

Example: deploying the diamonds explorer

Here is a complete Shiny app that explores the diamonds dataset. It uses a dropdown to select diamond cut quality and displays a histogram of carat weights for the selected group. Deploying it requires only the rsconnect::deployApp() call at the end; rsconnect detects the necessary packages (shiny, ggplot2) from the code and includes them in the deployment bundle automatically.

ui <- fluidPage(
  titlePanel("Diamond Explorer"),
  selectInput("cut", "Select Cut:", choices = unique(diamonds$cut)),
  plotOutput("hist")
)

server <- function(input, output) {
  output$hist <- renderPlot({
    library(ggplot2)
    ggplot(diamonds[diamonds$cut == input$cut, ], aes(carat)) +
      geom_histogram()
  })
}

shinyApp(ui, server)

# Deploy it; app will be live in under a minute
rsconnect::deployApp(".")

After deployment, you’ll receive a URL like https://your-name.shinyapps.io/diamond-explorer/.

While shinyapps.io gets you a public URL in under a minute, it lacks the authentication controls and internal scheduling that teams often need. Posit Connect fills that gap by running inside your organization’s infrastructure, giving you user management and audit capabilities that the hosted service does not provide.

Deploying to rstudio connect

RStudio Connect is a professional platform for sharing data products within organizations. It supports Shiny apps, R Markdown reports, APIs, and more.

Configuration

First, configure the server connection:

rsconnect::connectServer(url = "https://your-server.com",
                         apiKey = "your-api-key")

Get your API key from the RStudio Connect user settings.

The connectServer call registers the server URL in your local rsconnect configuration, but it does not actually push any files. To upload your app, you still call deployApp(), this time passing the server and account names that identify the Connect instance you configured.

Deployment

Deploy using the same deployApp() function:

rsconnect::deployApp("path/to/app",
                     server = "your-server-name",
                     account = "your-username")

Access control

RStudio Connect lets you restrict access to specific users or groups directly from R. The setAccessRecord() function accepts a vector of email addresses and an access type of "viewer" for read-only access or "collaborator" for users who can modify the app configuration. This integrates with your organization’s existing authentication system, so you do not need to build a separate login layer.

rsconnect::setAccessRecord(
  appName = "my-shiny-app",
  users = c("user1@company.com", "user2@company.com"),
  accessType = "viewer"
)

Once your app is deployed and access is locked down, you might need more control over the runtime than a managed platform offers. Docker lets you define the exact R version, system libraries, and network configuration your app depends on, then ship that environment as a self-contained image to any cloud or on-premises host.

Docker deployment

For maximum control over the runtime environment, deploy Shiny apps in Docker containers. This approach works on any cloud platform, including AWS, GCP, Azure, or a bare-metal server, and gives you full control over the R version, system libraries, and network configuration. The trade-off is that you manage the infrastructure yourself instead of relying on a hosted platform.

Create a dockerfile

FROM rocker/shiny-verse:latest

# Install dependencies
RUN R -e "install.packages(c('shiny', 'ggplot2', 'dplyr'))"

# Copy app files
COPY app.R /srv/shiny/
COPY www /srv/shiny/www/

# Expose port
EXPOSE 3838

# Run Shiny Server
CMD ["R", "-e", "shiny::runApp('/srv/shiny', port = 3838, host = '0.0.0.0')"]

Writing a Dockerfile defines what goes into the image, but it does not actually create or run anything. The next step compiles that definition into a runnable container image. The docker build command reads the Dockerfile, executes each instruction in order, and produces a tagged image you can launch with docker run. The port mapping (-p 3838:3838) forwards traffic from your host machine into the container so the Shiny app becomes reachable at http://localhost:3838.

Build and run

docker build -t my-shiny-app .
docker run -p 3838:3838 my-shiny-app

Running the container locally confirms your app works inside Docker. To deploy it to a cloud server or Kubernetes cluster, you need to push the image to a container registry. The docker tag command assigns a registry-qualified name to your local image, and docker push uploads the layers so your production environment can pull and run the exact same image you tested.

Push to container registry

docker tag my-shiny-app registry.example.com/my-shiny-app:latest
docker push registry.example.com/my-shiny-app:latest

No matter which platform you choose, the reliability of your deployment depends on getting the same package versions in production that you tested locally. The practices below cover dependency locking, secret handling, and resource sizing. Each one prevents a common deployment failure that is hard to debug after the fact.

Deployment best practices

1. use renv for reproducibility

The renv package locks your dependencies to ensure consistent behavior:

install.packages("renv")
renv::init()
renv::snapshot()

Commit renv.lock to version control. When deploying, rsconnect uses this file to install exact package versions. This single file is the difference between a deployment that works on the first try and one that fails because the server pulled a newer, incompatible version of a dependency.

2. handle secrets properly

Never hardcode API keys or passwords in your app. Use environment variables:

api_key <- Sys.getenv("MY_API_KEY")

On shinyapps.io, add secrets via the dashboard’s “Variables” tab.

Keeping secrets out of your source code protects you from accidental credential leaks, but another common deployment slowdown comes from bloated dependencies. Each unnecessary package adds to the upload size and increases the chance of version conflicts on the server. The next section shows how to slim down your imports.

3. optimize app size

Large apps take longer to deploy. Remove unnecessary packages:

# Instead of library(ggplot2), use specific imports
library(dplyr, exclude = "filter")

4. set appropriate resources

In shinyapps.io, configure instance size based on your app’s needs:

PlanMemoryConcurrent Users
Free1 GB25
Starter2 GB50
Professional8 GB200

Even with the right dependencies and resource allocation, deployments can still fail for reasons that are not obvious from the error message. The troubleshooting steps below cover the most common failure modes: missing packages, runtime errors, and connection timeouts, and how to diagnose each one so you can get your app live without guesswork.

Troubleshooting deployment issues

Package not available

If deployment fails with “package X is not available”:

  • Check the package supports your R version
  • Verify the package is on CRAN or a valid repository
  • For GitHub packages, use install_github() in your app before deploying

App loads but shows errors

Enable debugging:

rsconnect::deployApp(".", 
                     appName = "my-app",
                     launch.browser = FALSE,
                     lint = TRUE)

Connection timeout

Increase the timeout in your rsconnect configuration or switch to a larger instance plan.

Once your app is deployed and running, you will inevitably need to push updates. The good news is that rsconnect handles incremental deployments intelligently: it compares your local files against what is on the server and only uploads the differences. This makes subsequent deploys much faster than the initial one, often completing in seconds rather than minutes.

Updating your deployed app

After making changes to your app locally, redeploy with the same command:

rsconnect::deployApp(".")

The rsconnect package detects changes and uploads only modified files. However, if your app starts behaving strangely after an update (perhaps a cached dependency version is causing a conflict), you can bypass the incremental logic and force a clean rebuild. The forceRebuild flag tells rsconnect to reinstall every package from scratch, which resolves most dependency-related deployment issues at the cost of a slower upload.

To force a full redeploy (useful if dependency issues occur):

rsconnect::deployApp(".", forceRebuild = TRUE)

Beyond one-off deployments, many production Shiny apps need to refresh their data on a regular cadence. Posit Connect supports this through scheduled R Markdown rendering, where the platform runs your report at configured intervals and serves the updated output. This is especially useful for dashboards that pull from databases or APIs that change throughout the day.

Scheduling and background tasks

RStudio Connect supports scheduled rendering and background jobs. This lets your app refresh data periodically:

# In an R Markdown report context
rmarkdown::render("report.Rmd",
                  params = list(refresh = TRUE))

Set up schedules through the RStudio Connect dashboard for automatic report generation.

Monitoring and scaling

Shiny Server Open Source handles one request per app process. Under load, multiple users queue. Shiny Server Pro and Posit Connect support multiple worker processes per app, enabling concurrent users.

Application logs: Sys.time() entries in message() calls appear in the Shiny Server logs. shinyapps.io provides application logs in the web UI. For structured logging, log4r::log4r_appender() writes JSON logs compatible with log aggregation services.

Performance monitoring: profvis::profvis({ ... }) profiles reactive execution inside Shiny. shiny::profLog() logs reactive execution timing. High-latency renders indicate either slow data queries (add caching) or slow R computation (vectorize or parallelize).

Continuous deployment

GitHub Actions can deploy to shinyapps.io automatically on push:

- name: Deploy to shinyapps.io
  run: |
    Rscript -e "rsconnect::setAccountInfo(name='${{ secrets.SHINYAPPS_ACCOUNT }}',
      token='${{ secrets.SHINYAPPS_TOKEN }}', secret='${{ secrets.SHINYAPPS_SECRET }}')"
    Rscript -e "rsconnect::deployApp()"

Store shinyapps.io credentials as GitHub secrets. The workflow deploys on every push to main. Add a build step before deployment to run tests and validate the app starts without errors.

Summary

Deploying Shiny apps is straightforward with the right platform:

  • shinyapps.io: Easiest, fully managed, great for learning and small projects
  • Posit Connect: Best for organizations needing security and access control
  • Docker: Maximum flexibility for custom infrastructure

Key takeaways:

  • Use rsconnect::deployApp() for most deployments
  • Use renv for reproducible environments
  • Store secrets in environment variables, never in code

Next steps

Now that you understand deploying a Shiny app in R, explore these related topics to deepen your knowledge and apply these techniques in more complex scenarios.

See also