R and DevOps: Reproducible Pipelines in 2026
The intersection of R and DevOps has matured significantly. What once required fragile shell scripts now has elegant solutions. This guide covers the modern DevOps toolkit for R developers in 2026.
Why DevOps Matters for R
Data science workflows often suffer from the “it works on my machine” problem. R projects depend on specific package versions, system libraries, and R itself. DevOps practices solve this through containerization, automated testing, and reproducible environments.
Docker: Containerizing Your R Work
Docker has become essential for portable, reproducible R applications.
Writing a Dockerfile for R
FROM r-base:4.3.2
RUN apt-get update && apt-get install -y \
libcurl4-openssl-dev \
libssl-dev \
libxml2-dev \
libfontconfig1-dev \
libgit2-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /home/rstudio
COPY . .
RUN Rscript -e "renv::restore()"
EXPOSE 8000
CMD ["Rscript", "app.R"]
Multi-stage Builds
FROM r-base:4.3.2 AS builder
COPY . .
RUN R CMD build .
FROM r-base:4.3.2
COPY --from=builder /home/rstudio/*.tar.gz .
RUN R CMD INSTALL *.tar.gz
renv: Reproducible Environments
The renv package has become the standard for R project reproducibility.
renv::init()
renv::snapshot()
This creates renv.lock which pins every package version. Use renv::restore() to recreate the exact environment on any machine.
GitHub Actions for R
name: R CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: r-lib/actions/setup-r@v2
with:
r-version: 4.3.2
- name: Cache renv
uses: actions/cache@v4
with:
path: ~/.local/share/renv
key: renv-${{ hashFiles(renv.lock) }}
- run: Rscript -e "renv::restore()"
- run: Rscript -e "testthat::test_file(\"tests/testthat.R\")"
Multi-Version Testing
strategy:
matrix:
r-version: [4.1.3, 4.2.3, 4.3.2]
targets: Pipeline Orchestration
The targets package creates computational graphs that only re-run what changes.
library(targets)
tar_option_set(packages = c("tidyverse", "ggplot2"))
list(
tar_target(data_file, "data/raw.csv", format = "file"),
tar_target(raw_data, read_csv(data_file)),
tar_target(cleaned_data, raw_data |> filter(!is.na(value))),
tar_target(model, lm(value ~ ., data = cleaned_data)),
tar_target(plot, ggplot(cleaned_data, aes(x, y)) + geom_point())
)
Run with tar_make() — it only recomputes what changed.
Continuous Deployment
Deploy Shiny to Posit Connect
- name: Deploy
run: Rscript -e
"library(rsconnect); rsconnect::deployApp(
appDir = \".\",
account = \"${{ secrets.CONNECT_ACCOUNT }}\",
token = \"${{ secrets.CONNECT_TOKEN }}\",
secret = \"${{ secrets.CONNECT_SECRET }}\"
)"
Docker Compose for Local Dev
services:
r-shiny:
build: .
ports:
- "8000:8000"
volumes:
- ./data:/data
db:
image: postgres:15
Best Practices for 2026
- Always use renv for project-level dependency isolation
- Containerize early — start with Docker for local dev too
- Test in CI — testthat + GitHub Actions catches issues early
- One artifact per commit — use targets for data pipelines
- Monitor in production — logging + health endpoints
See Also
- Reproducible Environments with renv — Deep dive on renv
- Reproducible Pipelines with targets — Building data pipelines
- Running R in GitHub Actions — CI/CD patterns
- Building R Packages — Package development
The renv package integrates tightly with Docker. When you build your container, run inside to capture all package versions. Commit the resulting alongside your code. This approach has become the standard for reproducible R projects.
GitHub Actions workflows can test multiple R versions and OS combinations. The r-lib/actions repository provides reusable workflows for package checking, testthat execution, and even deployment to Posit Connect or Shiny Server.
For data pipelines, targets shines. It creates a dependency graph of your analysis steps, only recomputing what changed. This saves hours on large datasets and makes your workflow auditable.
Docker Compose lets you spin up entire stacks locally. Define your R app, database, and any supporting services in docker-compose.yml. Everyone on the team runs identical infrastructure.
Production monitoring matters. Use the logging package for structured logs, expose health endpoints with plumber, and set up alerts for failures.