docker-node icon indicating copy to clipboard operation
docker-node copied to clipboard

fails to invoke npm when used in run-scripts

Open electriquo opened this issue 2 years ago • 24 comments

Environment

  • Platform: MacOS
  • Docker Version: 20.10.16
  • Node.js Version: 16.15.1
  • Image Tag: node:16.15.1-alpine

Expected Behavior

Given the following package.json

{
  "name": "test",
  "scripts": {
    "ver": "npm --version"
  },
  "devDependencies": {
    "cowsay": "^1.5.0"
  }
}
$ npm run ver

> ver
> npm --version

8.12.1

The same behavior should be replicated within a container runtime

Current Behavior

When running within a container runtime, npm fails and returns 243 exit code

$ docker run --rm -v $PWD:/app --workdir /app --entrypoint npm node:16.15.1-alpine run ver

> ver
> npm --version


npm notice
npm notice New minor version of npm available! 8.11.0 -> 8.12.1
npm notice Changelog: <https://github.com/npm/cli/releases/tag/v8.12.1>
npm notice Run `npm install -g [email protected]` to update!
npm notice

$ echo $?
243

When using a different image, it works as expected

$ docker run --rm -v $PWD:/app --workdir /app --entrypoint npm node:14-alpine run ver

> test@ ver /app
> npm --version

6.14.17

It works on on some images (note the --user node:node option)

$ docker run --rm -v $PWD:/app --workdir /app --entrypoint npm node:16.15.0-alpine3.15@sha256:bb776153f81d6e931211e3cadd7eef92c811e7086993b685d1f40242d486b9bb run ver

> ver
> npm --version

8.5.5
npm notice
npm notice New minor version of npm available! 8.5.5 -> 8.12.1
npm notice Changelog: <https://github.com/npm/cli/releases/tag/v8.12.1>
npm notice Run `npm install -g [email protected]` to update!
npm notice

$ docker run --rm -v $PWD:/app --workdir /app --entrypoint npm --user node:node node:18-alpine run ver

> ver
> npm --version

8.11.0

when the node user is used, then there a permission issue when mount volumes are used.

$ docker run --rm -it -v $PWD:/src -v node_modules:/src/node_modules --workdir /src --user node:node --entrypoint /bin/sh node:lts-alpine -c 'npm install && npm run ver'
npm ERR! code EACCES
npm ERR! syscall mkdir
npm ERR! path /src/node_modules/ansi-regex
npm ERR! errno -13
npm ERR! Error: EACCES: permission denied, mkdir '/src/node_modules/ansi-regex'
npm ERR!  [Error: EACCES: permission denied, mkdir '/src/node_modules/ansi-regex'] {
npm ERR!   errno: -13,
npm ERR!   code: 'EACCES',
npm ERR!   syscall: 'mkdir',
npm ERR!   path: '/src/node_modules/ansi-regex'
npm ERR! }
npm ERR!
npm ERR! The operation was rejected by your operating system.
npm ERR! It is likely you do not have the permissions to access this file as the current user
npm ERR!
npm ERR! If you believe this might be a permissions issue, please double-check the
npm ERR! permissions of the file and its containing directories, or try running
npm ERR! the command again as root/Administrator.

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/node/.npm/_logs/2022-06-09T07_10_27_665Z-debug-0.log

$ docker run --rm -it -v $PWD:/src -v node_modules:/src/node_modules --workdir /src --user root:root --entrypoint /bin/sh node:lts-alpine -c 'npm install && npm run ver'

added 41 packages, and audited 42 packages in 2s

3 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
npm notice
npm notice New minor version of npm available! 8.11.0 -> 8.12.1
npm notice Changelog: https://github.com/npm/cli/releases/tag/v8.12.1
npm notice Run npm install -g [email protected] to update!
npm notice

> ver
> npm --version

Steps to Reproduce

Use the package.json configuration above and repeat the commands in this post.

electriquo avatar Jun 07 '22 14:06 electriquo

Seeing the same issue in my Jenkins pipeline with node:16.15-alpine3.15. I cannot reproduce the issue locally though.

alaney avatar Jun 07 '22 20:06 alaney

we are facing the same issue.

on k8s we're setting the pod security context to run with an arbitrary user id, after deleting this in the config the pod starts again.

we are setting the tag / sha of images to node:16.14 for now, to keep our pod settings.

also see https://github.com/npm/cli/issues/4996

parkwart avatar Jun 08 '22 09:06 parkwart

Also seeing this issue, hard coding to node:16.15.0-alpine in the meantime.

reidja avatar Jun 08 '22 14:06 reidja

