前言
近期遇到有部分 R Script 執行時佔用太多 VM 記憶體,所以打算把 R 打包成一個 Docker image,讓他可以在 GCP Cloud Run 上面跑,解放 VM 的資源。
首先我建立了一個基於 tidyverse 的 Dockerfile,安裝常用的兩個套件,並讓這個 image 可以由外部提供一個 JOB_NAME 參數,來動態指定要執行的檔案。
(選擇 tidyverse 的原因是使用 r-base 時會遇到無法安裝 bigrquery 的問題)
FROM rocker/tidyverse:4.4.1
RUN R -e "install.packages(c('dplyr', 'bigrquery'), repos='<http://cran.rstudio.com/>')"
COPY r_jobs /r_jobs
CMD ["sh", "-c", "Rscript /r_jobs/$JOB_NAME"]
建立 Docker image 時遇到的瓶頸
雖然這個 image 是可以正常執行,但 docker build 後發現兩個大問題:
- Docker image 太胖!
- image 竟然有 2.5 GB 這麼大,這將會造成部署與存放上的問題。
- R 套件安裝太慢!
- 光是安裝兩個套件的步驟,就要花 5 分鐘,安裝 12 個套件需要 20 分鐘(如下圖 1,203 秒),時間成本太高。

基於這兩個問題我開始研究 Docker image 的優化方法。
Step 1:優化安裝套件的速度
透過 Google 與 ChatGPT-4o 的協助下,發現大部分的套件都有 Precompiled 版本,這些版本的套件已經被預先編譯好,所以可以節省很多安裝時間。
改用 Precompiled 版本的套件 -> 安裝速度提升 8.6 倍
我利用 tidyverse 和 r-ver 兩種 Official Docker image 來測試,在安裝套件前先指定使用 RStudio Package Manager 來安裝,發現 docker build image 所花費的時間最低可以降到只剩 140 秒,速度提升了 8.6 倍!

