Switching users in a Dockerfile to precompile a package

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.


FROM apluslms/grading-base:$BASE_TAG


## Julia-related variable names.



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_CHECKSUM_URL=https://julialang-s3.julialang.org/bin/checksums/${JULIA_CHECKSUMS}


## Install Julia.

	&& \
	&& \
	&& \
	sha256sum --check "$JULIA_CHECKSUM" \
	&& \
	tar -xvf "${JULIA_ARCHIVE}" --directory /usr/local/ \
	&& \
	ln -s "/usr/local/julia-${JULIA_VERSION}/bin/julia" /usr/local/bin/julia \
	&& \

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

ARG JULIA_DEVDIR_PATH='/julia_devdir'



# 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




	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


USER tester

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

USER tester


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
  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?

I’m not sure why that would be necessary, but try setting

ENV HOME="/home/tester"

right after switching to user tester.

This got rid of the error. One would think that switching users was enough to set the HOME directory as well, but apparently it is not.