Problems instantiating a Julia environment during a Dockerfile build process

Viewed 409

All,

I'm using Julia in a Docker container designed for development. Various developers use the same Dockerfile on a variety of host machines w/ different architectures. Julia is installed in the Docker image but the git repos holding the source are mounted volumes at run-time. It's working just fine except that every time the container is launched we have the hit for reinstantiating the Julia environment. So, I'm trying to bake the instantiated environment into the Docker image at build time.

After installing Julia into the Docker image I then temporarily COPY the project repos to a directory within the the image that is identical to where they will be mounted during run-time. Here is a snippet of the Dockerfile,

# syntax = edrevo/dockerfile-plus

ARG PROGRAM_NAME=wrong_program
ARG DOCKER_UBUNTU_VERSION=wrong_version
FROM ubuntu:${DOCKER_UBUNTU_VERSION}
SHELL ["/bin/bash", "-c"]

RUN apt-get update \
  && apt-get install -y -qq --no-install-recommends \
    libglvnd0 \
    libgl1 \
    libglx0 \
    libegl1 \
    libxext6 \
    libx11-6 \
    glmark2 \
    mesa-utils \
  && rm -rf /var/lib/apt/lists/*

# Env vars for the nvidia-container-runtime.
ENV NVIDIA_VISIBLE_DEVICES all
ENV NVIDIA_DRIVER_CAPABILITIES graphics,utility,compute

ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
    apt-get install -qqy --no-install-recommends \
    sudo \
    apt-utils \
    autotools-dev \
    build-essential \ 
    ca-certificates \
    g++ \
    git \
    iputils-ping \
    libssl-dev \
    nano \
    openssl \
    python-dev \
    unzip \
    vim \
    sed \
    x11-apps 

RUN update-ca-certificates

# Change to non-root privilege
# https://dev.to/emmanuelnk/using-sudo-without-password-prompt-as-non-root-docker-user-52bg
ARG USERNAME=wrongUser
ARG USERID=1000
ARG GID=101
ARG GROUPNAME=dev

RUN addgroup --gid ${GID} ${GROUPNAME}  \
    && adduser --uid ${USERID} --disabled-password --home /home/${USERNAME} --shell /bin/bash --gecos '' ${USERNAME} \
    && adduser  ${USERNAME} sudo \
    && adduser  ${USERNAME} dev  \
    && grep -qxF '%sudo ALL=(ALL) NOPASSWD:ALL' /etc/sudoers || echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers 

USER ${USERNAME}
RUN grep -qxF 'export LS_COLORS="$LS_COLORS:ow=1;34:tw=1;34:"' ~/.bashrc || echo 'export LS_COLORS="$LS_COLORS:ow=1;34:tw=1;34:"' >> ~/.bashrc

ARG TEMPDIR=${THIRDPARTYDIR}/temp 
RUN echo "THIRDPARTYDIR = ${THIRDPARTYDIR}" && \
    echo "TEMPDIR = ${TEMPDIR}"

#--------------------------------------
#  Install julia
#--------------------------------------
ARG JULIA_VERSION
ARG JULIA_DOWNLOAD_FILE
RUN echo "JULIA_VERSION = ${JULIA_VERSION}" && \
    echo "JULIA_DOWNLOAD_FILE = ${JULIA_DOWNLOAD_FILE}"

COPY --chown=${USERNAME}:${USERNAME} downloads/${JULIA_DOWNLOAD_FILE} ${THIRDPARTYDIR}/
RUN chown ${USERNAME}:${USERNAME} ${THIRDPARTYDIR}/${JULIA_DOWNLOAD_FILE} && \
    chmod 744 ${THIRDPARTYDIR}/${JULIA_DOWNLOAD_FILE} 
WORKDIR ${THIRDPARTYDIR}
RUN tar xf ${JULIA_DOWNLOAD_FILE} && \
    rm -rf ${JULIA_DOWNLOAD_FILE}

WORKDIR ${THIRDPARTYDIR}
RUN sudo ln -s ${THIRDPARTYDIR}/julia-${JULIA_VERSION}/bin/julia /usr/bin/julia && \ 
    sudo apt-get install -y wget
ENV CMAKE_PREFIX_PATH=${THIRDPARTYDIR}/julia-${JULIA_VERSION}:${CMAKE_PREFIX_PATH}

# instantiate all Julia Packages
RUN echo "intiantiating Julia Packages"
ARG WORKSPACEDIR
RUN mkdir -p ${WORKSPACEDIR}/src/
COPY --chown=${USERNAME}:${USERNAME} JuliaProjectManifests/ ${WORKSPACEDIR}/src/

RUN echo "WORKSPACEDIR = ${WORKSPACEDIR}" 

#RUN sudo chown -R ${USERNAME}:${GROUPNAME} /tmp
WORKDIR ${WORKSPACEDIR}/src/
COPY --chown=${USERNAME}:${USERNAME} ./julia_instantiate.sh ${WORKSPACEDIR}/src/
RUN chmod +x ./julia_instantiate.sh 
RUN source ./julia_instantiate.sh 
#RUN sudo -E -s \
#   && source ./julia_instantiate.sh \
#   && exit

WORKDIR ${THIRDPARTYDIR}
RUN rm -rf temp
RUN sudo ldconfig

WORKDIR ${THIRDPARTYDIR}/..

CMD ["bash"]

To help clarify more... the ARG environment variables are defined on the command line when kicking off the build. That script looks like:

DOCKER_BUILDKIT=1 docker build                                                            \
    --no-cache=false                                                          \
    --network=host   \
    --rm                                                                \
    --build-arg PROGRAM_NAME=${PROGRAM_NAME}                                    \
    --build-arg WORKSPACEDIR=${WORKSPACEDIR}                                    \
    --build-arg USERNAME=${USER}                                    \
    --build-arg USERID=${UID}                                    \
    --build-arg THIRDPARTYDIR=${THIRDPARTYDIR}                                    \
    --build-arg DOCKER_UBUNTU_VERSION=${DOCKER_UBUNTU_VERSION}                                    \
    --build-arg JULIA_VERSION=${JULIA_VERSION}                    \
    --build-arg JULIA_DOWNLOAD_FILE=${JULIA_DOWNLOAD_FILE}            \
    -f ${CDDS}/${DOCKER_BUILD_FOLDER}/${DOCKER_FILE_NAME} \
    -t ${DOCKER_IMAGE} \
    ${DOCKER_BUILD_CONTEXT} \
    2>&1 | tee ${CDDS}/${DOCKER_BUILD_FOLDER}/${DOCKER_BUILD_LOG}

When I try to instantiate the project during the docker build I get the following error,

#115 sha256:0c589043bbee81f6e25ae6da0177acfea129c478ba518ca117a39838dc68d6f1
#115 0.148 iKinQP/
#115 0.924  Installing known registries into `~/.julia`
#115 1.032 ERROR: SystemError: mktemp: No such file or directory
#115 1.703 Stacktrace:
#115 1.935  [1] systemerror(::Symbol, ::Int32; extrainfo::Nothing) at ./error.jl:168
#115 2.272  [2] #systemerror#48 at ./error.jl:167 [inlined]
#115 2.279  [3] systemerror at ./error.jl:167 [inlined]
#115 2.279  [4] #mktemp#18 at ./file.jl:589 [inlined]
#115 2.279  [5] mktemp at ./file.jl:587 [inlined] (repeats 2 times)
#115 2.298  [6] probe_platform_engines!(; verbose::Bool) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.5/Pkg/src/PlatformEngines.jl:280
#115 2.299  [7] probe_platform_engines! at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.5/Pkg/src/PlatformEngines.jl:181 [inlined]
#115 2.299  [8] pkg_server_registry_urls() at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.5/Pkg/src/Types.jl:944
#115 2.349  [9] clone_default_registries(::Pkg.Types.Context; only_if_empty::Bool) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.5/Pkg/src/Types.jl:868
#115 2.350  [10] clone_default_registries at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.5/Pkg/src/Types.jl:862 [inlined]
#115 2.350  [11] find_registered!(::Pkg.Types.Context, ::Array{String,1}, ::Array{Base.UUID,1}) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.5/Pkg/src/Types.jl:1239
#115 2.355  [12] find_registered! at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.5/Pkg/src/Types.jl:1200 [inlined]
#115 2.355  [13] check_registered(::Pkg.Types.Context, ::Array{Pkg.Types.PackageSpec,1}) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.5/Pkg/src/Operations.jl:1054
#115 2.356  [14] instantiate(::Pkg.Types.Context; manifest::Nothing, update_registry::Bool, verbose::Bool, platform::Pkg.BinaryPlatforms.Linux, kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.5/Pkg/src/API.jl:833
#115 2.403  [15] instantiate(::Pkg.Types.Context) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.5/Pkg/src/API.jl:795
#115 2.404  [16] #instantiate#169 at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.5/Pkg/src/API.jl:791 [inlined]
#115 2.404  [17] instantiate() at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.5/Pkg/src/API.jl:791
#115 2.404  [18] top-level scope at none:1
#115 DONE 2.5s

Here is julia_instantiate.sh being RUN by the Dockerfile,

#!/bin/bash

cd ${WORKSPACEDIR}/src/;
folders=$(ls -d */) 
echo ${folders} 
for m in ${folders[@]}; do
        cd "${WORKSPACEDIR}/src/${m}"
        echo "instantiating: ${WORKSPACEDIR}/src/${m}"
        julia --project=. -e "using Pkg; Pkg.instantiate(); using $(echo "${m}" | tr -d /); exit();"
