Skip to content
ricochet

Dependency Management

Deploying a content item to ricochet follows the same lifecycle regardless of language:

  1. The bundle is uploaded to the REST API.

  2. The bundle is unpacked into the item’s source code.

  3. The content environment’s dependencies are installed.

  4. If the environment restores successfully, the deployment succeeds.

R content items use renv to restore the package environment.

When using the standalone installers or Helm charts, renv is vendored from RICOCHET_HOME/vendor/r/renv.tar.gz.

renv is restored with the following commands (abbreviated for brevity):

renv::init(bare = TRUE)
renv::record(sprintf("renv@%s", as.character(packageVersion("renv"))))
lock <- renv::lockfile_read()
lock$R$Version <- sprintf("%s.%s", R.version$major, R.version$minor)
renv::lockfile_write(lock)
renv::restore(prompt = FALSE)
renv::activate()

The vendored renv version is recorded to prevent renv from bootstrapping an out-of-date, incompatible version of itself for the content item. The R version that runs the restore is also recorded, so a different R version than the one in renv.lock does not mark the project as out of sync.

On startup, ricochet copies the tarball to RICOCHET_HOME/.cache/vendor/r/renv.tar.gz. This avoids masking from the PVC path in Kubernetes.

  1. ricochet reads the requested R version from the renv.lock file and finds the closest matching version of R on the host.

  2. For that R version, ricochet checks whether renv is installed at RICOCHET_HOME/r/{major}.{minor}. On the Kubernetes backend, it checks RICOCHET_HOME/r/{major}.{minor}/{os}/{arch}/.

  3. If renv is not yet installed, ricochet installs it while holding a lock, ensuring no other R process contends for the same installation location.

  4. Once renv is installed, the dependency restore continues.

In Kubernetes environments, ricochet detects all available node types and runs the restore job for both amd64 and arm64 architectures when both are present. The restore succeeds only if it succeeds on both architectures.

Julia content items are restored as follows:

  1. ricochet vendors juliaup to detect Julia versions.

  2. The requested Julia version is determined from the Manifest.toml file.

  3. Packages are installed by running the command below.

Terminal window
julia --quiet --history-file=no --project=. -e "using Pkg; Pkg.instantiate()"

The JULIA_DEPOT_PATH is stored at RICOCHET_HOME/.julia.

Python content items are restored with uv:

  1. The requested Python version is determined from the .python-version file.

  2. Packages are synced by running the command below.

Terminal window
uv sync --python {python_path} --no-python-downloads --locked

The following environment variables are set to limit resource usage:

  • UV_CONCURRENT_DOWNLOADS: 8
  • UV_CONCURRENT_INSTALLS: 4
  • UV_CONCURRENT_BUILDS: 2

A restore that fails because the lockfile disagrees with the project’s declared dependencies ends with the status Out of sync instead of Failure. Network, cache, and process errors still end with Failure.

LanguageA mismatch is reported when
Rrenv reports the project as out of sync during the restore
JuliaPkg.instantiate() needs to re-resolve or would rewrite Manifest.toml
Pythonuv sync --locked finds that uv.lock does not match pyproject.toml

For Julia, ricochet puts the uploaded Manifest.toml back before failing the restore.

An R restore that succeeds but logs drift, such as a package that is installed but not recorded in renv.lock, still deploys. The app and deployment pages then show an Out of sync badge that links to the matching lines of the restore log.

To clear a mismatch, the author updates the lockfile in the source project and redeploys.

Before spawning any apps, documents, or scripts, ricochet verifies that the packages recorded in the lockfile are installed. This is done through a preflight dependency check, shown below for each language.

user_lib <- Sys.getenv("R_LIBS_USER")
if (!requireNamespace("renv", quietly = TRUE)) {
install.packages("RICOCHET_HOME/.cache/vendor/r/renv.tar.gz", repos = NULL, lib = user_lib)
}
renv::load()
status <- renv::status()
missing <- setdiff(names(status$lockfile$Packages), names(status$library$Packages))
if (length(missing)) {
stop("Packages recorded in renv.lock are not installed: ", paste(missing, collapse = ", "))
}

If a check fails, the deployment is invalidated and a new deployment is triggered. If the check fails a second time, no further action is taken, avoiding an endless redeploy cycle.

ricochet applies the same compatible-version-matching logic across all languages.

The requested version is first determined from your project’s lockfile (renv.lock, Manifest.toml, or .python-version). ricochet then discovers the interpreters installed on your host. It uses internal logic for R, juliaup for Julia, and uv for Python.

Matching is best-effort. If the exact requested version isn’t available, ricochet resolves to the closest compatible one rather than failing. Versions are selected in the following order:

  1. Exact match. If the requested version is available, it is used.

  2. Nearest within the same major.minor release. Otherwise, the highest available version that is at or below the requested version.

  3. Nearest available. If none qualify, the closest available version.

  4. Newer major requested. If the requested version’s major is ahead of everything available, the largest available version is used.