mirror of
https://github.com/coder/registry.git
synced 2026-06-02 20:48:14 +00:00
feat(templates): add docker-rstudio template with code-server and RMarkdown (#819)
## New Template: docker-rstudio
Adds a Docker-based template for R development workspaces.
### What it provides
| Tool | Source | Access |
|------|--------|--------|
| **RStudio Server** | Pre-installed in `rocker/rstudio` image | Browser
via Coder proxy (subdomain) |
| **code-server** | `registry.coder.com/coder/code-server/coder` module
| Browser via Coder proxy |
| **RMarkdown** | Installed on first start, persisted in home-dir R
library | Available in both RStudio and code-server |
### Design decisions
<details>
<summary>Click to expand</summary>
- **`rocker/rstudio` as the base image** instead of
`codercom/enterprise-base:ubuntu` + the `rstudio-server` module. The
module runs RStudio inside a nested Docker container which requires
Docker-in-Docker or socket mounting in the workspace. Using the rocker
image directly avoids that complexity and starts faster since R and
RStudio are already installed.
- **Direct `coder_app` for RStudio** rather than the registry
`rstudio-server` module, because the module is designed for Docker-based
provisioning (it pulls and runs a rocker container). Since the workspace
itself _is_ the rocker container, RStudio Server is started natively via
`rserver`.
- **RMarkdown installed idempotently** — the startup script checks
`require('rmarkdown')` before installing. Since R libraries default to a
subdirectory under `/home/rstudio` (the persistent volume), packages
survive workspace restarts.
- **Persistent volume mounted at `/home/rstudio`** to match the default
user in the rocker image.
- **`--auth-none=1`** disables RStudio authentication since the Coder
proxy handles access control.
</details>
### Files added
- `registry/coder/templates/docker-rstudio/main.tf`
- `registry/coder/templates/docker-rstudio/README.md`
### Validation
- `go run ./cmd/readmevalidation/` — passes (32 templates detected)
- `terraform fmt` — clean
- `bun run fmt` — all files unchanged
---------
Co-authored-by: DevCats <christofer@coder.com>
Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
---
|
||||
display_name: Docker RStudio
|
||||
description: Provision Docker containers with RStudio, code-server, and RMarkdown
|
||||
icon: ../../../../.icons/rstudio.svg
|
||||
verified: true
|
||||
tags: [docker, rstudio, r, rmarkdown, code-server]
|
||||
---
|
||||
|
||||
# R Development on Docker Containers
|
||||
|
||||
Provision Docker containers pre-configured for R development as [Coder workspaces](https://coder.com/docs/workspaces) with this template.
|
||||
|
||||
Each workspace comes with:
|
||||
|
||||
- **RStudio Server** — full-featured R IDE in the browser.
|
||||
- **code-server** — VS Code in the browser for general editing.
|
||||
- **RMarkdown** — author reproducible documents, reports, and presentations.
|
||||
|
||||
The workspace is based on the [rocker/rstudio](https://rocker-project.org/) image, which ships R and RStudio Server pre-installed.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Infrastructure
|
||||
|
||||
#### Running Coder inside Docker
|
||||
|
||||
If you installed Coder as a container within Docker, you will have to do the following things:
|
||||
|
||||
- Make the Docker socket available to the container
|
||||
- **(recommended) Mount `/var/run/docker.sock` via `--mount`/`volume`**
|
||||
- _(advanced) Restrict the Docker socket via https://github.com/Tecnativa/docker-socket-proxy_
|
||||
- Set `--group-add`/`group_add` to the GID of the Docker group on the **host** machine
|
||||
- You can get the GID by running `getent group docker` on the **host** machine
|
||||
|
||||
#### Running Coder outside of Docker
|
||||
|
||||
If you installed Coder as a system package, the VM you run Coder on must have a running Docker socket and the `coder` user must be added to the Docker group:
|
||||
|
||||
```sh
|
||||
# Add coder user to Docker group
|
||||
sudo adduser coder docker
|
||||
|
||||
# Restart Coder server
|
||||
sudo systemctl restart coder
|
||||
|
||||
# Test Docker
|
||||
sudo -u coder docker ps
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
This template provisions the following resources:
|
||||
|
||||
- Docker image (built from `build/Dockerfile`, extending `rocker/rstudio` with system dependencies)
|
||||
- Docker container (ephemeral — destroyed on workspace stop)
|
||||
- Docker volume (persistent on `/home/rstudio`)
|
||||
|
||||
When the workspace restarts, tools and files outside `/home/rstudio` are not persisted. The R library path defaults to a subdirectory of the home folder, so installed packages (including RMarkdown) survive restarts.
|
||||
|
||||
> [!NOTE]
|
||||
> This template is designed to be a starting point! Edit the Terraform to extend it for your use case.
|
||||
|
||||
## Customization
|
||||
|
||||
### Changing the R version
|
||||
|
||||
Set the `rstudio_version` variable to any valid [rocker/rstudio tag](https://hub.docker.com/r/rocker/rstudio/tags) (for example `4.4.2`, `4.3`, or `latest`).
|
||||
|
||||
### Installing additional R packages
|
||||
|
||||
R packages are pre-installed via the `build/Dockerfile` so they are available immediately when the workspace starts. To add more packages, add `install.packages()` calls to the Dockerfile:
|
||||
|
||||
```dockerfile
|
||||
RUN R -e "install.packages(c('tidyverse', 'shiny'))"
|
||||
```
|
||||
|
||||
The image is pre-configured to use [Posit Package Manager](https://packagemanager.posit.co/) which provides pre-compiled binary packages for fast installation. Packages installed at build time avoid long startup delays from compiling from source on every workspace start.
|
||||
|
||||
### Adding system dependencies
|
||||
|
||||
The `build/Dockerfile` extends the `rocker/rstudio` base image with system packages required by modules (e.g. `curl` for code-server, `cmake` for R package compilation). If you add modules that need additional system-level tools, add them to the `Dockerfile`:
|
||||
|
||||
```dockerfile
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y \
|
||||
curl \
|
||||
cmake \
|
||||
your-package-here \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
```
|
||||
|
||||
### Adding LaTeX for PDF rendering
|
||||
|
||||
RMarkdown can render PDF output when LaTeX is available. Add the following to the startup script to install TinyTeX:
|
||||
|
||||
```sh
|
||||
R --quiet -e "if (!require('tinytex', quietly = TRUE)) { install.packages('tinytex', repos = 'https://cloud.r-project.org'); tinytex::install_tinytex() }"
|
||||
```
|
||||
@@ -0,0 +1,12 @@
|
||||
ARG RSTUDIO_VERSION=4
|
||||
FROM rocker/rstudio:${RSTUDIO_VERSION}
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y \
|
||||
curl \
|
||||
cmake \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN R -e "install.packages('rmarkdown')"
|
||||
|
||||
RUN echo "auth-minimum-user-id=0" >>/etc/rstudio/rserver.conf
|
||||
@@ -0,0 +1,244 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
}
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
username = data.coder_workspace_owner.me.name
|
||||
}
|
||||
|
||||
variable "docker_socket" {
|
||||
default = ""
|
||||
description = "(Optional) Docker socket URI"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "rstudio_version" {
|
||||
default = "4"
|
||||
description = "The rocker/rstudio image tag to use (e.g. 4, 4.4, 4.4.2)"
|
||||
type = string
|
||||
}
|
||||
|
||||
provider "docker" {
|
||||
# Defaulting to null if the variable is an empty string lets us
|
||||
# have an optional variable without having to set our own default.
|
||||
host = var.docker_socket != "" ? var.docker_socket : null
|
||||
}
|
||||
|
||||
data "coder_provisioner" "me" {}
|
||||
data "coder_workspace" "me" {}
|
||||
data "coder_workspace_owner" "me" {}
|
||||
|
||||
resource "coder_agent" "main" {
|
||||
arch = data.coder_provisioner.me.arch
|
||||
os = "linux"
|
||||
startup_script = <<-EOT
|
||||
set -e
|
||||
|
||||
# Prepare user home with default files on first start.
|
||||
if [ ! -f ~/.init_done ]; then
|
||||
cp -rT /etc/skel ~ 2>/dev/null || true
|
||||
touch ~/.init_done
|
||||
fi
|
||||
|
||||
# Start RStudio Server. The rocker/rstudio image ships the
|
||||
# server pre-installed. We disable authentication because
|
||||
# the Coder proxy handles access control.
|
||||
if command -v rserver > /dev/null 2>&1; then
|
||||
sudo rserver \
|
||||
--server-daemonize=0 \
|
||||
--auth-none=1 \
|
||||
--www-port=8787 \
|
||||
--server-user=rstudio > /tmp/rserver.log 2>&1 &
|
||||
elif [ -x /usr/lib/rstudio-server/bin/rserver ]; then
|
||||
sudo /usr/lib/rstudio-server/bin/rserver \
|
||||
--server-daemonize=0 \
|
||||
--auth-none=1 \
|
||||
--www-port=8787 \
|
||||
--server-user=rstudio > /tmp/rserver.log 2>&1 &
|
||||
fi
|
||||
EOT
|
||||
|
||||
# These environment variables allow you to make Git commits
|
||||
# right away after creating a workspace. They take precedence
|
||||
# over configuration in ~/.gitconfig. Remove this block if
|
||||
# you prefer to configure Git manually or via dotfiles.
|
||||
env = {
|
||||
GIT_AUTHOR_NAME = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
|
||||
GIT_AUTHOR_EMAIL = "${data.coder_workspace_owner.me.email}"
|
||||
GIT_COMMITTER_NAME = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
|
||||
GIT_COMMITTER_EMAIL = "${data.coder_workspace_owner.me.email}"
|
||||
}
|
||||
|
||||
metadata {
|
||||
display_name = "CPU Usage"
|
||||
key = "0_cpu_usage"
|
||||
script = "coder stat cpu"
|
||||
interval = 10
|
||||
timeout = 1
|
||||
}
|
||||
|
||||
metadata {
|
||||
display_name = "RAM Usage"
|
||||
key = "1_ram_usage"
|
||||
script = "coder stat mem"
|
||||
interval = 10
|
||||
timeout = 1
|
||||
}
|
||||
|
||||
metadata {
|
||||
display_name = "Home Disk"
|
||||
key = "3_home_disk"
|
||||
script = "coder stat disk --path $${HOME}"
|
||||
interval = 60
|
||||
timeout = 1
|
||||
}
|
||||
|
||||
metadata {
|
||||
display_name = "CPU Usage (Host)"
|
||||
key = "4_cpu_usage_host"
|
||||
script = "coder stat cpu --host"
|
||||
interval = 10
|
||||
timeout = 1
|
||||
}
|
||||
|
||||
metadata {
|
||||
display_name = "Memory Usage (Host)"
|
||||
key = "5_mem_usage_host"
|
||||
script = "coder stat mem --host"
|
||||
interval = 10
|
||||
timeout = 1
|
||||
}
|
||||
|
||||
metadata {
|
||||
display_name = "Load Average (Host)"
|
||||
key = "6_load_host"
|
||||
# Get load average scaled by number of cores.
|
||||
script = <<EOT
|
||||
echo "`cat /proc/loadavg | awk '{ print $1 }'` `nproc`" | awk '{ printf "%0.2f", $1/$2 }'
|
||||
EOT
|
||||
interval = 60
|
||||
timeout = 1
|
||||
}
|
||||
|
||||
metadata {
|
||||
display_name = "Swap Usage (Host)"
|
||||
key = "7_swap_host"
|
||||
script = <<EOT
|
||||
free -b | awk '/^Swap/ { printf("%.1f/%.1f", $3/1024.0/1024.0/1024.0, $2/1024.0/1024.0/1024.0) }'
|
||||
EOT
|
||||
interval = 10
|
||||
timeout = 1
|
||||
}
|
||||
}
|
||||
|
||||
# RStudio Server — served through the Coder proxy so users can
|
||||
# open the full RStudio IDE directly from the dashboard.
|
||||
resource "coder_app" "rstudio" {
|
||||
agent_id = coder_agent.main.id
|
||||
slug = "rstudio"
|
||||
display_name = "RStudio"
|
||||
url = "http://localhost:8787"
|
||||
icon = "/icon/rstudio.svg"
|
||||
subdomain = true
|
||||
share = "owner"
|
||||
order = 1
|
||||
}
|
||||
|
||||
# See https://registry.coder.com/modules/coder/code-server
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/code-server/coder"
|
||||
|
||||
# This ensures that the latest non-breaking version of the
|
||||
# module gets downloaded. You can also pin the module version
|
||||
# to prevent breaking changes in production.
|
||||
version = "~> 1.0"
|
||||
|
||||
agent_id = coder_agent.main.id
|
||||
order = 2
|
||||
folder = "/home/rstudio"
|
||||
}
|
||||
|
||||
resource "docker_image" "main" {
|
||||
name = "coder-${data.coder_workspace.me.id}-rstudio"
|
||||
build {
|
||||
context = "./build"
|
||||
build_args = {
|
||||
RSTUDIO_VERSION = var.rstudio_version
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "docker_volume" "home_volume" {
|
||||
name = "coder-${data.coder_workspace.me.id}-home"
|
||||
# Protect the volume from being deleted due to changes in
|
||||
# attributes.
|
||||
lifecycle {
|
||||
ignore_changes = all
|
||||
}
|
||||
# Add labels in Docker to keep track of orphan resources.
|
||||
labels {
|
||||
label = "coder.owner"
|
||||
value = data.coder_workspace_owner.me.name
|
||||
}
|
||||
labels {
|
||||
label = "coder.owner_id"
|
||||
value = data.coder_workspace_owner.me.id
|
||||
}
|
||||
labels {
|
||||
label = "coder.workspace_id"
|
||||
value = data.coder_workspace.me.id
|
||||
}
|
||||
# This field becomes outdated if the workspace is renamed but
|
||||
# can be useful for debugging or cleaning out dangling volumes.
|
||||
labels {
|
||||
label = "coder.workspace_name_at_creation"
|
||||
value = data.coder_workspace.me.name
|
||||
}
|
||||
}
|
||||
|
||||
resource "docker_container" "workspace" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
image = docker_image.main.image_id
|
||||
# Uses lower() to avoid Docker restriction on container names.
|
||||
name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
|
||||
# Hostname makes the shell more user friendly: rstudio@my-workspace:~$
|
||||
hostname = data.coder_workspace.me.name
|
||||
# Use the docker gateway if the access URL is 127.0.0.1.
|
||||
entrypoint = ["sh", "-c", replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal")]
|
||||
env = ["CODER_AGENT_TOKEN=${coder_agent.main.token}"]
|
||||
host {
|
||||
host = "host.docker.internal"
|
||||
ip = "host-gateway"
|
||||
}
|
||||
volumes {
|
||||
container_path = "/home/rstudio"
|
||||
volume_name = docker_volume.home_volume.name
|
||||
read_only = false
|
||||
}
|
||||
|
||||
# Add labels in Docker to keep track of orphan resources.
|
||||
labels {
|
||||
label = "coder.owner"
|
||||
value = data.coder_workspace_owner.me.name
|
||||
}
|
||||
labels {
|
||||
label = "coder.owner_id"
|
||||
value = data.coder_workspace_owner.me.id
|
||||
}
|
||||
labels {
|
||||
label = "coder.workspace_id"
|
||||
value = data.coder_workspace.me.id
|
||||
}
|
||||
labels {
|
||||
label = "coder.workspace_name"
|
||||
value = data.coder_workspace.me.name
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user