Deploying Plumber APIs to Production
Containerize and deploy Plumber APIs with Docker and cloud platforms.
Deploying Plumber APIs to Production is a free R Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the R Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Production Deployment Options
A Plumber API can be deployed in several ways:
- Docker container — portable, reproducible, works anywhere
- Posit Connect — push-button deploy with scheduling
- Digital Ocean / EC2 — a plain Linux VM running R
Docker is the most portable option and the industry standard for production R APIs.
Plumber API Entry Point
Create a top-level api.R that starts the Plumber server. Use Sys.getenv('PORT', unset='8000') so the port can be set by the container orchestrator without changing code.
# api.R
# library(plumber)
#
# pr <- plumb('routes.R')
# port <- as.integer(Sys.getenv('PORT', unset = '8000'))
# pr$run(host = '0.0.0.0', port = port)
#
# Listening on 0.0.0.0 is required inside Docker
# (127.0.0.1 is only reachable inside the container)Base Docker Image — rocker/r-ver
The rocker/r-ver image provides a minimal, version-pinned R environment. Always pin to a specific R version (e.g., rocker/r-ver:4.3.2) to ensure reproducible builds. Avoid :latest in production.
# Dockerfile
# FROM rocker/r-ver:4.3.2
#
# Alternatively use rocker/plumber which pre-installs plumber:
# FROM rstudio/plumber:latest
#
# rocker/r-ver is more minimal and gives you full control
# over which packages are installed.Installing R Packages in the Dockerfile
Use RUN Rscript -e "install.packages(...)" to install packages during the image build. Install system dependencies first (e.g., libssl-dev for httr2) using apt-get.
# FROM rocker/r-ver:4.3.2
#
# RUN apt-get update && apt-get install -y \
# libssl-dev \
# libcurl4-openssl-dev \
# && rm -rf /var/lib/apt/lists/*
#
# RUN Rscript -e "install.packages(c('plumber', 'jsonlite', 'httr2'), repos='https://cloud.r-project.org')"Copying Files and Setting the Port
Copy your R source files into the container with COPY. Declare the port with EXPOSE so Docker knows which port the container listens on. This is documentation only — it does not publish the port.
# FROM rocker/r-ver:4.3.2
# ...
# WORKDIR /app
# COPY routes.R .
# COPY api.R .
#
# EXPOSE 8000
#
# CMD ["Rscript", "api.R"]Building and Running the Docker Image
Build the image with docker build, then run a container mapping the host port to the container port. The -e flag injects environment variables for secrets.
# Build the image:
# docker build -t my-r-api:1.0 .
#
# Run a container:
# docker run -d \
# -p 8000:8000 \
# -e API_SECRET_KEY='my_secret' \
# -e DATABASE_URL='postgres://...' \
# --name r-api \
# my-r-api:1.0
#
# Test:
# curl http://localhost:8000/pingReading Secrets with Sys.getenv()
In production, never put secrets in the Dockerfile or source code. Read them at runtime with Sys.getenv(). Pass them through Docker -e flags, Kubernetes secrets, or environment-variable management systems like AWS Secrets Manager.
# In routes.R:
# db_url <- Sys.getenv('DATABASE_URL', unset = '')
# api_key <- Sys.getenv('API_SECRET_KEY', unset = '')
#
# if (nchar(db_url) == 0) stop('DATABASE_URL is required')
# if (nchar(api_key) == 0) stop('API_SECRET_KEY is required')
#
# Fail fast at startup rather than failing silently at request time
cat('Validate all required env vars at startup with stop()
')Health Check Endpoint
A /ping or /health endpoint lets load balancers and orchestrators (Kubernetes, ECS) confirm the API is alive. It should return 200 quickly with no authentication, and optionally check DB connectivity.
# #* Health check
# #* @preempt auth
# #* @get /ping
# function() {
# list(
# status = 'ok',
# version = '1.0.0',
# time = format(Sys.time(), '%Y-%m-%dT%H:%M:%SZ')
# )
# }
#
# Docker HEALTHCHECK:
# HEALTHCHECK CMD curl -f http://localhost:8000/ping || exit 1Complete Dockerfile Example
Putting it all together — a production-ready Dockerfile for a Plumber API:
# FROM rocker/r-ver:4.3.2
# RUN apt-get update && apt-get install -y libssl-dev libcurl4-openssl-dev \
# && rm -rf /var/lib/apt/lists/*
# RUN Rscript -e "install.packages(c('plumber','jsonlite'), repos='https://cloud.r-project.org')"
# WORKDIR /app
# COPY routes.R api.R ./
# EXPOSE 8000
# HEALTHCHECK CMD curl -f http://localhost:8000/ping || exit 1
# CMD ["Rscript", "api.R"]Logging in Production
Structured logging helps diagnose issues in production. Use cat() or the logger package to write timestamped logs to stdout — Docker and most platforms capture stdout automatically and route it to a log aggregator.
# Log format: ISO timestamp + level + message
log_info <- function(msg) {
cat(format(Sys.time(), '%Y-%m-%dT%H:%M:%S'), '[INFO]', msg, '
')
}
log_info('API starting up')
log_info(paste('Port:', Sys.getenv('PORT', '8000')))Reverse Proxy with Nginx
In production, put an Nginx reverse proxy in front of Plumber to handle TLS termination, rate limiting, and request buffering. Nginx passes requests to Plumber on localhost, while the outside world connects to Nginx on port 443.
# Nginx config snippet (nginx.conf):
# server {
# listen 443 ssl;
# ssl_certificate /etc/letsencrypt/.../fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/.../privkey.pem;
#
# location /api/ {
# proxy_pass http://127.0.0.1:8000/;
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# }
# }Quick Check: Docker EXPOSE
What does the EXPOSE 8000 instruction in a Dockerfile actually do?
Deploying Plumber APIs Recap
Key steps for production Plumber deployment:
- Listen on
0.0.0.0and readPORTfrom environment - Use
rocker/r-ver:X.Y.Z(version-pinned) as the base image - Install packages in the Dockerfile; copy only source files
- Inject secrets via
-eenv vars — never in source or Dockerfile - Add a
/pinghealth endpoint (with#* @preempt auth) - Use Nginx as a reverse proxy for TLS and rate limiting
Frequently asked questions
Is the “Deploying Plumber APIs to Production” lesson free?
Yes — the full text of “Deploying Plumber APIs to Production” is free to read here on the web, and the R Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the R Academy course, upgrade to CoddyKit PRO.
What will I learn in “Deploying Plumber APIs to Production”?
Containerize and deploy Plumber APIs with Docker and cloud platforms. You practise R Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start R Academy?
No prior experience is required. R Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Deploying Plumber APIs to Production” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this R Academy lesson?
Yes. Every R Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Introduction to Plumber and REST
- Creating GET and POST Endpoints
- Authentication and API Security
- Deploying Plumber APIs to Production