-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDockerfile.three-stage
More file actions
161 lines (139 loc) · 6.06 KB
/
Dockerfile.three-stage
File metadata and controls
161 lines (139 loc) · 6.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# ============================================================================
# THREE-STAGE DOCKERFILE (ADVANCED OPTIMIZATION)
# ============================================================================
# Architecture based on change frequency and package sources:
#
# BASE: CRAN packages (stable, rarely change) + build tools
# BUILDER: Custom/private packages (change independently from CRAN)
# RUNTIME: Pure application + runtime libraries only (NO build tools)
#
# Key advantages:
# 1. CRAN packages cached separately from custom packages
# 2. Custom package changes don't invalidate CRAN package layer
# 3. Runtime image is completely clean - no build tools whatsoever
# 4. Optimal layer caching based on change frequency
# ============================================================================
# ============================================================================
# STAGE 1: Base - CRAN packages and build environment
# ============================================================================
FROM rocker/r2u:24.04 AS base
# Configure renv cache to use the same path as runtime
# This ensures symlinks remain valid when copied to runtime stage
ENV RENV_PATHS_CACHE="/app/renv/.cache"
# Install system dependencies needed for compilation AND runtime
# Build tools are needed here to compile CRAN packages
RUN apt-get update && apt-get install -y \
# Build tools (needed for CRAN package compilation)
libcurl4-openssl-dev \
libssl-dev \
libxml2-dev \
libfontconfig1-dev \
libharfbuzz-dev \
libfribidi-dev \
libfreetype6-dev \
libpng-dev \
libtiff5-dev \
libjpeg-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# OPTIMIZATION 1: Copy only dependency files first
# This creates a cache-friendly layer that only rebuilds when CRAN dependencies change
COPY renv.lock renv.lock
COPY .Rprofile .Rprofile
COPY renv/activate.R renv/activate.R
COPY renv/settings.json renv/settings.json
# OPTIMIZATION 2: Install renv and restore CRAN packages
# This layer is cached unless renv.lock changes
# CRAN packages change rarely, so this provides excellent caching
RUN R -e "install.packages('renv', repos = 'https://cloud.r-project.org')"
RUN R -e "renv::restore()"
# ============================================================================
# STAGE 2: Builder - Custom/private packages
# ============================================================================
FROM base AS builder
# Inherit all CRAN packages and build tools from base
# Add any additional build dependencies for custom packages here if needed
# RUN apt-get update && apt-get install -y \
# <additional-build-deps> \
# && rm -rf /var/lib/apt/lists/*
WORKDIR /build
# Copy and build custom packages (not on CRAN)
# These might be:
# - Private packages from GitHub/GitLab
# - Internal company packages
# - Packages built from local source
#
# Example (uncomment and modify as needed):
# COPY custom_packages/ custom_packages/
# RUN R CMD INSTALL custom_packages/mypackage
# For now, just inherit from base
# In a real scenario, you'd install custom packages here:
# RUN R -e "remotes::install_github('yourorg/yourpackage')"
# Copy application code
# This is the most frequently changing layer
COPY app.R app.R
# ============================================================================
# STAGE 3: Runtime - Pure application (NO build tools)
# ============================================================================
FROM rocker/r2u:24.04
# Configure renv cache location to match where packages were installed
ENV RENV_PATHS_CACHE="/app/renv/.cache"
# Install ONLY runtime system dependencies (no -dev packages, no build tools)
# Note: Using minimal runtime libraries without -dev packages
RUN apt-get update && apt-get install -y --no-install-recommends \
libcurl4 \
libssl3 \
libxml2 \
libfontconfig1 \
libharfbuzz0b \
libfribidi0 \
libfreetype6 \
libpng16-16t64 \
libtiff6 \
libjpeg-turbo8 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy CRAN packages from base stage (includes cache at /app/renv/.cache)
COPY --from=base /app/renv /app/renv
COPY --from=base /app/.Rprofile /app/.Rprofile
COPY --from=base /app/renv.lock /app/renv.lock
# Copy custom packages from builder stage (if any were installed)
# COPY --from=builder /usr/local/lib/R/site-library/mypackage /usr/local/lib/R/site-library/mypackage
# Copy application code from builder
COPY --from=builder /build/app.R /app/app.R
# Expose Shiny port
EXPOSE 3838
# Run the application
# Note: Use --vanilla to skip .Rprofile and avoid renv activation at runtime
# The renv library path is already in R's search path
CMD ["R", "--vanilla", "-e", ".libPaths('/app/renv/library/linux-ubuntu-noble/R-4.5/x86_64-pc-linux-gnu'); shiny::runApp('/app', host = '0.0.0.0', port = 3838)"]
# ============================================================================
# Layer Optimization Strategy:
# ============================================================================
#
# CRAN packages (base): Changes LEAST frequently → Cached most aggressively
# Custom packages (builder): Changes MORE frequently → Independent cache layer
# Application code (runtime): Changes MOST frequently → Doesn't invalidate above
#
# Build flow:
# 1. Base layer compiles CRAN packages (cached unless renv.lock changes)
# 2. Builder adds custom packages (cached unless custom package sources change)
# 3. Runtime copies everything and adds application code
# 4. Code changes only invalidate the final COPY app.R step
#
# ============================================================================
# Build and Run Commands:
# ============================================================================
# Build (three-stage):
# docker build -f Dockerfile.three-stage -t shiny-app:three-stage .
#
# Run:
# docker run -p 3838:3838 shiny-app:three-stage
#
# Verify no build tools in runtime:
# docker run --rm shiny-app:three-stage dpkg -l | grep -E "dev|gcc|g\+\+"
# (should return nothing)
#
# Compare sizes:
# docker images | grep shiny-app
# ============================================================================