If you're developing a Node.js app in a Docker container, you may want to prevent the host machine from running npm install
for a number of reasons. For example: a Linux container won't be able to run packages that include bundled binaries if they were installed from MacOS or Windows. Node packages can also run arbitrary (and potentially malicious) code during installation, so you may want to isolate your host machine.
Even still, it's easy to accidentally run npm install
on the host.
Thankfully, NPM provides some ways to mitigate this. NPM has a preinstall
hook that runs before the npm install
command, and if it returns an error, it will prevent the install command from executing.
Using NPM's preinstall
hook
We can use NPM's preinstall
hook to prevent running npm install
on the host by checking for an environment variable that is only present in the container.
Take a look at the following package.json
file:
{
"scripts": {
"preinstall": "[ \"$IS_CONTAINER\" != 1 ] && echo \"Installing packages is not permitted outside of a container!\" 1>&2 && exit 1; exit 0"
},
"dependencies": {
"express": "^4.17.1"
}
}
If the environment variable IS_CONTAINER
doesn't exist or isn't set to 1
, NPM will echo an error message, then exit with an error code (exit 1
means
the program exited with an error). If IS_CONTAINER
is set to 1
, the script will exit with 0
, which means it was successful and the dependencies will install without issue.
Now we need to make sure that our Docker container has the IS_CONTAINER
environment variable.
Setting an environment variable in the container
We can set a IS_CONTAINER
environment variable using one of the following three methods:
1. We can set this variable in the docker-compose.yml
file.
services:
node:
image: node:lts-alpine
working_dir: /srv/app
command: node index.js
volumes:
- .:/srv/app
environment:
IS_CONTAINER: 1
ports:
- "3000:3000"
2. We can set this variable in a Dockerfile (if you're using one).
FROM node:lts-alpine
ENV IS_CONTAINER=1
WORKDIR /usr/src/app
COPY . .
RUN npm install
CMD ["node", "index.js"]
3. We can set it as an environment variable from the command line.
docker-compose run -e IS_CONTAINER=1 --rm node
With the combination of adding the preinstall
script in your package.json
file and setting the IS_CONTAINER
environment variable using one of the methods above, your Node packages will install in your container without issue when you run npm install
. Conversely: if you run npm install
on your host, you'll get an error that says Installing packages is not permitted outside of a container!
Unfortunately, this method will not prevent you from installing individual packages outside of your container: it will only prevent npm install
from installing the packages inside package.json
. For example, if you ran npm install lodash
in a console on your host machine, it would install lodash without an error. There is an NPM RFC that would change this behavior, but as of time of writing this post (November 2021), it hasn't gone through.
Prevent NPM scripts from running outside of a container
You can also prevent custom NPM scripts from running outside of a container by using NPM's pre
scripts. If you had an NPM script called dev
(example: npm run dev
), you could create a predev
script that would run before dev
.
Prepending any script name with pre
will cause NPM to run that script first. If you had a build
script, you could have a prebuild
. If you had a start
script, you could have a prestart
.
We can use these pre
scripts to look for an IS_CONTAINER
environment variable the way that we did in the preinstall
script.
{
"scripts": {
"preinstall": "[ \"$IS_CONTAINER\" != 1 ] && echo \"Installing packages is not permitted outside of a container!\" 1>&2 && exit 1; exit 0",
"predev": "[ \"$IS_CONTAINER\" != 1 ] && echo \"Running the development script is not permitted outside of a container!\" 1>&2 && exit 1; exit 0",
"dev": "nodemon index.js"
},
"dependencies": {
"express": "^4.17.1"
}
}
You can learn more about pre
and post
scripts in the official NPM docs.
Please let me know if this post helped you, and let me know in the comments if you know a better way of doing this!
Top comments (0)