An issue when building multistage docker

I have a Multistage Docker file and I am getting an error when trying to build it. I want R packages to be cached to make docker builds faster. Also this is connected to gitlab CI/CD for auto deployment to Cloud run

# Stage 1: Install R and R packages in a separate build stage
FROM r-base:4.4.1  as r-build

# Install necessary system dependencies
RUN apt-get update && apt-get install -y \
    software-properties-common \
    dirmngr \
    gnupg \
    apt-transport-https \
    ca-certificates \
    lsb-release \
    wget \
    libcurl4-openssl-dev \
    libssl-dev \
    libxml2-dev \
    libxt-dev

# Install R packages
RUN Rscript -e "install.packages('forecast', dependencies=TRUE)"
RUN Rscript -e "install.packages('lubridate')"
RUN Rscript -e "install.packages('googleCloudStorageR')"

# Stage 2: Build the final image
FROM ubuntu:latest as final_stage

# Copy the installed R libraries from the previous stage
COPY --from=r-build /usr/local/lib/R/site-library /usr/local/lib/R/site-library

# Set non-interactive frontend for apt-get
ENV DEBIAN_FRONTEND=noninteractive
ARG ENV=test

# Install necessary system dependencies
RUN apt-get update && apt-get install -y \
    install r-base \
    build-essential \
    python3-dev \
    libffi-dev \
    libssl-dev \
    libxml2-dev \
    libxslt1-dev \
    libcurl4-openssl-dev \
    zlib1g-dev \
    libjpeg-dev \
    libpng-dev \
    r-base \
    systemd \
    libstdc++6 \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# Set the working directory
WORKDIR /home/app

# Copy over requirements.txt and upgrade pip and setuptools before installing dependencies
COPY requirements.txt .
RUN python3 -m pip install --upgrade pip setuptools
RUN python3 -m pip install --no-cache-dir -r requirements.txt

# Copy the rest of the application code
COPY config/config_$ENV.py ./config/config.py
COPY run.py .
COPY startup_docker.sh .
COPY setup.sh .
COPY app ./app
COPY app/credentials.json credentials.json

# Ensure scripts have execution permissions
RUN chmod +x /home/app/setup.sh /home/app/startup_docker.sh

# Run the setup script (this is where R packages might be installed)
RUN ./setup.sh

# Set the entrypoint to the startup script
CMD ["python3", "/home/app/run.py"]

Here is the error

The following packages have unmet dependencies:
r-base : Depends: r-base-core (>= 4.4.1-1.2204.0) but it is not going to be installed
Depends: r-recommended (= 4.4.1-1.2204.0) but it is not going to be installed
Recommends: r-base-html but it is not going to be installed
Recommends: r-doc-html but it is not going to be installed
E: Unable to correct problems, you have held broken packages.

The error you’re encountering is related to the installation of r-base and its dependencies in the final stage of your Docker build. Since you’re copying the R packages from the previous stage (r-build), you don’t need to install r-base again in the final stage. Additionally, you can optimize the Dockerfile for caching R packages, which should make subsequent builds faster.

Here’s how you can modify your Dockerfile:

Modified Dockerfile

# Stage 1: Install R and R packages in a separate build stage
FROM r-base:4.4.1 as r-build

# Install necessary system dependencies
RUN apt-get update && apt-get install -y \
    software-properties-common \
    dirmngr \
    gnupg \
    apt-transport-https \
    ca-certificates \
    lsb-release \
    wget \
    libcurl4-openssl-dev \
    libssl-dev \
    libxml2-dev \
    libxt-dev

# Install R packages (with caching)
COPY install_packages.R /tmp/
RUN Rscript /tmp/install_packages.R

# Stage 2: Build the final image
FROM ubuntu:latest as final_stage

# Copy the installed R libraries from the previous stage
COPY --from=r-build /usr/local/lib/R/site-library /usr/local/lib/R/site-library

# Set non-interactive frontend for apt-get
ENV DEBIAN_FRONTEND=noninteractive
ARG ENV=test

# Install necessary system dependencies
RUN apt-get update && apt-get install -y \
    build-essential \
    python3-dev \
    libffi-dev \
    libssl-dev \
    libxml2-dev \
    libxslt1-dev \
    libcurl4-openssl-dev \
    zlib1g-dev \
    libjpeg-dev \
    libpng-dev \
    r-base \
    systemd \
    libstdc++6 \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# Set the working directory
WORKDIR /home/app

# Copy over requirements.txt and upgrade pip and setuptools before installing dependencies
COPY requirements.txt .
RUN python3 -m pip install --upgrade pip setuptools
RUN python3 -m pip install --no-cache-dir -r requirements.txt

# Copy the rest of the application code
COPY config/config_$ENV.py ./config/config.py
COPY run.py .
COPY startup_docker.sh .
COPY setup.sh .
COPY app ./app
COPY app/credentials.json credentials.json

# Ensure scripts have execution permissions
RUN chmod +x /home/app/setup.sh /home/app/startup_docker.sh

# Run the setup script (this is where R packages might be installed)
RUN ./setup.sh

# Set the entrypoint to the startup script
CMD ["python3", "/home/app/run.py"]

Explanation:

  1. Install R Packages in a Single RUN Statement:

    • The R packages are installed in a single RUN command to improve caching. This ensures that if no R package changes, the layer will be cached, speeding up the build.
  2. Install Packages Script (install_packages.R):

    • Create a script install_packages.R and place it in the root directory of your project:
      install.packages('forecast', dependencies=TRUE)
      install.packages('lubridate')
      install.packages('googleCloudStorageR')
      
    • This script will be copied to the container and executed, installing all the required R packages.
  3. No Need to Reinstall r-base:

    • Since you’re copying the R libraries from the r-build stage, you don’t need to reinstall r-base in the final stage. This avoids conflicts and unnecessary installations.

Deploying with GitLab CI/CD to Cloud Run

Ensure your .gitlab-ci.yml is set up to build the Docker image correctly. Here’s a simple example:

stages:
  - build
  - deploy

build:
  stage: build
  script:
    - docker build -t gcr.io/$PROJECT_ID/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA .
    - docker push gcr.io/$PROJECT_ID/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA

deploy:
  stage: deploy
  script:
    - gcloud run deploy $SERVICE_NAME --image gcr.io/$PROJECT_ID/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA --platform managed --region $REGION --allow-unauthenticated
  environment:
    name: production
    url: https://$SERVICE_NAME-$REGION.a.run.app

This setup should resolve the issue and also make your Docker builds more efficient by caching R packages.