we are facing the same issue.

on k8s we're setting the pod security context to run with an arbitrary user id, after deleting this in the config the pod starts again.

we are setting the tag / sha of images to node:16.14 for now, to keep our pod settings.

also see npm/cli#4996

This is our circumstance as well. We use an arbitrary non-privileged user ID which worked fine until v16.15.1.

gcstarr avatar Jun 10 '22 19:06 gcstarr

Maybe @nschonni knows what's up

electriquo avatar Jun 14 '22 06:06 electriquo

Facing the same issue.

Was using node:16-slim which brought in version 16.15.1-slim. So reverted back to previous minor version as a temporary fix by specifying node:16.14-slim

neonidian avatar Jun 15 '22 10:06 neonidian

Facing the same issue.

Was using node:16-slim which brought in version 16.15.1-slim. So reverted back to previous minor version as a temporary fix by specifying node:16.14-slim

For us this was specifically with the 16.15.1 patch release, 16.15.0 works in our case.

cascornelissen avatar Jun 22 '22 18:06 cascornelissen

I'm having a similar issue. We're seeing it when we try to do an npm install from the Docker command, when we set the user for the container. If you set the node cache to a non-existent path, it seems to work.

Can replicate it using a simple package.json file with a dependency. This example assumes the package.json is owned by user 1001.

> docker run --rm -it -v $PWD:/app -u=1001 -w /app node:16.15.1-alpine sh -c 'npm i'
> echo $?
243

When setting --cache=nope, the install works:

> docker run --rm -it -v $PWD:/app -u=1001 -w /app node:16.15.1-alpine sh -c 'npm i --cache=nope'
... added some packages
> echo $?
0

EDIT:

It appears (at least in part) to do with the home directory for the user it's running as. Running as user 1001 (which doesn't exist in the container) fails to run npm at all:

> docker run --rm -it -u 1001 node:16.15.1-alpine sh
> npm -v

> echo $?
243

If you add the user with that ID, it creates the home directory and works:

> docker run --rm -it node:16.15.1-alpine sh
> adduser -D -u 1001 test
> su test
> npm -v
8.11.0
> echo $?
0

If you add the user with that ID, but without the home directory, you get the same error:

> docker run --rm -it node:16.15.1-alpine sh
> adduser -D -H -u 1001 test
> su test
> npm -v

> echo $?
243

scooper91 avatar Jun 27 '22 17:06 scooper91

It seems that npm exec and other command-invoking command such as npx executes commands as the owner of the current working directory, regardless of the user that these commands executed.

user:~$ npm exec -- /usr/bin/whoami
user
user:~$ sudo npm exec -- /usr/bin/whoami
user
user:~$ cd /
user:/$ npm exec -- /usr/bin/whoami
user
user:/$ sudo npm exec -- /usr/bin/whoami
root

Edit: here overrides uid if root

https://github.com/npm/cli/blob/9f94049f058687b916da726ea625b5fa68d0829d/node_modules/%40npmcli/promise-spawn/lib/index.js#L13

yuki-js avatar Jun 28 '22 09:06 yuki-js

Probably the same problem with docker image of 18.2-alpine, npm version 8.9.0.

{
  "name": "test-app",
  "version": "0.0.0",
  "scripts": {
    "test": "npm --version"
  },
  ...
}

The issue here seems npm doesnt want to run as root. Example when container is run as root and when switched to user node using su:

1aae0dcc8624:/workspace/app# whoami
root
1aae0dcc8624:/workspace/app# npm run test

> [email protected] test
> npm --version


1aae0dcc8624:/workspace/app# su node
1aae0dcc8624:/workspace/app$ whoami
node
1aae0dcc8624:/workspace/app$ npm run test

> [email protected] test
> npm --version

8.9.0
1aae0dcc8624:/workspace/app$

Running the container as node fixes the issue.

SmallhillCZ avatar Jun 29 '22 13:06 SmallhillCZ

@SmallhillCZ

Running the container as node fixes the issue.

Not fully correct. It depends whether you are using the container in conjunction with mount/data volumes as node_modules directory. When it is used, the user must be root in most cases (depends on the inner implementation of how mount volumes work across different operating systems)

electriquo avatar Jun 29 '22 14:06 electriquo

The issue exists in other images too and seems like the maintainers/contributors are not a part of the thread discussion.

Do we know what is the cause for the issue? Is anyone working on a fix?

electriquo avatar Jul 06 '22 05:07 electriquo

@foolioo NPM overrides uid if current user is root in the following line.

https://github.com/npm/cli/blob/9f94049f058687b916da726ea625b5fa68d0829d/node_modules/%40npmcli/promise-spawn/lib/index.js#L13

