Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions registry/coder/templates/docker-rstudio/README.md
Original file line number Diff line number Diff line change
@@ -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() }"
```
12 changes: 12 additions & 0 deletions registry/coder/templates/docker-rstudio/build/Dockerfile
Original file line number Diff line number Diff line change
@@ -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
244 changes: 244 additions & 0 deletions registry/coder/templates/docker-rstudio/main.tf
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading