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 withapp.Randwww/assets - An account on your chosen deployment platform
- The
rsconnectpackage 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:
| Plan | Memory | Concurrent Users |
|---|---|---|
| Free | 1 GB | 25 |
| Starter | 2 GB | 50 |
| Professional | 8 GB | 200 |
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
renvfor 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
- Shiny Reactivity: Master reactive programming before deploying complex Shiny applications.
- Shiny Testing Guide: Test your app thoroughly before pushing to production.
- httr2 and APIs: Back your Shiny app with live data from REST APIs.
- Data Wrangling with dplyr: Process the data your deployed Shiny app serves to users.