Home > Mobile >  How to avoid changing permissions on node_modules for a non-root user in docker
How to avoid changing permissions on node_modules for a non-root user in docker

Time:11-15

The issue with my current files is that in my entrypoint.sh file, I have to change the ownership of my entire project directory to the non-administrative user (chown -R node /node-servers). However, when a lot of npm packages are installed, this takes a lot of time. Is there a way to avoid having to chown the node_modules directory?

Background: The reason I create everything as root in the Dockerfile is because this way I can match the UID and GID of a developer's local user. This enables mounting volumes more easily. The downside is that I have to step-down from root in an entrypoint.sh file and ensure that the permissions of the entire project files have all been changed to the non-administrative user.

my docker file:

FROM node:10.24-alpine

#image already has user node and group node which are 1000, thats what we will use

# grab gosu for easy step-down from root
# https://github.com/tianon/gosu/releases
ENV GOSU_VERSION 1.14

RUN set -eux; \
    \
    apk add --no-cache --virtual .gosu-deps \
        ca-certificates \
        dpkg \
        gnupg \
    ; \
    \
    dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
    wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
    wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
    \
# verify the signature
    export GNUPGHOME="$(mktemp -d)"; \
    gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
    gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
    command -v gpgconf && gpgconf --kill all || :; \
    rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
    \
# clean up fetch dependencies
    apk del --no-network .gosu-deps; \
    \
    chmod  x /usr/local/bin/gosu; \
# verify that the binary works
    gosu --version; \
    gosu nobody true


COPY ./ /node-servers

# Setting the working directory
WORKDIR /node-servers

# Install app dependencies
# Install openssl
RUN apk add --update openssl ca-certificates && \
    apk --no-cache add shadow && \
    apk add libcap && \
    npm install -g && \
    chmod  x /node-servers/entrypoint.sh && \
    setcap cap_net_bind_service= ep /usr/local/bin/node

# Entrypoint used to load the environment and start the node server
#ENTRYPOINT ["/bin/sh"]

my entrypoint.sh

# In Prod, this may be configured with a GID already matching the container
# allowing the container to be run directly as Jenkins. In Dev, or on unknown
# environments, run the container as root to automatically correct docker
# group in container to match the docker.sock GID mounted from the host
set -x
if [ -z ${HOST_UID x} ]; then
    echo "HOST_UID not set, so we are not changing it"
else
    echo "HOST_UID is set, so we are changing the container UID to match"
    # get group of notadmin inside container
    usermod -u ${HOST_UID} node
    CUR_GID=`getent group node | cut -f3 -d: || true`
    echo ${CUR_GID}
    # if they don't match, adjust
    if [ ! -z "$HOST_GID" -a "$HOST_GID" != "$CUR_GID" ]; then
        groupmod -g ${HOST_GID} -o node
    fi
    if ! groups node | grep -q node; then
        usermod -aG node node
    fi
fi

# gosu drops from root to node user
set -- gosu node "$@"

[ -d "/node-servers" ] && chown -v -R node /node-servers

exec "$@"

CodePudding user response:

You shouldn't need to run chown at all here. Leave the files owned by root (or by the host user). So long as they're world-readable the application will still be able to run; but if there's some sort of security issue or other bug, the application won't be able to accidentally overwrite its own source code.

You can then go on to simplify this even further. For most purposes, users in Unix are identified by their numeric user ID; there isn't actually a requirement that the user be listed in /etc/passwd. If you don't need to change the node user ID and you don't need to chown files, then the entrypoint script reduces to "switch user IDs and run the main script"; but then Docker can provide an alternate user ID for you via the docker run -u option. That means you don't need to install gosu either, which is a lot of the Dockerfile content.

All of this means you can reduce the Dockerfile to:

FROM node:10.24-alpine

# Install OS-level dependencies (before you COPY anything in)
apk add openssl ca-certificates

# (Do not install gosu or its various dependencies)

# Set (and create) the working directory
WORKDIR /node-servers

# Copy language-level dependencies in
COPY package.json package-lock.json .
RUN npm ci

# Copy the rest of the application in
# (make sure `node_modules` is in .dockerignore)
COPY . .

# (Do not call setcap here)

# Set the main command to run
USER node
CMD npm run start

Then when you run the container, you can use Docker options to specify the current user and additional capability.

docker run                          \
  -d                                \ # in the background
  -u $(id -u)                       \ # as an alternate user
  -v "$PWD/data:/node-servers/data" \ # mounting a data directory
  -p 8080:80                        \ # publishing a port
  my-image

Docker grants the NET_BIND_SERVICE capability by default so you don't need to specially set it.

This same permission setup will work if you're using bind mounts to overwrite the application code; again, without a chown call.

docker run ...                  \
  -u $(id -u)                   \
  -v "$PWD:/node-servers"       \ # run the application from the host, not the image
  -v /node-servers/node_modules \ # with libraries that will not be updated ever
  ...
  • Related