This guide walks you through deploying your first _server.yml application using buoyant.
What is buoyant?
buoyant is an R package for deploying web server applications to DigitalOcean. It works with any R web framework that implements the _server.yml standard, including:
- plumber2 - Modern R API framework
- fiery - Flexible web server framework
- Any custom package implementing the
_server.ymlstandard
When you deploy an application with buoyant:
- Your application files are uploaded to the server
- The engine package is installed if not present
- A systemd service is created for automatic restarts
- Nginx is configured as a reverse proxy
- Your application is accessible via HTTP (or HTTPS with
do_configure_https())
The _server.yml Standard
The _server.yml standard is a lightweight specification that allows different R web frameworks to be deployed consistently. At minimum, your application needs a _server.yml file with an engine field:
engine: plumber2The engine R package must contain a launch_server(settings, host = NULL, port = NULL, ...) function that: - Accepts the path to the starting server file as the first argument. The starting server file must have a _server.yml in the same folder. - Can override host and port via parameters - Starts the web server
There are no regulations on any other fields within _server.yml.
Case study: plumber2
plumber2 adheres to the _server.yml standard and in the following we will go through how it does so as an example for other engine developers who wish to support it.
YAML content
plumber2 currently defines 3 fields in addition to the engine field:
-
routes: An array of R files or folders that contain the server logic as annotated functions and objects. -
constructor: A path to a single R file that constructs thePlumber2object. -
options: A list of options to use for the server. These all corresponds to options that can be seen in?plumber2::all_opts().
plumber2 provide a function (create_server_yml()) that creates this file for you so that the user can be confident that it is formatted correctly. This is generally a good idea and also provides a place to document the content of the file.
launch_server()
The launch_server() function is quite simple for plumber2 since the api() constructor itself understands _server.yml files as input. Because of this the function looks like this:
{.r launch_server} function(settings, host = NULL, port = NULL, ...) { pa <- api(settings) if (!is.null(host)) { pa$host <- host } if (!is.null(port)) { pa$port <- port } api_run(pa) } <environment: namespace:plumber2>
Prerequisites
- R (>= 3.0.0)
- DigitalOcean Account - Sign up at https://www.digitalocean.com
- SSH Keys - Configure SSH keys for your system
Step 1: Create Your Application
Create a _server.yml compliant application. For example, with plumber2:
{.yaml server_yaml} #| file: _server.yml engine: plumber2 routes: app.R constructor: ~ options: {}
```{.r app} #| file: app.R #* @get /hello function() { list(message = “Hello from buoyant!”) }
#* @get /add #* @query a First number #* @query b Second number function(query) { result <- as.numeric(queryb) list(result = result) }
## Step 2: Test Locally (Optional)
If you have `{plumber2}` installed:
```{.r}
library(plumber2)
api("_server.yml") |> api_run()
Visit http://127.0.0.1:8080/api-docs to see your API documentation.
Step 3: Set Up DigitalOcean
# Authenticate with DigitalOcean
# This will open a browser for authentication
analogsea::do_oauth()
# Check authentication and existing droplets
analogsea::droplets() # Should return a list (may be empty)Configure SSH Keys
Make sure you provide digitial ocean your public key at https://cloud.digitalocean.com/ssh_keys. Github has some good advice on creating a new public key if you don’t already have one: https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent.
Be sure to store your Host to digitalocean.com within your ssh config to point to your private key that you’ve created:
Host github.com
UseKeychain yes
IdentityFile ~/.ssh/newly_created_key_file
If you didn’t use a password when creating your SSH key, you can skip the UseKeychain line.
You can verify your DigitalOcean SSH key exists with the proper host with by calling buoyant::do_keyfile() and having it return the path to your private key.
Step 4: Provision a Server
library(buoyant)
# This will cost money! Check DigitalOcean pricing first.
# A basic droplet costs around $6/month (as of 2025)
droplet <- do_provision(
name = "my-api-server",
region = "sfo3", # San Francisco
size = "s-1vcpu-1gb" # Basic tier
)
# This will take a few minutes...
# It installs R, nginx, firewall, and sets up the server structureStep 5: Deploy Your Application
# Validate your _server.yml file first
validate_server_yml("./")
# Deploy!
droplet |>
do_deploy_server(
path = "api", # URL sub-path: /api
local_path = "./_dev/barret", # Local directory
port = 8000, # Internal port
forward = TRUE, # Forwards `/` to `/api`
overwrite = TRUE
)The deployment process:
- Uploads your application files
- Installs the engine package (e.g. plumber2)
- Creates a systemd service for auto-restart
- Configures nginx as a reverse proxy
- Starts your application
Step 6: Access Your API
# Get the URL
url <- do_ip(droplet, "/api")
print(url) # e.g., "http://165.232.134.23/api"
# Open in browser
browseURL(paste0(url, "/hello"))
# Or use httr2 to test
httr2::request(paste0(url, "/hello")) |>
httr2::req_headers(Accept = "text/plain") |>
httr2::req_perform() |>
httr2::resp_body_string()
#> [1] "Hello from buoyant!"
httr2::request(paste0(url, "/add?a=5&b=3")) |>
httr2::req_headers(Accept = "text/plain") |>
httr2::req_perform() |>
httr2::resp_body_string()
#> [1] "8"Step 7: Monitor Your Application
SSH into your droplet to check logs:
analogsea::droplet_ssh(droplet, "journalctl -u server-api -f")Or to see if the service is running
analogsea::droplet_ssh(droplet, "systemctl status server-api")Optional: Add HTTPS
If you have a domain name:
# Point your domain to the droplet IP
ip <- do_ip(droplet)
print(ip)
# After DNS is configured (wait for propagation):
do_configure_https(
droplet = droplet,
domain = "api.yourdomain.com",
email = "you@example.com",
terms_of_service = TRUE # Agree to Let's Encrypt TOS
)Now your API is available at https://api.yourdomain.com/api
What’s Next?
You’ve successfully deployed your first application! Here are some next steps:
-
Update your application: Redeploy with
overwrite = TRUEwhen you make changes - Deploy multiple apps: Use different ports and paths on the same droplet
-
Add HTTPS: Use
do_configure_https()with your domain name -
Monitor your app: Check logs with
journalctl -u server-myapp
For detailed information, see:
- Advanced Usage - Managing deployments, multiple apps, HTTPS, and more
- Troubleshooting - Common issues and solutions
Getting Help
- Package issues: https://github.com/posit-dev/buoyant/issues
- DigitalOcean docs: https://docs.digitalocean.com
- plumber2 docs: https://plumber2.posit.co
- analogsea docs: https://github.com/pachadotdev/analogsea
-
_server.ymlspecification: https://plumber2.posit.co/articles/server_yml.html