Switching users in a Dockerfile to precompile a package

SeSodesa
Mon, 03 Apr 2023 05:47:54 +0000
Tooling

I would like to build a Docker image for grading student submissions on the A+ LMS. In addition to the base Debian system, the image would contain two separate Julia projects: /exercise_project and /tests. The student submission is copied under the src folder of the first, and teacher-specified test files under the src folder of the second, when the container is started. Then the second project would be used to test the student submission.

The reason I would like to try this approach instead of just having a single project with a test folder (which I currently have a working version of in grade-julia) is because using the default unit testing approach becomes insufferably slow, when any dependencies are added to the test module. This is of course because Julia has to pre-compile the unit test depedencies under a new process.

My current attempt at a Dockerfile is as follows:

### Dockerfile
#
# This file instructs Docker how to build the grading container. If everything
# goes as intended, it
#
# 1. downloads Julia from the official website with cURL,
# 2. downloads the checksum file from the same website with cURL,
# 3. checks the downloaded Julia archive against the checksum with sha256sum,
# 4. unpacks the downloaded Julia archive to /usr/local/
# 5. symbolically links the contained binary to /usr/local/bin and
# 6. removes the downloaded files to reduce final image size.
#

## A+ grader-related variables and inheriting from grading-base.

ARG BASE_TAG=latest

FROM apluslms/grading-base:$BASE_TAG

ARG GRADER_UTILS_VER=4.8

## Julia-related variable names.

ARG JULIA_BRANCH=1.8

ARG JULIA_VERSION=${JULIA_BRANCH}.5

ARG JULIA_ARCHIVE=julia-${JULIA_VERSION}-linux-x86_64.tar.gz

ARG JULIA_DOWNLOAD_URL=https://julialang-s3.julialang.org/bin/linux/x64/${JULIA_BRANCH}/${JULIA_ARCHIVE}

ARG JULIA_CHECKSUMS=julia-${JULIA_VERSION}.sha256

ARG JULIA_CHECKSUM_URL=https://julialang-s3.julialang.org/bin/checksums/${JULIA_CHECKSUMS}

ARG JULIA_CHECKSUM=julia.sha256

## Install Julia.

RUN \
	curl -o "$JULIA_ARCHIVE" "$JULIA_DOWNLOAD_URL" \
	&& \
	curl -o "$JULIA_CHECKSUMS" "$JULIA_CHECKSUM_URL" \
	&& \
	grep "${JULIA_ARCHIVE}" "$JULIA_CHECKSUMS" > "$JULIA_CHECKSUM" \
	&& \
	sha256sum --check "$JULIA_CHECKSUM" \
	&& \
	tar -xvf "${JULIA_ARCHIVE}" --directory /usr/local/ \
	&& \
	ln -s "/usr/local/julia-${JULIA_VERSION}/bin/julia" /usr/local/bin/julia \
	&& \
	rm "${JULIA_ARCHIVE}" "${JULIA_CHECKSUMS}" "${JULIA_CHECKSUM}"

## Create and set local Julia package discovery path for all users.

ARG JULIA_DEVDIR_PATH='/julia_devdir'

ENV JULIA_PKG_DEVDIR="${JULIA_DEVDIR_PATH}"

RUN mkdir $JULIA_DEVDIR_PATH

# Copy the exercise and test project skeletons in this repository into the
# grader and start working there. The tester user is provided by grading-base.

ARG EXERCISE_FOLDER=/exercise_project

ARG TEST_FOLDER=/tests

COPY exercise/ $EXERCISE_FOLDER

COPY tests/ $TEST_FOLDER

RUN \
	chown -R tester:tester $EXERCISE_FOLDER \
	&& \
	chown -R tester:tester $TEST_FOLDER

# Precompile the exercise project skeleton as tester. This also grants the
# project the necessary permissions, when the grader is started.

USER tester

WORKDIR $EXERCISE_FOLDER

USER tester

RUN julia --project='.' -e 'println(ENV); import Pkg; Pkg.activate("."); Pkg.precompile()'

USER tester

WORKDIR $TEST_FOLDER

USER tester

