Hosting

Once you have developed your plumber2 API, the next step is to find a way to host it. If you haven’t dealt with hosting an application on a server before, you may be tempted to run the api_run() function from an interactive session on your development machine (either your personal desktop or an RStudio Server instance) and direct traffic there. This is a dangerous idea for a number of reasons:

  1. Your development machine likely has a dynamic IP address. This means that clients may be able to reach you at that address today, but it will likely break on you in the coming weeks/months.
  2. Networks may leverage firewalls to block incoming traffic to certain networks and machines. Again, it may appear that everything is working for you locally, but other users elsewhere in the network or external clients may not be able to connect to your development machine.
  3. If your plumber2 process crashes (for instance, due to your server running out of memory), the method of running plumber2 will not automatically restart the crashed service for you. This means that your API will be offline until you manually login and restart it. Likewise if your development machine gets rebooted, your API will not automatically be started when the machine comes back online.
  4. This technique relies on having your clients specify a port number manually. Non-technical users may be tripped up by this; some of the other techniques do not require clients specifying the port for an API.
  5. This approach will eternally run one R process for your API. Some of the other approaches will allow you to load-balance traffic between multiple R processes to handle more requests. Posit Connect will even dynamically scale the number of running processes for you so that your API isn’t consuming more system resources than is necessary.
  6. Most importantly, serving public requests from your development environment can be a security hazard. Ideally, you should separate your development instances from the servers that are accessible by others.

For these reasons and more, you should consider setting up a separate server on which you can host your plumber2 APIs. There are a variety of options that you can consider.

The _server.yml file

Since plumber2 API specifications can be spread out over multiple files we need a single file that is the source of truth for what the API is based on. plumber2 uses the _server.yml specification for this and you can create a scaffold of such a file using create_server_yml().

The _server.yml file not only contains the R files that make up your API, but can also holds options that modify how the API is constructed (see get_opts()).

Creating a _server.yml file

At minimum, your _server.yml file needs to specify the engine:

engine: plumber2

A more complete example might look like:

engine: plumber2
routes:
  - api.R
  - routes/users.R
  - routes/data.R
options:
  host: 0.0.0.0
  port: 8000
  docs: true

You can create a scaffold of this file programmatically:

# Create _server.yml in the working directory
create_server_yml(
  "api.R",
  "routes/users.R",
  "routes/data.R",
  options = list(docs = TRUE, port = 8000, host = "0.0.0.0")
)

Testing your _server.yml file

Once you have a _server.yml file you should verify that it works as expected before deploying. You can do this by passing it to api() and testing the constructed API locally:

# Test that it works
api("_server.yml") |>
  api_run()

This will start your API locally using the same configuration that will be used in production. Make sure to test all your endpoints and verify the behavior matches your expectations.

For more details on the _server.yml specification, see the dedicated article.

Posit Connect

Posit Connect is an enterprise publishing platform from Posit. It supports automatic deployment of plumber2 APIs from RStudio and Positron using the rsconnect package and from Positron using the Posit Publisher extension.

Posit Connect automatically manages the number of R processes necessary to handle the current load and balances incoming traffic across all available processes. It can also shut down idle processes when they’re not in use. This allows you to run the appropriate number of R processes to scale your capacity to accommodate the current load.

DigitalOcean

DigitalOcean is a cloud infrastructure provider that makes it easy to deploy and scale web applications. The buoyant R package provides a streamlined way to deploy plumber2 APIs (and any other _server.yml compliant application) to DigitalOcean.

Prerequisites

Before deploying to DigitalOcean, you’ll need:

  1. A DigitalOcean account (sign up here)
  2. The buoyant and analogsea packages installed:
install.packages("pak")
pak::pak(c("buoyant", "analogsea"))
  1. A working _server.yml file for your API (see The _server.yml file section above)

Basic Deployment

Here’s how to deploy a plumber2 API to DigitalOcean:

library(buoyant)
library(analogsea)

# Authenticate with DigitalOcean (opens browser for OAuth)
do_oauth()

# Provision a new droplet (virtual server)
droplet <- do_provision(region = "sfo3")

# Deploy your application
do_deploy_server(
  droplet = droplet,
  path = "myapi",
  local_path = "path/to/my-api",
  port = 8000
)

# Get the URL to access your API
do_ip(droplet, "/myapi")
#> [1] "http://165.232.143.22/myapi"

Your API is now live! The do_deploy_server() function automatically:

