renv-cache
Configure R for development in VS Code, including library paths, GitHub token management, and package restoration via renv cache. Packages are installed once during the image build and reused on every container start, dramatically speeding up rebuilds.
Example Usage
{
"image": "ghcr.io/rocker-org/devcontainer/r-ver:4.4",
"features": {
"ghcr.io/MiguelRodo/DevContainerFeatures/renv-cache:1": {
"repositories": "MiguelRodo/projr@main"
}
}
}With a custom renv directory and selective package exclusion:
{
"features": {
"ghcr.io/MiguelRodo/DevContainerFeatures/renv-cache:1": {
"repositories": "myUsername/repo1,myUsername/repo2@dev:shiny",
"pkg": "dplyr,ggplot2",
"createUnifiedLockfile": "true",
"purgePostUnification": "keep-one",
"pkgExclude": "problematicPkg"
}
}
}Options
| Options Id | Description | Type | Default Value |
|---|---|---|---|
| repositories | Comma-separated list of GitHub repos whose renv.lock files it will restore, e.g. myUsername/repo1,myUsername/repo2. By default, uses the renv.lock file at the root of the repository. Can specify branch (e.g., myUsername/repo1@branch), renv profile (e.g., myUsername/repo1:renvProfile) or both (e.g., myUsername/repo1@branch:renvProfile) |
string | - |
| pkg | Comma-separated list of specific packages to cache explicitly. | string | - |
| pkgExclude | Comma-separated list of packages to exclude from the renv snapshot restore process. | string | - |
| restore | Whether to run package restoration using renvvv::renvvv_restore(). Default is true. | boolean | true |
| update | Whether to run package update using renvvv::renvvv_update(). If both restore and update are true, renvvv::renvvv_restore_and_update() is used. Default is false. | boolean | false |
| createUnifiedLockfile | Whether to create a single unified renv.lock file combining dependencies. ‘auto’ creates it only if multiple lockfiles or explicit packages are provided. ‘true’ always creates it. ‘false’ skips creation. |
string | auto |
| purgePostUnification | Determines which package versions to keep when purging the global cache to save disk space (if a unified lockfile is created). ‘none’ disables purging. ‘keep-one’ retains the update-based lockfile packages if update is true and the restore-based lockfile otherwise. ‘keep-both’ retains both. If no unified lockfile is create, this option is ignored. |
string | none |
| setRLibPaths | Whether to set default paths for R libraries (including for renv) to avoid needing to reinstall upon codespace rebuild. |
boolean | true |
| overrideTokensAtInstall | If true, temporarily override GITHUB_TOKEN and set GITHUB_PAT from the best available token (priority: GITHUB_PAT > GH_TOKEN > GITHUB_TOKEN) during the renv package install phase. Tokens are reset to their original values after install completes. Disable if you want to manage tokens manually. | boolean | true |
| lockfileDir | Path to the directory containing subdirectories with renv.lock files. Defaults to /usr/local/share/renv-cache/lockfiles. |
string | /usr/local/share/renv-cache/lockfiles |
| usePak | Whether to use pak for package installation. For some reason, restoring from the renv cache has not worked when using pak to build the image, so this is not recommended. |
boolean | false |
| debugRenv | Whether to use verbose renv output. Sets RENV_CONFIG_INSTALL_VERBOSE and RENV_VERBOSE to true when enabled. |
boolean | false |
| installSystemRequirements | Uses the Posit API to install apt-dependencies. | boolean | true |
| cranMirror | - | string | https://cloud.r-project.org |
How It Works
1. R Configuration Files Modified
The feature modifies the site-wide Renviron.site file during the build process. This file is located at $R_HOME/lib/R/etc/Renviron.site (typically /usr/local/lib/R/etc/Renviron.site or similar depending on R installation).
Two different configurations are applied at different stages:
During Image Build (scripts/r-lib.sh):
RENV_PATHS_ROOT=/renv/local
RENV_PATHS_LIBRARY_ROOT=/workspaces/.local/lib/R/library
RENV_PATHS_CACHE=/renv/cache
R_LIBS=/workspaces/.local/lib/RAfter Container Creation (scripts/r-lib-update.sh):
RENV_PATHS_ROOT=/workspaces/.local/renv
RENV_PATHS_LIBRARY_ROOT=/workspaces/.local/lib/R/library
RENV_PATHS_CACHE=/workspaces/.cache/renv:/renv/cache
R_LIBS=/workspaces/.local/lib/R2. Image Build Phase
When the container image is built:
Renviron.siteis configured with initial paths byscripts/r-lib.sh.- Directories are created for the renv project root during build (
/renv/local), the global package cache (/renv/cache), the library root (/workspaces/.local/lib/R/library), and the pak cache directory (/workspaces/.cache/R/pkgcache/pkg). - Packages are installed from multiple potential sources. These sources include
renv.lockfiles located in subdirectories of thelockfileDir(default:/usr/local/share/renv-cache/lockfiles), remote GitHub repositories specified via therepositoriesoption, and explicit package strings specified via thepkgoption. For each source, packages are restored usingrenvvv::renvvv_restore()(orrenvvv_update()/renvvv_restore_and_update()based on options). Installed packages are automatically cached in/renv/cachedue to theRENV_PATHS_CACHEsetting. - A Unified Lockfile is Generated (Optional/Auto). By default (when
createUnifiedLockfileis set toauto), if you provided multiple lockfiles or specified explicit packages, the feature aggregates all unique package dependencies and performs a combinedrenv::restore(clean = FALSE)followed by arenv::snapshot(). This creates a single, masterrenv.lockcontaining the union of all dependencies. Both this combined lockfile and the individual project lockfiles are saved to an internal container cache (/usr/local/share/renv-cache/lockfiles). - Unused Package Versions are Purged (Optional). If
purgePostUnificationis set to"keep-one"or"keep-both"(andcreateUnifiedLockfileevaluates totrue), the feature scans the global package cache and deletes all package versions except the exact ones tracked by the final unified environment to minimize image size. - Cache permissions are set via environment variables to ensure proper access for the runtime user.
3. Container Runtime Phase
When the container starts:
Renviron.siteis updated byscripts/r-lib-update.sh(called during post-create).RENV_PATHS_CACHEis updated to/workspaces/.cache/renv:/renv/cache. This creates a two-level cache: workspace-specific cache first, then the built-in cache as fallback.RENV_PATHS_ROOTpoints to/workspaces/.local/renvfor workspace-specific project data.- Packages from the build cache are automatically available. When renv needs a package, it first checks
/workspaces/.cache/renv. If not found, it checks/renv/cache(populated during build). If found in either cache, renv links the package instead of reinstalling.
The renv-cache-restore CLI
This feature also installs a built-in CLI tool, renv-cache-restore, which serves as a robust wrapper around renvvv::renvvv_restore(). It handles environment activation, ensures the correct version of renv is synced, and provides advanced cache-targeting capabilities.
Basic usage:
# Restore the project in the current directory
renv-cache-restore
# Restore a project in a specific directory
renv-cache-restore -d ./my-projectForced Cache Usage
If you want to ensure the fastest possible initialization times without hitting external networks (like CRAN or Bioconductor), you can use the --force-cache-version flag.
renv-cache-restore -c
# or
renv-cache-restore --force-cache-versionHow it works: When this flag is passed, the script temporarily hot-swaps your renv.lock file in memory before the restoration begins. It scans the container’s global package cache for standard CRAN and Bioconductor packages. If your lockfile specifies a version of a package not available in the cache but the cache contains other version(s) of that package, then it forcibly injects the highest-available version and cache hash into the lockfile.
This improves usage of the cache. The original renv.lock file is automatically rolled back to its unmodified state immediately after the restoration process finishes. Run renv::snapshot() if you are happy with the updated versions afterwards.
(Note: This forced cache override ignores GitHub, Git, and Local package sources).
Other useful options: * -u, --update: Run renvvv_update() alongside or instead of a standard restore. * -e, --exclude <pkg1,pkg2>: Temporarily skip specific packages during the restoration process. * -p, --pak: Enable the pak backend for this specific restore.`
The renv-cache-init CLI
If you are starting a brand new project, or migrating an existing project that doesn’t have an renv.lock file yet, you can use the renv-cache-init tool to bootstrap your environment end-to-end.
Basic usage:
# Initialize the project in the current directory
renv-cache-init
# Initialize a project in a specific directory
renv-cache-init -d ./my-new-projectHow it works: Instead of blindly downloading the latest packages from CRAN, this script acts as a “Cache-First Bootstrapper”:
- Scaffolds Infrastructure: It automatically sets up the basic
renvinfrastructure (renv/activate.R,.Rprofile) if it isn’t already present. - Scans Dependencies: It uses
renv::dependencies()to discover exactly which packages your project depends on. - Cross-References the Cache: It matches the list of discovered packages against the container’s global package cache.
- Prioritizes Cached Versions: If a required package is found inside the cache, it explicitly installs that specific cached version to save time and bandwidth. If a package is completely missing from the cache, it gracefully falls back to downloading and installing the latest available version from CRAN or Bioconductor.
- Locks the Environment: Once the local project library is fully populated, it triggers a fresh
renv::snapshot()to generate an up-to-daterenv.lockfile for your workspace.
The renv-cache-copy-lockfile CLI
This feature installs a built-in CLI tool, renv-cache-copy-lockfile, which allows you to easily copy the cached renv.lock files out of the internal cache and into your workspace.
By default, it copies the unified, combined lockfile (containing all dependencies from all projects and repos) to your current directory.
Basic usage:
# Copy the unified lockfile to your current directory
renv-cache-copy-lockfile
# Copy the unified lockfile to a specific path
renv-cache-copy-lockfile ./my-project/renv.lock--list: Lists all available cached projects (including the defaultrenv-cache-overallcombined lockfile and any specific remote repos/local subdirectories) and whether they have updated versions available.-p, --project <name>: Copy a specific project’s lockfile instead of the overall combined one (e.g.,-p M72_CITEseqHIVE_scRNAseq_Pipeline).--update: Ifupdate: truewas set in the feature options, prefer copying the updated lockfile over the originally restored one.--overwrite: Overwrite the destination file if it already exists.
How to Use This Feature
To cache your dependencies during the image build phase, the feature needs access to your lockfiles before the VS Code workspace is mounted. You can provide these lockfiles using local directories, remote repositories, or a combination of both.
Method A: Using Local Lockfiles
If you have local renv.lock files, you must copy them into the image using a minimal Dockerfile so the feature can see them during the build. First, organize your lockfiles in a .devcontainer/renv/ directory (e.g., .devcontainer/renv/project1/renv.lock). Next, create a Dockerfile in your .devcontainer folder that copies this directory into the feature’s default internal path:
FROM bioconductor/bioconductor_docker:RELEASE_3_21-r-4.5.2
# Copy lockfiles so the feature can process them during the build
COPY .devcontainer/renv /usr/local/share/renv-cache/lockfilesFinally, reference this Dockerfile and the feature in your devcontainer.json:
{
"build": {
"dockerfile": "Dockerfile"
},
"features": {
"ghcr.io/MiguelRodo/DevContainerFeatures/renv-cache:1": {}
}
}Method B: Using Remote Repositories
If your dependencies are defined in remote GitHub repositories, you do not need a custom Dockerfile. You can use a standard image and pass the repository targets directly to the feature via the repositories option:
{
"image": "bioconductor/bioconductor_docker:RELEASE_3_21-r-4.5.2",
"features": {
"ghcr.io/MiguelRodo/DevContainerFeatures/renv-cache:1": {
"repositories": "MiguelRodo/projr@main,MiguelRodo/renvvv"
}
}
}By default, the feature assumes there is a standard renv.lock file located at the root of the cloned repository.
Targeting Branches and Profiles
You can target specific branches or renv profiles within your remote repositories using the syntax user/repo@branch:profile. If you append @branch (e.g., MiguelRodo/projr@v2), the feature will clone that specific branch instead of the default branch. If you append :profile (e.g., MiguelRodo/projr:dev), the feature will restore using that specific renv profile, expecting the lockfile to be located at renv/profiles/<profile>/renv.lock. You can combine both options using the full syntax (e.g., MiguelRodo/projr@main:dev).
Final Step: Initialize Your Workspace
Once the container finishes building and starts, your dependencies are securely cached inside the image. To activate them in your current workspace, run the copy command to extract the pre-warmed, unified lockfile and restore the project:
renv-cache-copy-lockfile
Rscript -e "renv::restore()"Package Restoration
This feature uses renvvv::renvvv_restore() for package restoration instead of the default renv::restore(). This provides more robust restoration logic that continues past individual package failures, retries failed packages individually, and reports what couldn’t be installed. It also handles GitHub, CRAN, and Bioconductor packages while providing better error recovery. Additionally, it supports skipping specific packages via the pkgExclude option. The renvvv package is automatically installed during feature setup.
Skipping Packages
You can exclude specific packages from being restored by using the pkgExclude option with a comma-separated list of package names:
"features": {
"ghcr.io/MiguelRodo/DevContainerFeatures/renv-cache:1": {
"pkgExclude": "package1,package2,package3"
}
}This is useful when certain packages fail to install in your environment, you want to manually manage specific package versions, or some packages are simply not needed for your workflow.
Unified Lockfile Control
By default, the feature intelligently decides whether to create a single, master renv.lock file based on how many environments you pass it. You can explicitly override this behavior using the createUnifiedLockfile option:
"auto"(default): Creates the unified lockfile only if multiple sources (e.g., multiple lockfiles across local subdirectories or remotes) or explicit packages viapkgare provided."true": Always creates a unified lockfile, even if you only provided a single project."false": Disables unified lockfile generation entirely.
If disabled, the renv-cache-copy-lockfile CLI tool will automatically default to serving the lockfile of your single cached project.
Cache Size Optimization (Purging)
When combining multiple projects with conflicting package versions, or heavily using the pkgExclude option to strip out certain dependencies, the global renv cache inside the Docker image can become bloated with unused package versions.
To minimize the size of your final Docker image, you can enable post-unification purging:
"features": {
"ghcr.io/MiguelRodo/DevContainerFeatures/renv-cache:1": {
"purgePostUnification": "keep-one"
}
}Note on purging strategies: *
"keep-one": Retains only the updated package versions (ifupdateistrue) or the originally restored versions (ifupdateisfalse). *"keep-both": Safely retains both the original and updated versions of the packages in the cache. *"none": Disables purging entirely. The default.
GitHub Token Management
Build-Time Token Override (overrideTokensAtInstall)
During the image build phase, renv-cache temporarily overrides GitHub authentication tokens so that renv package installation can authenticate with GitHub. This is controlled by the overrideTokensAtInstall option (default: true).
What it does:
- Saves the current values of
GITHUB_TOKENandGITHUB_PAT - Sets
GITHUB_PATfrom the best available token (priority:GITHUB_PAT>GH_TOKEN>GITHUB_TOKEN) if not already set - Overrides
GITHUB_TOKENwith the most permissive token available (priority:GITHUB_PAT>GH_TOKEN) so R tools find it first - Runs the renv package restore/install
- Resets
GITHUB_TOKENandGITHUB_PATto their original values (or unsets them if they were not set before)
This is a simple, blunt approach: it applies only during the feature install step and has no persistent effect on the container.
To opt out:
{
"features": {
"ghcr.io/MiguelRodo/DevContainerFeatures/renv-cache:1": {
"overrideTokensAtInstall": false
}
}
}Session-Time Token Management
If you also want GitHub token elevation on every shell startup (e.g., for interactive R sessions), use the companion github-tokens feature:
{
"features": {
"ghcr.io/MiguelRodo/DevContainerFeatures/renv-cache:1": {},
"ghcr.io/MiguelRodo/DevContainerFeatures/github-tokens:1": {}
}
}Acknowledgments
This project incorporates code from AwesomeProject, which is licensed under the MIT License.