RUN julia --project='.' -e 'println(ENV); import Pkg; Pkg.activate("."); Pkg.develop(path="/exercise_project"); Pkg.precompile()'

# Copy shell scripts into a folder on the path.

COPY sh/ /usr/local/bin/

# Set new entrypoint.

ENTRYPOINT [ "/usr/local/bin/grading-wrapper" ]

CMD ["/exercise/run.sh"]

This throws the following error:

>> docker build --tag sesodesa/grade-julia:latest .
[+] Building 3.6s (13/16)
 => [internal] load .dockerignore                                                                                                  0.0s
 => => transferring context: 2B                                                                                                    0.0s
 => [internal] load build definition from Dockerfile                                                                               0.0s
 => => transferring dockerfile: 2.79kB                                                                                             0.0s
 => [internal] load metadata for docker.io/apluslms/grading-base:latest                                                            1.0s
 => [auth] apluslms/grading-base:pull token for registry-1.docker.io                                                               0.0s
 => [ 1/11] FROM docker.io/apluslms/grading-base:latest@sha256:5de2d77bda8867e70eaa82d8afb0845207ccc557c89f6300c2a77bb4ece1259a    0.0s
 => [internal] load build context                                                                                                  0.0s
 => => transferring context: 792B                                                                                                  0.0s
 => CACHED [ 2/11] RUN  curl -o "julia-1.8.5-linux-x86_64.tar.gz" "https://julialang-s3.julialang.org/bin/linux/x64/1.8/julia-1.8  0.0s
 => CACHED [ 3/11] RUN mkdir /julia_devdir                                                                                         0.0s
 => CACHED [ 4/11] COPY exercise/ /exercise_project                                                                                0.0s
 => CACHED [ 5/11] COPY tests/ /tests                                                                                              0.0s
 => CACHED [ 6/11] RUN  chown -R tester:tester /exercise_project  &&  chown -R tester:tester /tests                                0.0s
 => CACHED [ 7/11] WORKDIR /exercise_project                                                                                       0.0s
 => ERROR [ 8/11] RUN julia --project='.' -e 'println(ENV); import Pkg; Pkg.activate("."); Pkg.precompile()'                       2.5s
------
 > [ 8/11] RUN julia --project='.' -e 'println(ENV); import Pkg; Pkg.activate("."); Pkg.precompile()':
#0 1.032 ERROR: IOError: stat("/root/.julia/config/startup.jl"): permission denied (EACCES)
#0 2.423 Stacktrace:
#0 2.423  [1] uv_error
#0 2.423    @ ./libuv.jl:97 [inlined]
#0 2.423  [2] stat(path::String)
#0 2.476    @ Base.Filesystem ./stat.jl:152
#0 2.477  [3] isfile
#0 2.477    @ ./stat.jl:456 [inlined]
#0 2.477  [4] _local_julia_startup_file
#0 2.477    @ ./client.jl:340 [inlined]
#0 2.477  [5] load_julia_startup()
#0 2.477    @ Base ./client.jl:348
#0 2.477  [6] exec_options(opts::Base.JLOptions)
#0 2.477    @ Base ./client.jl:266
#0 2.477  [7] _start()
#0 2.477    @ Base ./client.jl:522
------
Dockerfile:88
--------------------
  86 |     USER tester
  87 |
  88 | >>> RUN julia --project='.' -e 'println(ENV); import Pkg; Pkg.activate("."); Pkg.precompile()'
  89 |
  90 |     USER tester
--------------------
ERROR: failed to solve: process "/bin/sh -c julia --project='.' -e 'println(ENV); import Pkg; Pkg.activate(\".\"); Pkg.precompile()'" did not complete successfully: exit code: 1

Now it seems that even when I switch users to tester, that was defined in the base image, when I am trying to run the first Julia command to set up and precompile a project, the container seems to think that I am still the root user, as the Julia startup script in the /root folder is being accessed. I would like the $HOME folder to be that of tester, as that is the user that will be running the tests, and therefore any dependencies added during container construction need to be available to that user.

Why is the Dockerfile not properly switching users during the build script?

3 posts - 2 participants