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