Learn More

For more detailed information about deploying with buoyant, see the buoyant package documentation.

Docker

Docker is a containerization platform that allows you to package your API with all its dependencies into a portable container. This ensures consistent behavior across different environments (development, staging, production).

To run a plumber2 API in Docker, create a Dockerfile that:

  1. Installs R and required packages
  2. Copies your application files
  3. Exposes the port your API will use
  4. Runs the API using the _server.yml file

Choosing a Base Image

We recommend using the Rocker Project’s rocker/r-ver images as your base. These images provide versioned, minimal R installations optimized for reproducibility. Key benefits include:

Learn more about the rocker/r-ver images at https://rocker-project.org/images/versioned/r-ver.html.

System Dependencies

To determine which system packages are required for plumber2 and its dependencies, use pak::pkg_sysreqs():

#| eval: false
pak::pkg_sysreqs(c("plumber2"), sysreqs_platform = "ubuntu")
#> ── Install scripts ─────────────────────────────────────────── Ubuntu NA ──
#> apt-get -y update
#> apt-get -y install libx11-dev libcurl4-openssl-dev libssl-dev make \
#>   zlib1g-dev pandoc cmake xz-utils libfreetype6-dev libjpeg-dev \
#>   libpng-dev libtiff-dev libwebp-dev libsodium-dev libicu-dev \
#>   libfontconfig1-dev libfribidi-dev libharfbuzz-dev rustc cargo \
#>   libxml2-dev
#>
#> ── Packages and their system dependencies ─────────────────────────────────
#> clipr       – libx11-dev
#> curl        – libcurl4-openssl-dev, libssl-dev
#> fs          – make
#> httpuv      – make, zlib1g-dev
#> knitr       – pandoc
#> nanonext    – cmake, xz-utils
#> ragg        – libfreetype6-dev, libjpeg-dev, libpng-dev, libtiff-dev, libwebp-dev
#> sodium      – libsodium-dev
#> stringi     – libicu-dev
#> svglite     – libpng-dev
#> systemfonts – libfontconfig1-dev, libfreetype6-dev
#> textshaping – libfreetype6-dev, libfribidi-dev, libharfbuzz-dev
#> waysign     – cargo, rustc, xz-utils
#> websocket   – libssl-dev, make
#> xml2        – libxml2-dev

Example Dockerfile

Here’s an example Dockerfile for a plumber2 API:

FROM rocker/r-ver:4

