Model Deployment with Vetiver in R

· 5 min read · Updated March 17, 2026 · advanced
mlops machine-learning model-deployment posit r vetiver

Vetiver is Posit’s official package for MLOps workflows in R. It provides a standardized way to version, deploy, and monitor machine learning models. If you’re already using tidymodels for model building, vetiver fits naturally into your pipeline for serving predictions in production.

This article walks through an end-to-end workflow: training a model, creating a model card, versioning with pins, deploying as a Plumber API, and serving predictions over HTTP.

Training and Preparing a Model

First, you need a trained model. Vetiver works with any model that follows the tidymodels prediction interface. Here’s a simple example using a linear regression on the mtcars dataset:

library(tidymodels)
library(vetiver)
library(pins)

# Train a simple model
data(mtcars)
lm_model <- linear_reg() %>%
  set_engine("lm") %>%
  fit(mpg ~ wt + hp + cyl, data = mtcars)

# Create a vetiver model object
v <- vetiver_model(lm_model, "mpg_predictor")
v

The vetiver_model() function wraps your trained model with metadata needed for deployment. This includes the model name, version, and required preprocessing.

Using Workflows with Custom Preprocessing

For real-world models, you’ll often need custom preprocessing steps beyond simple formula syntax. Vetiver supports tidymodels workflows that bundle preprocessing recipes with your model:

library(recipes)

# Define a preprocessing recipe
mpg_recipe <- recipe(mpg ~ wt + hp + cyl, data = mtcars) %>%
  step_log(hp) %>%
  step_center(all_predictors()) %>%
  step_scale(all_predictors())

# Create a workflow combining recipe and model
lm_wf <- workflow() %>%
  add_recipe(mpg_recipe) %>%
  add_model(linear_reg() %>% set_engine("lm")) %>%
  fit(data = mtcars)

# Wrap in vetiver
v <- vetiver_model(lm_wf, "mpg_predictor_workflow")
v

The workflow object preserves your preprocessing steps when deployed, ensuring predictions use the same transformations as training.

Creating Model Cards

Model cards document your model for reproducibility and governance. Vetiver generates model cards from templates using rmarkdown:

# Generate a model card from the vetiver template
rmarkdown::draft(
  "model-card.Rmd",
  template = "vetiver_model_card",
  package = "vetiver"
)

This creates an R Markdown file you can customize with your model description, performance metrics, and training data information. Edit the generated file to add:

  • Model description and purpose
  • Input/output specifications
  • Performance metrics (if available)
  • Training data information
  • Any known limitations

Render the model card to HTML or PDF for sharing:

rmarkdown::render("model-card.Rmd")

Versioning with Pins

Vetiver uses the pins package for model versioning. This stores your model in a versioned registry, making it easy to track changes over time:

# Connect to a board (local folder in this case)
board <- board_folder("models/")

# Pin the model with version tracking
board %>% vetiver_pin_write(v)

When you update your model and re-run vetiver_pin_write(), pins automatically versions the new model. You can retrieve specific versions:

# Get the latest version
latest_model <- board %>% vetiver_pin_read("mpg_predictor")

# Get a specific version
v1_model <- board %>% vetiver_pin_read("mpg_predictor", version = 1)

The pins dashboard shows your model registry:

board %>% pin_versions("mpg_predictor")

Deploying as a Plumber API

Vetiver can generate a Plumber router automatically. Plumber is R’s package for building REST APIs that call R functions:

library(plumber)

# Create the Plumber router using vetiver_api()
pr <- pr() %>% vetiver_api(v)

# Or customize the API with additional endpoints
pr <- pr() %>%
  vetiver_api(v) %>%
  pr_get("/health", function() {
    list(status = "ok", timestamp = Sys.time())
  })

You can test the API locally before deploying:

# Run locally on port 8080
pr_run(pr, host = "0.0.0.0", port = 8080)

Serving Predictions via HTTP

Once deployed, clients can send prediction requests to your API. The request body contains the input features as JSON:

library(httr)
library(jsonlite)

# Example prediction request payload
prediction_request <- list(
  wt = 2.5,
  hp = 120,
  cyl = 6
)

# Send request to the API
httr::POST(
  "http://localhost:8080/predict",
  body = jsonlite::toJSON(prediction_request),
  content_type_json()
)

The API returns predictions in JSON format:

{
  "predictions": [
    {"mpg": 23.5}
  ]
}

Handling Batch Predictions

For bulk prediction workloads, you can send multiple rows at once:

# Create a data frame with multiple predictions
batch_request <- data.frame(
  wt = c(2.0, 2.5, 3.0),
  hp = c(100, 120, 150),
  cyl = c(4, 6, 8)
)

# Send batch request
response <- httr::POST(
  "http://localhost:8080/predict",
  body = jsonlite::toJSON(batch_request),
  content_type_json()
)

# Parse response
predictions <- jsonlite::fromJSON(httr::content(response, as = "text"))
predictions

The API automatically handles the data frame and returns predictions for all input rows.

Deploying to Posit Connect

For production deployment, Posit Connect provides the smoothest integration with vetiver. After publishing your API:

library(rsconnect)

# Deploy the Plumber API to Posit Connect
rsconnect::deployAPI(
  api = "app.R",
  account = "your-account-name",
  appName = "mpg_predictor",
  appTitle = "MPG Prediction API"
)

Replace "app.R" with the file containing your Plumber router. Posit Connect handles hosting, scaling, and authentication out of the box.

Complete Workflow Example

Here’s the full pipeline from training to deployment:

library(tidymodels)
library(vetiver)
library(pins)
library(plumber)
library(recipes)

# 1. Train model with preprocessing
data(mtcars)
lm_wf <- workflow() %>%
  add_recipe(recipe(mpg ~ wt + hp + cyl, data = mtcars) %>%
    step_log(hp)) %>%
  add_model(linear_reg() %>% set_engine("lm")) %>%
  fit(data = mtcars)

# 2. Create vetiver model
v <- vetiver_model(lm_wf, "mpg_predictor")

# 3. Create model card from template
rmarkdown::draft(
  "model_card.Rmd",
  template = "vetiver_model_card",
  package = "vetiver"
)

# 4. Version with pins
board <- board_folder("models/")
board %>% vetiver_pin_write(v)

# 5. Deploy as API
pr <- pr() %>% vetiver_api(v)
pr_run(pr, host = "0.0.0.0", port = 8080)

Deployment Considerations

For production deployment, consider these aspects:

API Hosting: You can deploy Plumber APIs on various platforms including Posit Connect, Shiny Server, or containerized with Docker. Posit Connect provides the smoothest integration with vetiver through the rsconnect package.

Model Monitoring: Track prediction drift by logging requests and responses. Vetiver doesn’t include built-in monitoring, but you can add custom logging within your Plumber endpoints.

Authentication: Plumber supports authentication via middleware. For production APIs, ensure appropriate access controls are in place.

Scaling: For high-traffic applications, consider running multiple R processes behind a load balancer. Plumber APIs are single-threaded by default.

Summary

Vetiver makes it straightforward to serve tidymodels predictions over HTTP. The key functions are:

  • vetiver_model() wraps your trained model
  • vetiver_pin_write() and vetiver_pin_read() handle version control
  • The vetiver_model_card template generates documentation
  • pr() %>% vetiver_api(v) creates a deployable API

The workflow integrates with tidymodels for training, recipes for preprocessing, and pins for versioning, giving you the tools to put models into production without building custom infrastructure from scratch.