56
votes

I'm following a tutorial about Jenkins pipeline and I can get a "hello world" working under at node 6.10 docker container.

But, when I added a default EmberJS app (using ember init) to the repo and attempt to build that in the pipeline, it fails when running npm install (because of directory access issues). The Jenkinsfile can be seen here: https://github.com/CloudTrap/pipeline-tutorial/blob/fix-build/Jenkinsfile

The error message printed by the build is (which is installed locally and run using java -jar jenkins.war on a Macbook, not relevant but included just in case) is:

npm ERR! Linux 4.9.12-moby
npm ERR! argv "/usr/local/bin/node" "/usr/local/bin/npm" "install"
npm ERR! node v6.10.0
npm ERR! npm  v3.10.10
npm ERR! path /.npm
npm ERR! code EACCES
npm ERR! errno -13
npm ERR! syscall mkdir

npm ERR! Error: EACCES: permission denied, mkdir '/.npm'
npm ERR!     at Error (native)
npm ERR!  { Error: EACCES: permission denied, mkdir '/.npm'
npm ERR!     at Error (native)
npm ERR!   errno: -13,
npm ERR!   code: 'EACCES',
npm ERR!   syscall: 'mkdir',
npm ERR!   path: '/.npm',
npm ERR!   parent: 'pipeline-tutorial' }
npm ERR! 
npm ERR! Please try running this command again as root/Administrator.

Note: I would like to not run npm install as root / sudo.

UPDATE: I have been able to make some progress as follows:

I found the command that Jenkins uses to build using the container from the logs:

[Pipeline] withDockerContainer
$ docker run -t -d -u 501:20 -w /long-workspace-directory -v /long-workspace-directory:/long-workspace-directory:rw -v /long-workspace-directory@tmp:/long-workspace-directory@tmp:rw -e

So when the docker image runs, it's work directory is a /long-workspace-directory (it's really a cryptic looking jenkins workspace path) and the user id is 501 (group id 20), etc. The user doesn't have a name (which is apparently breaking other things not related to this question).

  1. Changed agent to use a Dockefile:

    agent {
      dockerfile {
        filename 'Dockerfile'
        args '-v /.cache/ -v /.bower/  -v /.config/configstore/'
      }
    }
    
  2. Specify args '-v ...' for creating volumes for the directories npm install / bower needs.

13
npm ERR! Please try running this command again as root/Administrator. try this hint.Ember Freak
ordinarily, you do not run npm install as root; it's considered really poor form; i'd like to avoid that.les2
Please take a look at nvmEbrahim Pasbani
I'm not installing any global dependencies. i use nvm extensively at work and for our existing ci setup.les2
i think I'm using the image incorrectly. the node image is for running​a node server application, not running a series of build commands.les2

13 Answers

69
votes

Adding the environments and setting the Home to '.' solves this as below.

pipeline {
    agent { docker { image 'node:8.12.0' } }
    environment {
        HOME = '.'
    }
    stages {
        stage('Clone') {
            steps {
                git branch: 'master',
                    credentialsId: '121231k3jkj2kjkjk',
                    url: 'https://myserver.com/my-repo.git'
            }
        }
        stage('Build') {
            steps {
                sh "npm install"
            }
        }
    }
}
35
votes

from https://github.com/jenkins-infra/jenkins.io/blob/master/Jenkinsfile

docker.image('openjdk:8').inside {
    /* One Weird Trick(tm) to allow git(1) to clone inside of a
    * container
    */
    withEnv([
        /* Override the npm cache directory to avoid: EACCES: permission denied, mkdir '/.npm' */
        'npm_config_cache=npm-cache',
        /* set home to our current directory because other bower
        * nonsense breaks with HOME=/, e.g.:
        * EACCES: permission denied, mkdir '/.config'
        */
        'HOME=.',
    ]) {
            // your code
    }
}
16
votes

Having wasted a whole day on this issue, I found simply adding the following as an environment variable at the agent stage using the Pipeline Editor removed the problem.

'npm_config_cache=npm-cache'
8
votes

Wanted to just provide a bit more detail, in short the accepted answer works but that I'm new to Docker and wanted to get a better understanding and figured i'd share what i found.

So for our jenkins setup, it starts containers via

docker run -t -d -u 995:315 -w /folder/forProject -v /folder/forProject:/folder/forProject:rw,z ...

As a result this container is running as user uid=995 gid=315 groups=315

Since the image I was using (circleci/node:latest) doesn’t have a user with this UID/GID, the user will not have a “home” folder and will only have permission on the mounted volume.

When NPM commands are called it will try using that users home directory (for cache) and since that user wasn’t created on the image, the home directory gets set to / (default for linux?). So to get NPM to work correctly we simply point the containers HOME environment variable for the user to the current folder via the Jenkins file

