Package Management with renv

Marc Los Huertos

EA030 · Compiled 2026-04-23

Why It Matters How renv Works Initialising The Lockfile Daily Workflow Restoring With GitHub Troubleshooting Quick Reference

🎯 Learning Outcomes

  1. Explain why package version differences cause analyses to break silently or loudly.
  2. Describe what renv records in a lockfile and how the project-local library works.
  3. Initialise renv in an existing RStudio Project with renv::init().
  4. Record package versions with renv::snapshot() after installing or updating.
  5. Restore an identical environment on a new machine with renv::restore().
  6. Commit renv.lock correctly to a GitHub repository.
  7. Diagnose and resolve the three most common renv problems.

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. renv solves this by locking a snapshot of every package version into a plain-text file committed alongside your code.


1. Why Package Versions Matter

The “works on my machine” problem

✗ Without renv

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.

✓ With renv

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.

Real packages that have broken analyses at specific versions

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.


2. How renv Works

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.


3. Initialising renv

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.


4. The Lockfile in Detail

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


5. Daily Workflow

Once renv is initialised, three functions cover almost everything.

check
renv::status()
Is the lockfile in sync with the library? Run any time you are unsure.
record
renv::snapshot()
Update the lockfile to match installed packages. Run after every install or update.
install
renv::restore()
Install packages from the lockfile. Run after cloning or pulling a changed lockfile.
add pkg
renv::install()
Install a package into the project library. renv-aware alternative to install.packages().
upgrade
renv::update()
Update one or all packages to latest CRAN versions. Always snapshot afterwards.

Installing a new package

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"

Checking status

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.

OR: package in lockfile but not installed

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.


6. Restoring an Environment

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.


7. Using renv with GitHub

What to commit

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

A complete two-person workflow

# ── 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.


8. Troubleshooting

Problem 1 — renv::restore() fails with a download error

Error: 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

Problem 2 — Out of sync after git pull

renv::status()     # confirms what is out of sync
renv::restore()    # installs the new or changed packages

Problem 3 — Package installed but 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.

Problem 4 — Adding renv to an existing git repo

# 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

9. Quick Reference

renv at a glance

💬 Check your understanding

  1. A classmate clones your GitHub repository, opens the .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.
  2. You add a new figure requiring the patchwork package. List every step — including the git commands — needed to share this change reproducibly with your collaborators.
  3. Why does renv commit renv/activate.R but gitignore renv/library/? What would go wrong if you committed the library instead?
  4. renv::update() updates packages in the project library. Why should you run your full analysis after updating, before committing the new renv.lock?
  5. You open a project from two years ago. renv::restore() fails because one package version is no longer on CRAN. What are your two options, and what is the tradeoff between them?

References

Ushey, K., and H. Wickham. 2024. renv: Project Environments for R. R package version 1.0. https://rstudio.github.io/renv/