Join the Shiny Community every month at Shiny Gatherings

hero image with Gitlab CI/CD logo

How to build a CI/CD pipeline for Shiny apps with Gitlab-CI and RStudio (Posit) Connect


RStudio Connect makes publishing Shiny Apps incredibly simple with only a few easy-to-follow steps. You can find step-by-step instructions in RStudio’s guide for publishing a Shiny app. Despite that ease of use, at some point, you might want to automate this process by implementing a CI/CD pipeline. Building a CI/CD Pipeline for your Shiny apps takes a little effort up front, but it’s easy to achieve with Gitlab-CI and RStudio Connect. The ROI is well worth it for your Shiny app production.

A continuous integration pipeline is an ideal solution for projects with multiple developers and when deploying the app to multiple environments. The CI/CD pipeline will enable features in your project like running unit tests on every PR and automatically deploying the app after changes are merged to the proper branch, e.g. main or dev. Having continuous integration means frequent merges to shared branches with automated validation. This automated validator makes it easier to find and fix bugs quickly. The best and easiest way to implement such a pipeline is to use the built-in tools inside the platform where you store your code, e.g GitHub Actions or GitLab-CI.

Speed up production with Appsilon’s open source Shiny Dashboard Templates, available now!

In this article, I will describe how to create such a pipeline in the GitLab-CI platform.

Note: At the time of writing this article, Posit PBC was RStudio PBC. We use RStudio and Posit interchangeably in this text (e.g. RStudio Connect == Posit Connect).

Requirements to build a CI/CD pipeline for Shiny apps

Before we can enable the CI/CD pipeline we have to prepare our repository with a Shiny app for automatic testing and deployments.

First, initialize renv.lock by using renv package to restore the packages inside your CI/CD job. The package will be used to restore packages inside your CI/CD job. Next, generate manifest.json and push it to your repository. The manifest.json file tells RStudio Connect how to deploy and host your content. Lastly, ensure that you have GitLab runner available and enabled for your project and verify that there is network connectivity between RStudio Connect and GitLab-CI.

Initializing renv.lock

To familiarize yourself with the renv package, check out RStudio’s comprehensive renv documentation. But for now, all you have to do is to call renv::init() to initialize a new project-local environment with a private R library.

Generating manifest.json

The manifest can be created by calling the R function rsconnect::writeManifest() from within your project directory. Pushing it and storing it in the repo will allow you to review the manifests before they are deployed. This additional manual step will allow you to fully control which packages, and from which sources, will be installed on RStudio Connect.

Repository file structure

What you want to achieve is the file structure shown in the image below. After initializing renv.lock and generating manifest.json file, one last thing to do is to create gitlab-ci.yml file. It will include the definition of the CI pipeline we want to implement. I intentionally said CI instead of CI/CD because in our case CD part will be configured outside of GitLab-CI platform.

repository file structure

Continuous integration with GitLab-CI

In general, you should run tests with every commit to ensure that new code is properly tested. This will help prevent new errors from being introduced. To do this we first need to create a CI/CD pipeline definition and name it properly – .gitlab-ci.yml. Note: the name must be .gitlab-ci.yml when using the GitLab CI platform.

adding tests on Gitlab-ci

Running tests inside the CI/CD pipeline

Before we run our tests, our environment needs to meet requirements. Inside the environment, you should be able to run an R job and restore necessary packages before running a testthat command. It’s very easy to achieve, you only have to pick a base docker image that has R installed and then execute renv::restore() command before running the main script.

image: rstudio/r-base:4.1.0-bionic 
before_script:   
  - R -e 'renv::restore(prompt=FALSE)'

High quality, reproducible code takes skill. Learn to write production-ready R code with Marcin Dubel

Now the core part, running tests. I will use testthat::test_dir command with a reporter parameter to save the result of the test job as a file with the proper format.

script: 
  - R -e 'testthat::test_dir("tests/testthat", reporter = testthat::JunitReporter$new(file = "../../junit_result.xml"))'

Displaying test results in GitLab-CI platform

This code will run all tests and save the results as junit_result.xml file by using testthat::JunitReporter$new function so that results can be displayed in the GitLab-CI platform in a user-friendly format by invoking the Unit test reports GitLab feature.

gitlab-ci test results

To achieve such results you will have to add a code block responsible for publishing reports in your pipeline:

artifacts:   
  reports:     
    junit: junit_result.xml

To read more about the above configuration please refer to official GitLab docs for unit test reports.

Final CI/CD Pipeline for Shiny Apps

stages:
  - tests

variables:
  RENV_PATHS_CACHE: ${CI_PROJECT_DIR}/cache
  RENV_PATHS_LIBRARY: ${CI_PROJECT_DIR}/renv/library

cache:
  key: ${CI_COMMIT_REF_SLU}
  paths:
    - ${RENV_PATHS_CACHE}
    - ${RENV_PATHS_LIBRARY}

tests:
  stage: tests
  image: rstudio/r-base:4.1.0-bionic
  before_script:
    - apt-get update && apt-get install libxml2-dev
    - R -e 'renv::restore(prompt=FALSE)'
  script:
    - R -e 'testthat::test_dir("tests/testthat", reporter = testthat::JunitReporter$new(file = "../../junit_result.xml"))'
  artifacts:
    when: always
    reports:
      junit: junit_result.xml

Continuous deployment using Git-backed content

You can deploy content directly from a remote Git repository using the Git-backed content feature available in RStudio Connect. This means that content will automatically fetch from the associated remote Git repository and re-deployed with changes. This part will be configured inside RStudio Connect and is very straightforward. You can check out how to configure this by watching the gif below. Additionally, you can go to official RStudio documentation to read more and use their prepared examples from GitHub to test it yourself.

There is no need to change the RStudio Connect configuration file unless you want to pull code from the private repository. In this case, you will have to specify git credentials for RStudio Connect to use. You can read more about this from the official RStudio documentation on private repos.

Does your Shiny app need a check-up? Discover how to pull Shiny usage data from RStudio Connect

Benefits of building a CI/CD pipeline for Shiny apps

​Setting up CI/CD for your shiny apps with RStudio Connect is simple and straightforward. A CI/CD pipeline can save you the headache of sending a good build to your production environment and ensure its readiness for production release. Consider setting up such a pipeline in your projects and introduce best practices for testing, reviewing, and deploying! Reduce your costs, minimize errors, and free up your developer resources so your team can focus on more important matters. It’s a small investment that will help you develop better Shiny applications and save you a lot of time wasted for manual deployments in the future.

Another way forward

Automated pipelines can significantly reduce dev resources and speed up time to a production-ready launch. But every project is unique and requires some forethought for the right tool. As experts in R/Shiny, we know how to find the right tools for the right project.

Appsilon is a proud RStudio Full Service Certified Partner with a well-balanced team devoted to providing industry-leading solutions to our clients. We can help guide you to find the best solution for a high-quality, enterprise application.