--- title: "Docker and CI workflows" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Docker and CI workflows} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) library(sysreqr) ``` This vignette shows how to use `sysreqr` to generate system-requirement snippets for Docker images and CI pipelines. It assumes basic familiarity with both. See `vignette("linux-fundamentals")` for a primer. ## The simple case: one project, one Dockerfile If you have a project directory and want a Dockerfile snippet that installs its system requirements, pipe `check_project()` into `dockerfile()`. ```{r, eval = FALSE} plan <- check_project(".", platform = "ubuntu-22.04") cat(dockerfile(plan)) ``` The output looks like this: ```dockerfile RUN apt-get update && apt-get install -y --no-install-recommends \ libxml2-dev \ libcurl4-openssl-dev \ libssl-dev \ && rm -rf /var/lib/apt/lists/* ``` Paste it into a `Dockerfile` immediately after the base image, or write it to a side file with `write_dockerfile_snippet()` and `INCLUDE` it from your build pipeline. ## Always pass `platform` for Docker builds When you build a Docker image, the *container's* operating system is what matters, not your laptop. If you build a `rocker/r-ver` image from a Mac, the container is Debian-based. So always supply `platform` explicitly when generating Dockerfile snippets. ```{r} plan <- check_packages("xml2", platform = "ubuntu-22.04") cat(dockerfile(plan)) ``` ## Recommended base images The Rocker Project maintains opinionated R images. | Image | What you get | When to use | |----------------------------|-------------------------------------------------|-----------------------------------| | `rocker/r-ver:` | Versioned R on Debian | Small server-side base | | `rocker/rstudio:` | The above plus RStudio Server | Interactive development | | `rocker/tidyverse:`| `tidyverse` and friends pre-installed | Data-science containers | | `rocker/geospatial:`| The above plus GDAL, PROJ, GEOS, etc. | Spatial work | | `rocker/r-base:` | Plain R from the R Project Debian repository | Quick experiments | `sysreqr` is independent of which base image you pick: it only generates the install commands for the system packages your R packages need. ## A two-stage Docker pattern A common practice is to separate the *build* image (which has compilers and `-dev` headers) from the *runtime* image (which only has runtime libraries). This keeps the deployed image small. ```dockerfile # ---- Stage 1: build ---- FROM rocker/r-ver:4.4 AS build # System build deps (compilers and headers) RUN apt-get update && apt-get install -y --no-install-recommends \ libxml2-dev libcurl4-openssl-dev libssl-dev \ && rm -rf /var/lib/apt/lists/* COPY . /src WORKDIR /src RUN Rscript install-r-dependencies.R # ---- Stage 2: runtime ---- FROM rocker/r-ver:4.4 # Runtime libraries only (note: no -dev suffix) RUN apt-get update && apt-get install -y --no-install-recommends \ libxml2 libcurl4 libssl3 \ && rm -rf /var/lib/apt/lists/* COPY --from=build /usr/local/lib/R/site-library /usr/local/lib/R/site-library COPY --from=build /src /app WORKDIR /app CMD ["R", "--no-save"] ``` `sysreqr::dockerfile()` produces the build-stage block. The runtime block, which uses the non-`-dev` variants, is a manual mirror. ## Pinning Posit Package Manager snapshots For reproducible Docker images, point R at a *dated* PPM snapshot rather than `latest`. ```{r} url <- ppm_repo(platform = "ubuntu-22.04", snapshot = "2026-04-01") url ``` Drop those lines into your Dockerfile by way of `Rscript -e` or an `.Rprofile` written into the image: ```dockerfile RUN echo 'options(repos = c(CRAN = "https://packagemanager.posit.co/cran/__linux__/jammy/2026-04-01"))' \ >> /usr/local/lib/R/etc/Rprofile.site ``` Combined with the system-package install above, this gives bit-for-bit reproducible installs. ## GitHub Actions `github_actions()` (alias `gha()`) generates a YAML step that runs the same install commands inside a GitHub-hosted Ubuntu runner. ```{r} plan <- check_packages(c("xml2", "curl"), platform = "ubuntu-22.04") cat(github_actions(plan)) ``` Paste it into `.github/workflows/.yaml` after the `actions/setup-r` step: ```yaml jobs: R-CMD-check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: r-lib/actions/setup-r@v2 with: use-public-rspm: true - name: Install Linux system dependencies run: | sudo apt-get update sudo apt-get install -y libxml2-dev libcurl4-openssl-dev libssl-dev - uses: r-lib/actions/setup-r-dependencies@v2 with: needs: check ``` If you prefer to let the `r-lib/actions` ecosystem handle system requirements for you, use the `extra-packages` and `needs` inputs of `setup-r-dependencies`. `sysreqr` is then most useful for two things: 1. **Pre-CI auditing**: run `check_project(".")` locally before pushing. 2. **CI for non-R-package projects** (Shiny apps, scripts, reports), where the standard r-lib actions are less of a fit. ## Beyond GitHub Actions The `install_command()` output is portable across CI systems. For GitLab CI: ```yaml test: image: rocker/r-ver:4.4 before_script: - apt-get update - apt-get install -y libxml2-dev libcurl4-openssl-dev libssl-dev script: - Rscript -e 'devtools::check()' ``` For Jenkins, Drone, or shell-driven CI, write the install script once: ```{r, eval = FALSE} write_install_script(plan, file.path(tempdir(), "install-sysreqs.sh")) ``` Then call `sh ci/install-sysreqs.sh` from any CI runner. ## Posit Workbench and Posit Connect Both Posit Workbench and Posit Connect benefit from Posit Package Manager binary R packages, which avoid source compilation entirely for the distributions they support. `use_ppm("user")` writes the `.Rprofile` fragment that points R at the right binary repository. For Connect specifically, server administrators usually configure the repository at the server level, so end users only need the application code, not `.Rprofile` edits. ## See also * `vignette("preflight-setup")` for the basic workflow. * `vignette("linux-fundamentals")` for the system-level concepts. * `vignette("faq")` for common gotchas.