This was removed before, but reintroduced now.

npm/promise-spawn#6

yuki-js avatar Jul 10 '22 19:07 yuki-js

@yuki-js Thanks. Do you have any suggestion how to resolve the issue?

electriquo avatar Jul 10 '22 19:07 electriquo

@foolioo There seems no fundamental solution for now. Adjust uid of all files, or use yarn instead.

yuki-js avatar Jul 10 '22 19:07 yuki-js

For me, problems disappear when I add USER node command before invoking my npm run ... command inside my Dockerfile.

Example:

# Multi-stage dockerfile:
# https://docs.docker.com/develop/develop-images/multistage-build/

# Base stage
FROM node:16.15.1-slim AS base
EXPOSE 8080
RUN mkdir -p /app && chown -R node:node /app
WORKDIR /app
USER node

# Builder stage
FROM base AS builder
COPY --chown=node:node . .
RUN npm ci
RUN npm run build

# Prod stage
FROM base AS prod
COPY --from=builder --chown=node:node /app/dist ./dist
CMD ["node", "dist"]

jagu-sayan avatar Jul 10 '22 19:07 jagu-sayan

@yuki-js Have you tried adding a user to match the mounted file permissions, and using that to use npm? Obviously it isn't ideal, but it worked for our use case. (See my comment above). Can see how it works with our setup here: https://github.com/7digital/mysql2-timeout/blob/dbc820bcdd7a638e1f02bf983beb6bb52059d07f/docker-compose.yml#L12

scooper91 avatar Jul 10 '22 19:07 scooper91

@scooper91 I know, but unfortunately it doesn't fit my use case.

yuki-js avatar Jul 10 '22 19:07 yuki-js

The issue still persists with Ubuntu:20.04 image and Node 16.x LTS installed.

HaziFlorinMarian avatar Jul 13 '22 08:07 HaziFlorinMarian

In my case, I use a docker-compose.yml as is:

version: "3"
services:
  npm:
    build: ./docker/node/16
    entrypoint: npm
    user: node
    volumes:
      - .:/home/node
    working_dir: /home/node

