4.2 GitHub Actions
GitHub Actions is a service for running highly-customizable and flexible automated workflows, fully integrated with GitHub and very suitable to CI/CD pipelines. Workflows use YAML
syntax and should be stored in the .github/workflows
directory in the root of the repository. Workflows are constituted of jobs and each job is a set of steps to perform individual tasks, e.g. commands or actions.
The next sections describe in detail the relevant workflow steps of a typical CI/CD pipeline for a packaged Shiny app, also covering the usage of renv
to track package dependencies. Finally, we will show how you can use the convenience function usethis::use_github_action()
for including such workflows in you project.
4.2.1 Workflow steps
A workflow should have an identifying name
and an on
section that indicates upon which events the workflow should be triggered. It should include at least one job and each job will have a set of steps fully specifying what to execute. Such steps can be an action (predefined, sourcing from GitHub repos that contain such actions) or custom shell commands. With the introduction of composite Actions, most of the relevant workflow steps for an R project are covered by actions provided by the r-lib/actions repository.
4.2.1.1 Setup
- Checkout the source package from the repository, using
actions/checkout
provided by GitHub. - Setup R using the action
r-lib/actions/setup-r
. - Install package dependencies (including system requirements and caching) using
r-lib/actions/setup-r-dependencies
.
Using renv
If your project relies on package renv for tracking dependencies via an renv.lock
file, caching and installation of R package dependencies requires a different setup, as described in the Using renv with Continuous Integration vignette. As shown in the complete workflow files below:
- system requirements are installed explicitly (for the
ubuntu
runner defined for the workflow), based onpkgdepends
. - dependencies tracked by renv are installed (including caching) using
r-lib/actions/setup-renv
.
Some remarks about using an renv
setup for a packaged Shiny app and a corresponding CI/CD workflow (see also our chapter ‘Control dependencies with renv
’ for details):
renv
should be configured using"explicit"
snapshots, given that dependencies are stated in theDESCRIPTION
file.- As the
DESCRIPTION
file defines the development dependencies used for CI/CD, make sure they are tracked byrenv
viarenv::snapshot(dev = TRUE)
so they are restored and available in the workflow. - Since the deployment to shinyapps.io is also based on
renv
and would rely on an existingrenv.lock
, we would end up with development dependencies included in the deployment.- To prevent this, we can ignore
renv.lock
during development by creating an.rscignore
text file containingrenv.lock
in the project root (build-ignored viausethis::use_build_ignore(".rscignore")
). - This will cause
rsconnect::deployApp()
to create a newrenv
snapshot with the run-time dependencies only, still relying on the versions restored fromrenv.lock
in the workflow viar-lib/actions/setup-renv
.
- To prevent this, we can ignore
4.2.1.2 Package check
- Check the package using
r-lib/actions/check-r-package
.
4.2.1.3 Deployment
Continuous deployment to shinyapps.io is automated upon any push to the main
(or master
) branch.
In order to provide credentials for the deployment, account name and corresponding tokens for shinyapps.io are defined as environment variables
SHINYAPPS_ACCOUNT
,SHINYAPPS_TOKEN
andSHINYAPPS_SECRET
, specified / accessible as GitHub Actions secrets.Deployment to shinyapps.io is best configured and defined in an R script, e.g.
deploy/deploy-shinyapps.R
(build-ignored viausethis::use_build_ignore("deploy")
), which:- sets up account credentials based on the environment variables for continuous deployment
- deploys the app via
rsconnect::deployApp()
, specifying as files for deployment only what is relevant at run-time: this does not only prevent unnecessary files from being deployed, but also makes sure only run-time dependencies are captured in the deployment
The script would also be used locally for manual deployments (to rely on the defined files), where credentials would be manually configured.
# deploy/deploy-shinyapps.R # usethis::use_build_ignore("deploy") if (!interactive()) { rsconnect::setAccountInfo( Sys.getenv("SHINYAPPS_ACCOUNT"), Sys.getenv("SHINYAPPS_TOKEN"), Sys.getenv("SHINYAPPS_SECRET") ) } # Add here any additional files/directories the app needs app_files = c( "app.R", "DESCRIPTION", "NAMESPACE", "R/", "inst/" ) rsconnect::deployApp( appName = "ShinyCICD", appFiles = app_files, forceUpdate = TRUE )
4.2.2 Workflow file
The steps
described in the previous section are defined in the .yml
workflow file as follows:
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
# Name of the workflow
name: CI-CD
# Create status badge with usethis::use_github_actions_badge("ci-cd.yml")
on:
# Triggered on push and pull request events
push:
pull_request:
# Allow manual runs from the Actions tab
workflow_dispatch:
jobs:
CI-CD:
runs-on: ${{ matrix.config.os }}
name: ${{ matrix.config.os }} (${{ matrix.config.r }})
strategy:
# We keep a matrix for convenience, but we would typically just run on one
# single OS and R version, aligned with the target deployment environment
matrix:
config:
- {os: ubuntu-latest, r: 'release'}
env:
# Access token for GitHub
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
# Preserve package sources for informative references in case of errors
R_KEEP_PKG_SOURCE: yes
# rsconnect needs transitive LinkingTo-only dependencies to be installed
# (see https://github.com/r-lib/pak/issues/485)
PKG_INCLUDE_LINKINGTO: true
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Setup R
uses: r-lib/actions/setup-r@v2
with:
r-version: ${{ matrix.config.r }}
# Enable RStudio Package Manager to speed up package installation
use-public-rspm: true
- name: Install and cache dependencies
uses: r-lib/actions/setup-r-dependencies@v2
with:
extra-packages: any::rcmdcheck
- name: Check package
uses: r-lib/actions/check-r-package@v2
- name: Deploy to shinyapps.io
# Continuous deployment only for pushes to the main / master branch
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
env:
SHINYAPPS_ACCOUNT: ${{ secrets.SHINYAPPS_ACCOUNT }}
SHINYAPPS_TOKEN: ${{ secrets.SHINYAPPS_TOKEN }}
SHINYAPPS_SECRET: ${{ secrets.SHINYAPPS_SECRET }}
run: Rscript deploy/deploy-shinyapps.R
As visible from the run logs that can be found in the GitHub repository under the Actions
tab, all the CI/CD pipeline steps are performed subsequently, and are identifiable by the name
field. See the example below, showing how the deployment step is skipped for a run not triggered by a push action the main
(or master
) branch:
4.2.3 Complete workflows and usethis::use_github_action()
Full YAML workflows for CI and CI/CD pipelines, with and without renv
, are shown below and provided as part of this guide.
In order to setup and use CI/CD GitHub Actions workflows as described above, you can simply include the relevant workflow file your project via:
usethis::use_github_action(url = paste0(
"https://github.com/miraisolutions/techguides/blob/master/",
"shiny-ci-cd/actions/ci-cd.yml"
# "shiny-ci-cd/actions/ci-cd-renv.yml"
# "shiny-ci-cd/actions/ci.yml"
# "shiny-ci-cd/actions/ci-renv.yml"
))
usethis::use_github_actions_badge("ci-cd.yml") # or "ci.yml"/"ci(-cd)-renv.yml"
4.2.3.1 Complete workflow files
shiny-ci-cd/actions/ci-cd.yml
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
# Name of the workflow
name: CI-CD
# Create status badge with usethis::use_github_actions_badge("ci-cd.yml")
on:
# Triggered on push and pull request events
push:
pull_request:
# Allow manual runs from the Actions tab
workflow_dispatch:
jobs:
CI-CD:
runs-on: ${{ matrix.config.os }}
name: ${{ matrix.config.os }} (${{ matrix.config.r }})
strategy:
# We keep a matrix for convenience, but we would typically just run on one
# single OS and R version, aligned with the target deployment environment
matrix:
config:
- {os: ubuntu-latest, r: 'release'}
env:
# Access token for GitHub
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
# Preserve package sources for informative references in case of errors
R_KEEP_PKG_SOURCE: yes
# rsconnect needs transitive LinkingTo-only dependencies to be installed
# (see https://github.com/r-lib/pak/issues/485)
PKG_INCLUDE_LINKINGTO: true
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Setup R
uses: r-lib/actions/setup-r@v2
with:
r-version: ${{ matrix.config.r }}
# Enable RStudio Package Manager to speed up package installation
use-public-rspm: true
- name: Install and cache dependencies
uses: r-lib/actions/setup-r-dependencies@v2
with:
extra-packages: any::rcmdcheck
- name: Check package
uses: r-lib/actions/check-r-package@v2
- name: Deploy to shinyapps.io
# Continuous deployment only for pushes to the main / master branch
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
env:
SHINYAPPS_ACCOUNT: ${{ secrets.SHINYAPPS_ACCOUNT }}
SHINYAPPS_TOKEN: ${{ secrets.SHINYAPPS_TOKEN }}
SHINYAPPS_SECRET: ${{ secrets.SHINYAPPS_SECRET }}
run: Rscript deploy/deploy-shinyapps.R
shiny-ci-cd/actions/ci-cd-renv.yml
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
# Name of the workflow
name: CI-CD-renv
# Create status badge with usethis::use_github_actions_badge("ci-cd-renv.yml")
on:
# Triggered on push and pull request events
push:
pull_request:
# Allow manual runs from the Actions tab
workflow_dispatch:
jobs:
CI-CD:
runs-on: ${{ matrix.config.os }}
name: ${{ matrix.config.os }} (${{ matrix.config.r }})
strategy:
# We keep a matrix for convenience, but we would typically just run on one
# single OS and R version, aligned with the target deployment environment
matrix:
config:
- {os: ubuntu-latest, r: 'renv'}
env:
# Access token for GitHub
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
# Preserve package sources for informative references in case of errors
R_KEEP_PKG_SOURCE: yes
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Setup R
uses: r-lib/actions/setup-r@v2
with:
r-version: ${{ matrix.config.r }}
# No RStudio Package Manager to respect renv.lock
use-public-rspm: false
- name: Install system dependencies
# This is not taken care of (yet) by r-lib/actions/setup-renv
# See https://github.com/r-lib/actions/issues/785
run: |
# We rely on pkgdepends from the library embedded in pak
install.packages("pak", repos = "https://r-lib.github.io/p/pak/stable/")
install.packages("jsonlite")
.libPaths(c(system.file("library", package = "pak"), .libPaths()))
pkgdepends::new_pkg_installation_proposal(
names(jsonlite::read_json("renv.lock")$Packages), config = list(dependencies = FALSE)
)$solve()$install_sysreqs()
shell: Rscript {0}
- name: Activate renv and restore packages with cache
uses: r-lib/actions/setup-renv@v2
- name: Install R CMD check
run: install.packages("rcmdcheck")
shell: Rscript {0}
- name: Check package
uses: r-lib/actions/check-r-package@v2
- name: Deploy to shinyapps.io
# Continuous deployment only for pushes to the main / master branch
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
env:
SHINYAPPS_ACCOUNT: ${{ secrets.SHINYAPPS_ACCOUNT }}
SHINYAPPS_TOKEN: ${{ secrets.SHINYAPPS_TOKEN }}
SHINYAPPS_SECRET: ${{ secrets.SHINYAPPS_SECRET }}
run: Rscript deploy/deploy-shinyapps.R
shiny-ci-cd/actions/ci.yml
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
# Name of the workflow
name: CI
# Create status badge with usethis::use_github_actions_badge("ci.yml")
on:
# Triggered on push and pull request events
push:
pull_request:
# Allow manual runs from the Actions tab
workflow_dispatch:
jobs:
CI-CD:
runs-on: ${{ matrix.config.os }}
name: ${{ matrix.config.os }} (${{ matrix.config.r }})
strategy:
# We keep a matrix for convenience, but we would typically just run on one
# single OS and R version, aligned with the target deployment environment
matrix:
config:
- {os: ubuntu-latest, r: 'release'}
env:
# Access token for GitHub
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
# Preserve package sources for informative references in case of errors
R_KEEP_PKG_SOURCE: yes
# rsconnect needs transitive LinkingTo-only dependencies to be installed
# (see https://github.com/r-lib/pak/issues/485)
PKG_INCLUDE_LINKINGTO: true
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Setup R
uses: r-lib/actions/setup-r@v2
with:
r-version: ${{ matrix.config.r }}
# Enable RStudio Package Manager to speed up package installation
use-public-rspm: true
- name: Install and cache dependencies
uses: r-lib/actions/setup-r-dependencies@v2
with:
extra-packages: any::rcmdcheck
- name: Check package
uses: r-lib/actions/check-r-package@v2
shiny-ci-cd/actions/ci-renv.yml
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
# Name of the workflow
name: CI-renv
# Create status badge with usethis::use_github_actions_badge("ci-renv.yml")
on:
# Triggered on push and pull request events
push:
pull_request:
# Allow manual runs from the Actions tab
workflow_dispatch:
jobs:
CI-CD:
runs-on: ${{ matrix.config.os }}
name: ${{ matrix.config.os }} (${{ matrix.config.r }})
strategy:
# We keep a matrix for convenience, but we would typically just run on one
# single OS and R version, aligned with the target deployment environment
matrix:
config:
- {os: ubuntu-latest, r: 'renv'}
env:
# Access token for GitHub
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
# Preserve package sources for informative references in case of errors
R_KEEP_PKG_SOURCE: yes
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Setup R
uses: r-lib/actions/setup-r@v2
with:
r-version: ${{ matrix.config.r }}
# No RStudio Package Manager to respect renv.lock
use-public-rspm: false
- name: Install system dependencies
# This is not taken care of (yet) by r-lib/actions/setup-renv
# See https://github.com/r-lib/actions/issues/785
run: |
# We rely on pkgdepends from the library embedded in pak
install.packages("pak", repos = "https://r-lib.github.io/p/pak/stable/")
install.packages("jsonlite")
.libPaths(c(system.file("library", package = "pak"), .libPaths()))
pkgdepends::new_pkg_installation_proposal(
names(jsonlite::read_json("renv.lock")$Packages), config = list(dependencies = FALSE)
)$solve()$install_sysreqs()
shell: Rscript {0}
- name: Activate renv and restore packages with cache
uses: r-lib/actions/setup-renv@v2
- name: Install R CMD check
run: install.packages("rcmdcheck")
shell: Rscript {0}
- name: Check package
uses: r-lib/actions/check-r-package@v2