RUN apt-get update && apt-get install -y \
    libx11-dev libcurl4-openssl-dev libssl-dev make \
    zlib1g-dev pandoc cmake xz-utils libfreetype6-dev libjpeg-dev libpng-dev \
    libtiff-dev libwebp-dev libsodium-dev libicu-dev libfontconfig1-dev \
    libfribidi-dev libharfbuzz-dev rustc cargo libxml2-dev \
    && rm -rf /var/lib/apt/lists/*

RUN R -q -e 'install.packages(c("plumber2"))'

WORKDIR /app
COPY . /app

EXPOSE 8000

CMD ["R", "-e", "plumber2::api('_server.yml') |> plumber2::api_run(host='0.0.0.0', port=8000)"]

Build and run your container:

# Build the image
docker build -t my-plumber2-api .

# Run the container
docker run -p 8000:8000 my-plumber2-api

# Your API is now available at http://localhost:8000

pm2

pm2 is a production process manager for Node.js applications, but it can also be used to manage R processes. While not specifically designed for R, pm2 provides useful features like automatic restarts, logging, and startup scripts. This is a good option if you already have Node.js infrastructure or prefer a cross-platform process manager.

Prerequisites

Install pm2 (requires Node.js):

npm install -g pm2

Setting up pm2 for plumber2

First, create a wrapper script that pm2 can execute. Create a file called run-api.R:

#!/usr/bin/env Rscript

# Load the API and run it
plumber2::api("_server.yml") |>
  plumber2::api_run(host = "0.0.0.0", port = 8000)

Make it executable:

chmod +x run-api.R

Starting Your API with pm2

Start your API using pm2:

pm2 start run-api.R --interpreter="Rscript" --name="my-plumber2-api"

Managing Your API

pm2 provides several commands for managing your application:

# View status of all applications
pm2 status

# View logs (combined stdout and stderr)
pm2 logs my-plumber2-api

# View only error logs
pm2 logs my-plumber2-api --err

# Restart the application
pm2 restart my-plumber2-api

# Stop the application
pm2 stop my-plumber2-api

# Delete the application from pm2
pm2 delete my-plumber2-api

Running Multiple Instances

pm2 can run multiple instances of your API for load balancing:

# Start 4 instances of your API
pm2 start run-api.R --interpreter="Rscript" --name="my-api" -i 4

# Or use max (number of CPU cores)
pm2 start run-api.R --interpreter="Rscript" --name="my-api" -i max

Note: Each instance needs to run on a different port, so you’ll need to either: 1. Use a load balancer (like nginx) in front of pm2 instances 2. Modify your script to accept port as an argument

Here’s an improved run-api.R that accepts a port argument:

#!/usr/bin/env Rscript

# Get port from command line argument or environment variable
args <- commandArgs(trailingOnly = TRUE)
port <- if (length(args) > 0) {
  as.integer(args[1])
} else {
  as.integer(Sys.getenv("PORT", "8000"))
}

# Load the API and run it
plumber2::api("_server.yml") |>
  plumber2::api_run(host = "0.0.0.0", port = port)

Automatic Startup on Boot

Configure pm2 to start your API automatically when the server boots:

# Save the current pm2 process list
pm2 save

# Generate and configure startup script
pm2 startup

# This will output a command like:
# sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u youruser --hp /home/youruser
# Run that command to enable startup on boot

Using an Ecosystem File

For more complex configurations, create a ecosystem.config.js file:

module.exports = {
  apps: [{
    name: 'plumber2-api',
    script: './run-api.R',
    interpreter: 'Rscript',
    instances: 4,
    exec_mode: 'cluster',
    env: {
      PORT: 8000,
      NODE_ENV: 'production'
    },
    error_file: './logs/err.log',
    out_file: './logs/out.log',
    log_date_format: 'YYYY-MM-DD HH:mm Z',
    merge_logs: true
  }]
};

Start it with:

pm2 start ecosystem.config.js

Monitoring and Logs

pm2 provides built-in monitoring:

# Real-time dashboard
pm2 monit

# View resource usage
pm2 list

Logs are automatically rotated and can be configured with pm2-logrotate:

pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7

systemd

systemd is the standard service manager for modern Linux distributions (Ubuntu 16.04+, CentOS 7+, Debian 8+, etc.). It provides robust process management without requiring Docker or Node.js. This is a good option for Linux servers where you want native system integration and simple process management.

Creating a systemd Service

First, create a service file at /etc/systemd/system/plumber2-api.service:

[Unit]
Description=Plumber2 API Service
After=network.target

[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/plumber2-api
Environment="PATH=/usr/bin:/usr/local/bin"

# Run the API using _server.yml
ExecStart=/usr/bin/Rscript -e "plumber2::api('_server.yml') |> plumber2::api_run(host='0.0.0.0', port=8000)"

# Restart policy
Restart=always
RestartSec=10

# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=plumber2-api

[Install]
WantedBy=multi-user.target

Setting Up Your Application

Place your API files in the working directory specified in the service file:

# Create directory
sudo mkdir -p /var/www/plumber2-api

# Copy your API files
sudo cp -r /path/to/your/api/* /var/www/plumber2-api/

# Set ownership
sudo chown -R www-data:www-data /var/www/plumber2-api

Managing the Service

Enable and start your service:

# Reload systemd to recognize the new service
sudo systemctl daemon-reload

# Enable the service to start on boot
sudo systemctl enable plumber2-api

# Start the service
sudo systemctl start plumber2-api

# Check status
sudo systemctl status plumber2-api

Viewing Logs

systemd logs can be viewed with journalctl:

# View all logs for your service
sudo journalctl -u plumber2-api

# Follow logs in real-time
sudo journalctl -u plumber2-api -f

# View logs from the last hour
sudo journalctl -u plumber2-api --since "1 hour ago"

# View logs with specific priority (error and above)
sudo journalctl -u plumber2-api -p err

Service Management Commands

# Start the service
sudo systemctl start plumber2-api

# Stop the service
sudo systemctl stop plumber2-api

# Restart the service
sudo systemctl restart plumber2-api

# Reload configuration without stopping
sudo systemctl reload plumber2-api

# Check if service is running
sudo systemctl is-active plumber2-api

# Check if service is enabled on boot
sudo systemctl is-enabled plumber2-api

# Disable auto-start on boot
sudo systemctl disable plumber2-api