Prof. Marc Los Huertos • Pomona College • Environmental Analysis
Why It Matters How renv Works Initialising The Lockfile Daily Workflow Restoring With GitHub Troubleshooting Quick Reference
renv::init().
renv::snapshot() after
installing or updating.
renv::restore().
renv.lock correctly to a GitHub repository.
What this module covers. Your R analysis depends on dozens of packages, each of which changes with every update. An analysis that runs perfectly today may produce different results — or fail outright — when a collaborator opens it on a different machine, or when you return six months later after updating R.
renvsolves this by locking a snapshot of every package version into a plain-text file committed alongside your code.
You finish your PM2.5 analysis using dplyr 1.1.4. A
classmate has dplyr 1.0.8. The .by = argument
— added in 1.1.0 — causes an error they cannot explain. They spend an
afternoon debugging code that is actually correct.
Or worse: the
analysis runs but produces different numbers because a statistical
default changed between versions. Neither of you notices.
Your project contains renv.lock recording the exact
version of every package. A classmate runs renv::restore()
after cloning your repo. renv installs the exact versions you used and
the analysis runs identically.
The lockfile is a few kilobytes
of plain text committed to GitHub alongside your code.
| Package | Breaking version | What changed |
|---|---|---|
dplyr |
1.1.0 | .by = argument added; group_by() scoping
tightened |
ggplot2 |
3.4.0 | size aesthetic split into linewidth +
size for lines |
lubridate |
1.9.0 | as_date() handling of POSIXct with tz changed |
vegan |
2.6.0 | adonis() deprecated in favour of
adonis2() |
⚠️ Silent version breakage is the most dangerous kind. When a change causes an error you notice immediately and fix it. When it silently shifts a default argument — a different sort order, a reversed factor level — your results change without any indication that something is wrong. Locking package versions prevents both.
renv adds three things to your RStudio Project:
your-project/
├── data/
├── R/
├── your-project.Rproj
├── renv.lock <– records
every package + version
├── .Rprofile <–
activates renv when the project opens
└── renv/
├── activate.R <– bootstrap
script
├── library/ <–
project-local packages (gitignored)
└──
.gitignore <– keeps library out of git
automatically
The project-local library. Normally all packages
share one folder on your machine. renv gives each project its own
renv/library/ so packages installed for project A never
affect project B.
The lockfile. renv.lock is a JSON file
recording the name, version, and source of every package. It is the only
file you need to commit to reproduce the environment elsewhere.
The cache. renv maintains a global cache at
~/.cache/R/renv/. When two projects use the same package
version renv hard-links rather than copies the files, so disk usage
stays manageable even with many projects.
📌 Commit the lockfile, not the library.
renv/library/ is automatically added to
.gitignore. Committing compiled binaries would bloat your
repository by hundreds of megabytes. The lockfile alone — typically 5–50
KB — is everything needed to recreate the environment on another
machine.
renv works inside an RStudio Project
(.Rproj file). If your analysis is not already in a
project, create one first: File → New Project → Existing
Directory. See the Project Structure module for details.
# Install renv once per machine
install.packages("renv")
# Initialise renv in the current project
renv::init()
# Console output from renv::init() * Initializing project … * Discovering package dependencies … Done! * Copying packages into the cache … [33 packages] Done! # CRAN ——————————————————- - dplyr [* -> 1.1.4] - ggplot2 [* -> 3.5.1] - lubridate [* -> 1.9.3] - vegan [* -> 2.6-6.1] … (29 more packages) * Lockfile written to ‘~/my-project/renv.lock’.
renv::init() scans all .R and
.Rmd files for library() calls, records the
installed version of every package found, and writes
renv.lock. After running it, restart R
(Session → Restart R) so the new .Rprofile
activates the project library.
💡 Initialise early, not at the end of a project. The best time is right after creating the RStudio Project, before installing any packages. The lockfile will then always match exactly what your project uses, with no orphaned packages from other work on your machine.
renv.lock is plain JSON you can open in any text
editor.
# renv.lock (simplified excerpt)
{
“R”: {
“Version”: “4.4.1”,
<– R version is recorded too
“Repositories”: [{ “Name”: “CRAN” }]
},
“Packages”: {
“dplyr”: {
“Package”: “dplyr”,
“Version”: “1.1.4”,
<– exact version pinned
“Source”: “Repository”,
“Hash”: “fedd9d03c235d5a3ed7a05ee28588dbb” <– integrity check
}
}
}
Three fields matter most: Version pins the exact release, Source distinguishes CRAN from GitHub packages, and Hash is a cryptographic fingerprint so renv can verify what it downloads is byte-for-byte identical to what you had. For GitHub packages renv records the exact commit SHA:
“RemoteSha”: “d7a4f3c19b2a…” <– exact commit, not just a version tag
Once renv is initialised, three functions cover almost everything.
renv::install("viridis") # preferred — renv-aware
# install.packages("viridis") also works; renv intercepts it
renv::snapshot() # record the new version in the lockfile
# renv::snapshot() output The following package(s) will be updated in the lockfile: # CRAN ——————————————————- - viridis [* -> 0.6.5] - viridisLite [* -> 0.4.2] * Lockfile written to ‘~/my-project/renv.lock’.
After every snapshot() commit renv.lock to
git:
# git add renv.lock
# git commit -m "renv: add viridis 0.6.5"
renv::status()
* The project is already synchronized with the
lockfile.
# OR: package installed
but not yet recorded The following package(s) are installed but not
recorded in the lockfile: viridis * Use renv::snapshot() to update the
lockfile.
The following package(s) are recorded in the lockfile but not installed: viridis * Use renv::restore() to install these packages.
⚠️ Never run update.packages() inside an renv
project. That updates the system library, not the project
library, and can silently break things. Always use
renv::update() instead. Before updating, run your full
analysis on a branch, check results, then merge and snapshot.
renv::restore() is what your collaborators run after
cloning the project. It reads renv.lock and installs the
exact package versions recorded.
# After cloning the repo and opening the .Rproj file:
renv::restore()
The following package(s) will be updated: # CRAN ——————————————————- - dplyr [1.0.8 -> 1.1.4] - ggplot2 [3.3.6 -> 3.5.1] - lubridate [1.8.0 -> 1.9.3] … (31 more packages) * Package database updated.
renv checks the global cache first. If the required version is
already cached from another project no download is needed and
restoration takes seconds. The bootstrap:
renv/activate.R is run by .Rprofile whenever
someone opens your project. If renv is not installed on their machine it
downloads and installs itself automatically, then prompts them to run
renv::restore(). Collaborators do not need to know about
renv at all — opening the .Rproj is enough.
| Path | Commit? | Reason |
|---|---|---|
renv.lock |
Yes — always | The reproducibility record |
.Rprofile |
Yes | Activates renv when the project opens |
renv/activate.R |
Yes | Bootstrap for new machines |
renv/.gitignore |
Yes | Keeps the library out of git |
renv/library/ |
No | Auto-gitignored; too large; platform-specific |
# ── PERSON A: starting the project ──────────────────────────────
renv::init()
# ... install packages, do analysis ...
renv::snapshot()
# git add renv.lock .Rprofile renv/activate.R renv/.gitignore
# git commit -m "renv: initialise project environment"
# git push
# ── PERSON B: picking up the project ────────────────────────────
# git clone https://github.com/marclos/RTricks.git
# Open RTricks.Rproj in RStudio
renv::restore() # installs exact versions from renv.lock
# ── PERSON A: adding a package ──────────────────────────────────
renv::install("patchwork")
# ... use patchwork in analysis ...
renv::snapshot()
# git add renv.lock
# git commit -m "renv: add patchwork 1.3.0"
# git push
# ── PERSON B: pulling the update ────────────────────────────────
# git pull
renv::restore() # installs patchwork 1.3.0 to match lockfile
💡 Keep lockfile changes in their own commit. A
message like “renv: add patchwork 1.3.0” makes the package
history easy to scan in the git log and easy to revert if an update
breaks something.
renv::restore() fails with a download
errorError: failed to download 'dplyr' [version 1.0.8]
CRAN archives older versions but occasionally removes them.
# Option A: install from the CRAN archive directly
renv::install("dplyr@1.0.8")
# Option B: accept the current CRAN version and update the lockfile
renv::update("dplyr")
renv::snapshot() # test that your analysis still runs correctly first
git pullrenv::status() # confirms what is out of sync
renv::restore() # installs the new or changed packages
library() says “no
package called…”.libPaths() # check which library R is searching
renv::project() # should print your project path, not NULL
# If NULL: renv is not active — restart R inside the .Rproj
# Session --> Restart R
📌 renv only activates when you open the .Rproj
file. If you open analysis.R directly, the
.Rprofile in the project root does not run, renv is not
activated, and R uses the system library. Always open projects via
File → Open Project or by double-clicking the
.Rproj file.
# After renv::init(), stage only these specific files:
# git add renv.lock .Rprofile renv/activate.R renv/.gitignore
# git commit -m "renv: add package management"
# Do NOT run: git add renv/
# That would stage the library before git sees .gitignore
install.packages(“renv”)
then renv::init()
renv::snapshot() then commit renv.lock
renv::restore()
renv::status()
renv::install(“pkgname”) or
renv::install(“user/repo”)
renv::update(“pkgname”)
then renv::snapshot()
renv::install(“dplyr@1.1.4”)
renv::project()
renv.lock,
.Rprofile, renv/activate.R,
renv/.gitignore
renv/library/
.Rproj, and gets an renv prompt. They have never used renv
before. Walk them through exactly what to run to get the project working
on their machine.
patchwork package. List
every step — including the git commands — needed to share this change
reproducibly with your collaborators.
renv/activate.R but gitignore
renv/library/? What would go wrong if you committed the
library instead?
renv::update() updates packages in the project library. Why
should you run your full analysis after updating, before committing the
new renv.lock?
renv::restore()
fails because one package version is no longer on CRAN. What are your
two options, and what is the tradeoff between them?
Ushey, K., and H. Wickham. 2024. renv: Project Environments for R. R package version 1.0. https://rstudio.github.io/renv/