done
  • Docker image OS: Ubuntu 18.04
  • Host OS: WSL2 Ubuntu 20.04
  • Docker version: 20.10.6, build 370c289
  • Julia version: 1.5.3

Julia issue #33593 seems related but it doesn't give me a work around.

Thoughts on how to work around this?

EDIT: Another point is that if I COPY julia_instantiate.sh into the docker image but don't execute it during the build, I get an image with the script and the Julia package source files in the image. If I run this container interactively and manually run julia_instantiate.sh it runs just fine. I run the container and attach as the same user that is baked into the docker image. So, it seams that the process fails only when the script is run by docker build.

NOTE: I've updated *julia_instantiate.sh to the minimal that I've been testing with. I've also added some additional information about the Dockerfile.

1 Answers

I'm posting an answer but its only partial, as I am in the midst of learning how to do the same thing. I'm having trouble understanding the dockerfile given, I think the approach might be complicating things and making it hard to find errors.

Here is the approach (not compeletely documented) that has led to me getting a running julia image with packages installed and instantiated.

  1. Build from a base image and install julia
  • based on the julia dockerfile at dockerhub
  • this makes a nice starting point for other julia workers based on various package combinations.
  • I'm required to build from AL2 base instead of the usual public julia dockerfiles, thats the only reason to do this step separately.
  1. FROM this julia base image, run the Pkg.add for various packages, after which run instantiate
# the important parts of dockerfile
RUN julia -e 'using Pkg; Pkg.add("Pipe", preserve=PRESERVE_DIRECT);'
RUN julia -e 'using Pkg; Pkg.add("DataFrames", preserve=PRESERVE_DIRECT);'
RUN julia -e 'using Pkg; Pkg.add("CSV", preserve=PRESERVE_DIRECT);'

RUN set -eux; \
    mkdir "$JULIA_USER_HOME";

RUN julia -e 'using Pkg; Pkg.instantiate();'

This post from bkamins@, "My practices for managing project dependencies in Julia" helped a lot, particularly the dependency preservation commands available in Pkg.add.

Sorry this is not a complete solution, I haven't completed the process yet myself but these are things I wish I had found on SO when I started.

Related