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?