pipeline {
  agent none
  stages {
    stage('NPM Installs') {
      agent {
        docker {
            image 'circleci/node:latest'
        }
      }
      environment { HOME="." }
      ...
    }
  }
}

Thus giving the user the ability to create the required .npm folder in /folder/forProject/.npm

Hopefully this is helpful to someone and if you see something that i got wrong please let me know :D

7
votes

When Jenkins runs the stage in Docker agent it usually sets HOME=/ (image WORKDIR value), but Jenkins user does not have write permissions in this directory, that's why npm cannot create its cache directory ~/.npm. To solve this you need either to override HOME or NPM default cache directory:

environment {
    // Override HOME to WORKSPACE
    HOME = "${WORKSPACE}"
    // or override default cache directory (~/.npm)
    NPM_CONFIG_CACHE = "${WORKSPACE}/.npm"
}
2
votes

I add the same issue. I solved it using the root user to run the Docker image:

node {
    stage("Prepare environment") {
        checkout scm
        // Build the Docker image from the Dockerfile located at the root of the project
        docker.build("${JOB_NAME}")
    }

    stage("Install dependencies") {
        // Run the container as `root` user
        // Note: you can run any official Docker image here
        withDockerContainer(args: "-u root", image: "${JOB_NAME}") {
            sh "npm install"
        }
    }
}
2
votes

You can override the user that Jenkins runs the docker container with, for example here I override with the root (userid:groupid is 0:0):

docker { 
    image 'node:8'
    args '-u 0:0'
}

You can spot the current user in the docker run parameters in the console output.

1
votes

We had the same issue, the core of the problem for us was, that the user in the Container and the User running the Jenkins node had different UIDs. After changing the UID+GID of the user in the container (and changing ownership of the users home-directory) to match the user running the build node npm would behave normal.

This might also happen if the Home-Directory of the container-user is not writeable.

Code in the Dockerfile:

RUN usermod -u <uid of buildnode> <container user> && \
    groupmod -g <gid of buildnode> <container user group> && \
    chown -R <container user>:<container user group> /home/<container user>

As the Workspace is mounted into the container it will already belong to the UID. When running the container through the Jenkinsfile the UID and GID of the container user are set automatically to match the buildnode. But the home directory will still have its original owner.

Now the node_modules will be placed in the current directory.

1
votes

In my case the problem was that inside the container I was user jenkins instead of root. I got there by setting whoami inside the container and got error like cannot determine user 111 (which happens to be jenkins). So I did the following:

stage('Run build') {
        webappImage.inside("-u root") {
            sh "yarn run build"
        }
    }
1
votes

this configuration work for me.

pipeline {
    agent {
        docker {
            image 'node:6-alpine'
            args '-p 3000:3000 -p 5000:5000'
            args '-u 0:0'

        }
    }
    environment {
        CI = 'true'
    }
    stages {
        stage('Build') {
            steps {
                sh 'npm install --unsafe-perm'
            }
        }
        stage('Test') {
            steps {
                sh './jenkins/scripts/test.sh'
            }
        }
        stage('Deliver for development') {
            when {
                branch 'development' 
            }
            steps {
                sh './jenkins/scripts/deliver-for-development.sh'
                input message: 'Finished using the web site? (Click "Proceed" to continue)'
                sh './jenkins/scripts/kill.sh'
            }
        }
        stage('Deploy for production') {
            when {
                branch 'production'  
            }
            steps {
                sh './jenkins/scripts/deploy-for-production.sh'
                input message: 'Finished using the web site? (Click "Proceed" to continue)'
                sh './jenkins/scripts/kill.sh'
            }
        }
    }
}
1
votes

I was using maven to build the project and which indeed was running the frontend-maven-plugin which is responsible for calling the npm install So npm install was breaking with error: path /.npm npm ERR! code EACCES npm ERR! errno -13 npm ERR! syscall mkdir

I changed it to npm install --cache /tmp/empty-cache

and it worked

0
votes

You can install nvm on the fly before building, in a local directory with NVM_DIR without setting it as global dependency :

mkdir -p node_dir
export NVM_DIR=$(pwd)/node_dir
curl https://raw.githubusercontent.com/creationix/nvm/v0.33.1/install.sh | bash
source $(pwd)/node_dir/nvm.sh
nvm install 7
nvm use 7

The new locations are :

$ which node
~/someDir/node_dir/versions/node/v7.7.2/bin/node

$ which npm
~/someDir/node_dir/versions/node/v7.7.2/bin/npm
0
votes

In my case this solved the problem

            agent {
                docker {
                    image 'node:10-stretch'
                    args '-v /home/jenkins/.ssh:/home/jenkins/.ssh:ro -u 0'
                }
            }