I'm trying to build a Docker Python 3 image which runs as non-root user by default. However, for development purposes on my local machine, I would like to be able to run a container based on this image as root user, for example because it simplifies access to mounted volumes.
My problem is that I don't know how to seamlessly share the non-root user specific pip packages with the root user. So far I have tried to add a target to pip.conf of the root user which seems to work as intended but already installed packages under the target path are always attempted to be reinstalled. Take this Dockerfile as example:
FROM python:3.8.12-slim-bullseye
ARG VERSION
ARG USER=someuser
ARG GROUP=somegroup
ARG UID=1000
ARG GID=1000
ENV SOME_USER ${USER}
RUN pip install --upgrade pip
# add user to group and add home dir for new user
RUN groupadd -g ${GID} ${GROUP} && useradd -u ${UID} -g ${GROUP} -s /bin/bash -m ${USER}
# Make pip executed as root point to non-root user's packages
RUN mkdir /root/.pip
RUN echo "[global]\ntarget=/home/${USER}/.local/lib/python3.8/site-packages" > /root/.pip/pip.conf
# Switch to non-root user
USER ${UID}:${GID}
# set workdir to home dir of new user and add .local/bin of this user to PATH
WORKDIR /home/${USER}
ENV PATH="/home/${SOME_USER}/.local/bin:${PATH}"
# extend Python path so that root user can always access non-root user's packages
ENV PYTHONPATH=/"home/${SOME_USER}/.local/lib/python3.8/site-packages"
# Install some random lib for demo purposes
RUN pip install --user hug
I can build this image without issues, but installing packages with Pip as root user does not work so well:
$ docker run -it --rm -u root myimage:0.0 /bin/bash
And then inside the container:
root@d37b052f939d:/home/someuser# pip install hug
Which produces:
Collecting hug
Downloading hug-2.6.1-py2.py3-none-any.whl (75 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 75.1/75.1 KB 3.2 MB/s eta 0:00:00
Collecting requests
Downloading requests-2.27.1-py2.py3-none-any.whl (63 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 63.1/63.1 KB 2.5 MB/s eta 0:00:00
(...)
Installing collected packages: certifi, urllib3, idna, falcon, charset-normalizer, requests, hug
Successfully installed certifi-2021.10.8 charset-normalizer-2.0.11 falcon-2.0.0 hug-2.6.1 idna-3.3 requests-2.27.1 urllib3-1.26.8
WARNING: Target directory /home/someuser/.local/lib/python3.8/site-packages/charset_normalizer already exists. Specify --upgrade to force replacement.
WARNING: Target directory /home/someuser/.local/lib/python3.8/site-packages/hug-2.6.1.dist-info already exists. Specify --upgrade to force replacement.
(...)
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behavior with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
It seems that under the root user, Pip does not intelligently check if a desired package is already installed within the target directory. Packages are downladed and attempted to be installed regardless whether they're already installed under the target or not. In case of this toy example it doesn't matter but in a real-world use case with dozens of packages, some which must be built from source at every installation, this becomes unnecessarily time consuming. On the other hand, it works as expected when I run the container as the new default user:
someuser@0e9533629517:~$ pip install --user hug
Requirement already satisfied: hug in ./.local/lib/python3.8/site-packages (2.6.1)
Requirement already satisfied: requests in ./.local/lib/python3.8/site-packages (from hug) (2.27.1)
(...)
Any ideas how to achieve this behavior with the root user?