And I had this "243" error (which a few months ago with the exact same docker-compose setup I hadn't).

First I tried to upgrade to node 18, but the error was the same. Then I tried to specify the group of the user from "node" to "node:node". Same error.

Then I remembered sometimes on my local there would be an error about writing on the ".npm" folder because of lack of permissions. So I figured I'd had the volume so the npm script would not have to write it on the host folder system (the one on Github must have reduced folder permissions for security purposes).

version: "3"
services:
  npm:
    build: ./docker/node/16
    entrypoint: npm
    user: node:node
    volumes:
      - .:/home/node
      - ./docker-data/npm:/.npm # <--
    working_dir: /home/node

Then the CI on Github started to be more eloquent, this time it would not manage to write a log file on the ".npm/_logs" folder. Getting close:

npm WARN logfile could not be created: Error: EACCES: permission denied, open '/home/node/.npm/_logs/2022-07-14T09_18_36_207Z-debug-0.log'

So I decided to also create the "_logs" folder on my "docker-data/npm" folder, so the npm script would not have to create the folder as well. Here is my folder structure:

my-app/
├── docker/
│   └── 18/
│       └── Dockerfile
└── docker-data/
    ├── npm/
    │   └── _logs/
    │       └── .gitignore
    └── .gitignore

And I also had to remove the "user: node:node" by the way (not sure if I put it back it will still work):

version: "3"
services:
  npm:
    build: ./docker/node/16
    entrypoint: npm
    volumes:
      - .:/home/node
      - ./docker-data/npm:/.npm
    working_dir: /home/node

With this setup, running this command will work without 243 errors

docker-compose run --rm npm outdated

As well as installing dependencies:

docker-compose run --rm npm ci

But compiling my assets will fail (this uses Laravel Mix behind the scene)

Run docker-compose -f app/docker-compose.yml run npm run prod
  docker-compose -f app/docker-compose.yml run npm run prod
  shell: /usr/bin/bash -e {0}
Creating app_npm_run ... 
Creating app_npm_run ... done

> prod
> npm run production

npm notice 
npm notice New minor version of npm available! 8.12.1 -> 8.14.0
npm notice Changelog: <https://github.com/npm/cli/releases/tag/v8.14.0>
npm notice Run `npm install -g [email protected]` to update!
npm notice 

Error: Process completed with exit code 243.

Edit

I managed to fix all permission issues by starting from Ubuntu:22.04 instead of Node:18-alpine on my Dockerfile

I recap for folks that want to do the same

docker-compose.yml

npm:
    build: ./docker/node/18
    entrypoint: npm
    volumes:
      - .:/home/node
    working_dir: /home/node

docker/node/18/Dockerfile

FROM ubuntu:22.04

RUN apt-get update && \
    apt-get upgrade --yes && \
    apt-get install --yes \ // Remove g++ and others dependencies if you don't need them, I needed them to compile some NPM deps
    g++ \
    make \
    curl \
    bash && \
    curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
    apt-get install --yes nodejs

Now all theses commands on my CI works without perm issues

docker-compose run npm ci
docker-compose run npm run prod
docker-compose run npm outdated

khalyomede avatar Jul 14 '22 09:07 khalyomede

I've added an entrypoint script to all project that suffer from this issue. It solved the permissions issues of a Docker mount volumes and NPM itself

$ cat docker-compose.yaml
version: '3.9'
services:
  node:
    container_name: node
    image: node:lts-alpine
    user: root:root
    entrypoint: /src/entrypoint.sh node
    volumes:
      - .:/src
      - node_modules:/src/node_modules
    working_dir: /src
    environment:
      - HOST_UID=${HOST_UID:-1000}

volumes:
  node_modules:
$ cat entrypoint.sh
#!/usr/bin/env sh

set -eu

apk add --update --no-cache --no-progress --quiet shadow
usermod -u 1000 node >/dev/null 2>&1
usermod -u "$HOST_UID" node >/dev/null 2>&1
chown node:node node_modules

CMD="$*"
su node -c "$CMD"

And have an environment variable named HOST_UID that holds the current user ID. For instance export HOST_UID=$(id -u)

electriquo avatar Jul 19 '22 07:07 electriquo

Hi! What's the status, is the problem fixed on latest node:16-alpine image?

KingKunn avatar Aug 23 '22 06:08 KingKunn

The simple solution is to change the nodejs version. I was facing the same issue, i fixed it by changing node version from 16 to 14 and this worked

neon010 avatar Sep 14 '22 12:09 neon010

Simply adding user: node to my docker-compose.yaml fixed it in my case...

DaleCam avatar Sep 16 '22 16:09 DaleCam

This is still happening on 16.17.1 docker images.

ViliusS avatar Oct 10 '22 10:10 ViliusS

Actually, this is not only an issue with run-scripts. node:16-alpine docker image is completely broken as a base build image in these cases:

  1. Environments where image is run by root user.
  2. Read-only root filesystem environments, e.g., Kubernetes container with readOnlyRootFilesystem: true.
  3. Environments where one could not modify a running user, e.g., on Jenkins pipelines which by default run under Jenkins user and the UID is most probably not the same as UID 1000 which is used in the image.

The source of these issues is that npm_config_prefix is set to /usr/local and npm_config_userconfig is not set at all, so NPM is trying to use HOME folder which in the docker image is set to /. Again, that folder is not writable by any of the users except root, but remember, NPM ignores root user due to https://github.com/npm/cli/blob/37bf6d31bd2f68e661928744833b57dcabbb0d1a/node_modules/@npmcli/promise-spawn/lib/index.js#L13

The only way I found to fix this on Jenkins is set NPM_CONFIG_CACHE to something like /tmp/jenkins/.npm. GitLab/GitHub runners can be fixed in similar manner.

I guess the final fix should be to create .npm cache folder somewhere writable by any user on the docker image, so at least it works by default on CI pipelines. Maybe create some kind of documented folder specified via npm_config_userconfig so anyone can map it and modify the behaviour of a running user.

P.S. Why NPM tries to spawn a process according to permission on current folder in case it is run by the root user is still a mistery to me. It goes against all commons. Programs should not modify running user.

ViliusS avatar Oct 10 '22 19:10 ViliusS

https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#non-root-user

nschonni avatar Oct 10 '22 19:10 nschonni

https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#non-root-user

Yes, I saw that, but this assumes that Docker image can be modified. In a lot of cases, you cannot do that. Free GitLab runners are one such example.

ViliusS avatar Oct 10 '22 19:10 ViliusS

I confirm, the issue is still exist in latest 16.17.1 docker images when we run them on Openshift/Kubernetes. We need to run latest node 16 version images because of security policy we have! Node 16.15.0 Docker image worked before for us just fine. Now something is changed/broken on later releases.

Seems issue is so old already on nowbody cares to fix it 👎

sasskinn12 avatar Oct 11 '22 08:10 sasskinn12

cares ro fix it

cares to fix it

hannesvdvreken avatar Oct 11 '22 08:10 hannesvdvreken