0

I have a fairly simple Dockerfile, with python:3.9 as my base image.

FROM python:3.9 as builder

WORKDIR /server/
COPY Pipfile Pipfile.lock /server/

RUN pip install pipenv --no-cache-dir \
    && pipenv install --system --deploy

COPY . /server/

However I was advised I should not use any commands inside this image as root user, and instead create my own user first.

However when I do this, the pipenv install command suddenly fails: /bin/sh: 1: pipenv: not found

I'm struggling to understand why this would fail, and have a difficult time googling, since I have no idea what to even google for at this point.

FROM python:3.9 as builder

RUN useradd -ms /bin/bash sorted
USER sorted

WORKDIR /server/
COPY Pipfile Pipfile.lock /server/

RUN pip install pipenv --no-cache-dir \
    && pipenv install --system --deploy

COPY . /server/
Mike
  • 45
  • 1
  • 7

1 Answers1

1

When you switch USER to a non-root user before running pip, it's able to figure out that it can't write to the "system" Python directories, and so it instead runs in user mode. This puts both Python packages and Python entry point scripts into a subdirectory of the current user's home directory, which isn't especially well-defined in Docker. I'd guess that you have a /.local/bin/pipenv script inside the image, but that directory isn't on the normal command search $PATH.

The typical advice I've heard around Docker is that a container shouldn't run as root, but the Dockerfile is relatively trusted and it's okay to have RUN commands as root (in a couple of cases this is required). If you RUN pip install as root then the wrapper scripts will go into /usr/local/bin, which you can find normally.

FROM python:3.9 as builder

RUN useradd -r sorted

# still as root
RUN pip install pipenv --no-cache-dir \
    && pipenv install --system --deploy

# only when you go to run the container
USER sorted
CMD ["./main.py"]

If you really can't run anything at all as root in the Dockerfile then you need to find the "user" directory. The pip documentation has many examples of using python -m pip instead of the pip wrapper and this will have an extended search path. You'll also need to run pipenv in its default "user" mode, which will mean wrapping every Python-related invocation in pipenv run (also see How to get pipenv running in docker?).

FROM python:3.9 as builder

RUN useradd -r sorted
USER sorted
ENV PATH=$HOME/.local/bin:$PATH

RUN pip install pipenv --no-cache-dir \
    && pipenv install --deploy

CMD ["pipenv", "run", "./main.py"]

The one particular advantage of this setup is around multi-stage builds. Your Dockerfile uses multi-stage build syntax, and some Python libraries depend on C extensions that have heavy-weight dependencies, but only at build time. If the build and final images use the same base image then you can COPY the entire virtual environment across, which will let you avoid installing large meta-packages like build-essential in the final images. (Note, again, that the Debian apt-get package manager must run as root.)

David Maze
  • 130,717
  • 29
  • 175
  • 215
  • Thank you for the explanation. With your first example, I was able to get my Dockerfile up and running again! – Mike Aug 15 '23 at 07:04