要使用 Precompiled 版本 來安裝就要在安裝前加上,以下是我使用 tidyverse 和 r-ver 兩種 Official Docker image 的 Dockerfile 範例(部分省略)。
# Building 140.3s (9/9) FINISHED
FROM rocker/tidyverse:4.4.1
RUN R -e "options(repos = c(CRAN = '<https://packagemanager.rstudio.com/cran/__linux__/focal/latest>'))"
RUN R -e "install.packages(c('yaml', 'bigrquery', 'dplyr', 'tmcn', 'stopwords', 'jiebaR', 'stringr', 'text2vec', 'googleCloudStorageR', 'arrow', 'Matrix', 'lubridate'))"
...
# Building 179.0s (9/9) FINISHED
FROM rocker/r-ver:4.4.1
RUN R -e "options(repos = c(CRAN = '<https://packagemanager.rstudio.com/cran/__linux__/focal/latest>'))"
RUN R -e "install.packages(c('yaml', 'bigrquery', 'dplyr', 'tmcn', 'stopwords', 'jiebaR', 'stringr', 'text2vec', 'googleCloudStorageR', 'arrow', 'Matrix', 'lubridate'))"
...
Step 2:優化 Docker image size
用 r-ver 取代 tidyverse -> 節省 image size 約 2.8 倍
- 首先我在 Rocker Project 官網 上看到
tidyverse這個 image 是基本的r-verimage 再加上 RStudio Server + tidyverse packages & devtools,對我的現況來說這些套件都是不必要的,等同浪費了很多空間,所以我試著改為單純使用r-ver這個 image。 - 發現 docker image 降到只有 993 MB(2780 → 993 MB),相當於省下了 2.8 倍的空間!
# [TAG: origin]
FROM rocker/tidyverse:4.4.1
...
# [TAG: use-r-ver]
FROM rocker/r-ver:4.4.1
...
REPOSITORY TAG SIZE
gcr.io/project/r-script use-r-ver 993MB
gcr.io/project/r-script origin 2.78GB
使用 Multi-stage Builds:兩階段相同 image -> 沒有太大改變
- 後來又發現有一個技巧叫作「Multi-stage Builds」,就是允許我們在 Dockerfile 中寫多個
FROM,每個FROM都是一個新的 Stage,最終建立的 image 會以最後的FROM的 Stage 為主(如果想要產出不同的 stage image,要在 build 時加上--target指令)
- 我以兩個 Stage 都是使用
r-ver這個 image 來進行測試,並把basestage 的套件資料夾複製到releasestage 裡面 - 最終的 image size 卻反而變大了一點點(993 → 994 MB)。
# [TAG: multi-stage-r-ver]
FROM rocker/r-ver:4.4.1 AS base
RUN R -e "options(repos = c(CRAN = '<https://packagemanager.rstudio.com/cran/__linux__/focal/latest>'))"
RUN R -e "install.packages(c('yaml', 'bigrquery', 'dplyr', 'tmcn', 'stopwords', 'jiebaR', 'stringr', 'text2vec', 'googleCloudStorageR', 'arrow', 'Matrix', 'lubridate'))"
# 以下就是 Multi-stage Builds 的第二個 FROM (=Stage)
FROM rocker/r-ver:4.4.1 AS release
COPY --from=base /usr/local/lib/R/site-library /usr/local/lib/R/site-library
COPY r_jobs /r_jobs
CMD ["sh", "-c", "Rscript /r_jobs/$JOB_NAME"]
REPOSITORY TAG SIZE
gcr.io/project/r-script multi-stage-r-ver 994MB
gcr.io/project/r-script use-r-ver 993MB
gcr.io/project/r-script origin 2.78GB
使用 Multi-stage builds:第二階段改 ubuntu -> 成功縮減 2 倍 image size
- 延續上一個測試,我想說是不是最後階段的 image 不需要用相同的
r-ver來建立,所以我用了最基本的ubuntu來測試(要額外再安裝r-base不然會遇到Rscript not found的錯誤)。
- 最後 build 完的 image 是 499 MB,成功又讓 Docker image 更小了將近 2 倍(993 → 499 MB)。
# [TAG: multi-stage-ubuntu]
FROM rocker/r-ver:4.4.1 AS base
RUN R -e "options(repos = c(CRAN = '<https://packagemanager.rstudio.com/cran/__linux__/focal/latest>'))"
RUN R -e "install.packages(c('yaml', 'bigrquery', 'dplyr', 'tmcn', 'stopwords', 'jiebaR', 'stringr', 'text2vec', 'googleCloudStorageR', 'arrow', 'Matrix', 'lubridate'))"
# 使用 ubuntu 進行 Multi-stage Builds
FROM ubuntu:24.10 AS release
RUN apt-get update && apt-get install -y --no-install-recommends \\
r-base
COPY --from=base /usr/local/lib/R/site-library /usr/local/lib/R/site-library
COPY r_jobs /r_jobs
CMD ["sh", "-c", "Rscript /r_jobs/$JOB_NAME"]
REPOSITORY TAG SIZE
gcr.io/project/r-script multi-stage-ubuntu 499MB
gcr.io/project/r-script multi-stage-r-ver 994MB
gcr.io/project/r-script use-r-ver 993MB
gcr.io/project/r-script origin 2.78GB
總結
這次成功解決了 Docker image 安裝 R 套件過慢以及 Size 過大的問題
- 利用 precompiled 的方式,安裝套件速度提升了 8.6 倍(1203 秒 → 140 秒)
- 透過 Multi-stage builds,讓 image size 縮減了 5.6 倍(2.78 GB→ 499 MB)
後記
- 雖然這次的優化結果很令人滿意,但後來我實際執行時發現有些比較複雜的套件(像是
JiebaR)可能跟系統其他套件相依性比較高,所以會有無法成功 import 的問題。 - 為了部署後的穩定性,我後來捨棄
ubuntu,還是選擇了兩個 Stage 都使用r-ver的那個版本(994 MB) - 但我猜如果是只要基本的一些 R 功能的話,應該
ubuntu的版本也是可以使用的。