vscode-dev-containers icon indicating copy to clipboard operation
vscode-dev-containers copied to clipboard

Publish Node images with no global packages

Open aaronadamsCA opened this issue 5 years ago • 4 comments

I'd like to see published Node images that don't install any global packages (currently eslint, typescript, tslint). This could mean revising the existing javascript-node and typescript-node definitions, or maybe just creating a new core node definition that includes everything from javascript-node except the last step.

Global packages in general have fallen out of favour, and most docs now strongly encourage local package installation. I think it would be beneficial to lead by example and encourage dev container maintainers to do so as well.

It's much easier and more natural to manage local packages than global ones. For example, to add an ESLint plugin to a dev container:

  1. Global: Maintainer modifies Dockerfile (and hopefully includes a version tag!), everybody rebuilds the container
  2. Local: Maintainer just runs npm i -D <plugin>, everybody else just runs npm i

It also reduces compatibility issues, since package.json versions packages by default (as opposed to the non-versioned eslint and typescript packages currently defined in base.Dockerfile), and package-lock.json generally ensures everyone ends up with the same dependency tree (and as of NPM 7 guarantees it).

I wanted to file this request to gauge 👍/👎 reaction.

aaronadamsCA avatar Oct 27 '20 12:10 aaronadamsCA

Yeah, I've been debating this one myself - on the one hand, having these there allows you to just get started with a blank project and get going. On the other hand, as you said, globals are generally not favored once you have a project created. So its mainly sandbox scenarios that would benefit from what exists now.

Generally tslint is on life support, we left it there during the transition period (along with the extension), but we could probably remove at this point.

I guess the question is, if you have package.json and either yarn.lock or package-lock.json already, does having these globally cause any problems?

Chuxel avatar Oct 27 '20 22:10 Chuxel

I guess the question is, if you have package.json and either yarn.lock or package-lock.json already, does having these globally cause any problems?

Well, as just one example, the main reason I noticed the global ESLint package was the VSCode plugin picking it up by default. Thankfully it at least asked me if I wanted to use it; accidentally running eslint instead of npx eslint wouldn't be as noticeable a mistake.

So its mainly sandbox scenarios that would benefit from what exists now.

That's why I figured a new node definition with no global packages might be the best path forward; leave the existing images for said sandbox scenarios, but quickly rebuild them atop a shared core image with no global packages, and slowly revise docs to recommend using that core image in .devcontainer/Dockerfile.

For now I've just added RUN sudo -u node npm uninstall -g eslint && npm cache clean --force > /dev/null 2>&1 to my Dockerfile, since that's a lot easier than copying javascript-node/.devcontainer/base.Dockerfile and then manually keeping up with changes as this (awesome) project continues to evolve.

aaronadamsCA avatar Oct 28 '20 11:10 aaronadamsCA

Hey @Chuxel, I've been doing a bit more thinking here, and I just wanted to share some additional food for thought.

A couple more of my own goals you might like to consider, since they may be shared by many others:

  • I'd like to be able to align the Node.js point release between our development container and our production build environment. It seems NVM + .nvmrc is probably the best way to accomplish this.
  • I often find myself working in metered-bandwidth situations, so I've been thinking about how to reduce the bandwidth used during container builds.

I looked to see what's inside the node image, and I was surprised at how simple it is:

https://github.com/nodejs/docker-node/blob/702fcf0f5cb33b1772e80bb4ae406fd6f6d237a3/16/buster/Dockerfile

This just creates a user, installs Node.js, and installs Yarn; your project can already do all 3 itself with ease.

So, would anything of value be lost by cutting out the node image altogether, and simply rolling your own Node.js container image based on NVM instead?

Pseudocode

base.Dockerfile

FROM buildpack-deps:buster

# Install needed packages, yarn, nvm and setup non-root user. Use a separate RUN statement to add your own dependencies.
# [...]
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
    # Install common packages, non-root user, update yarn and install nvm
    # [...]
    # Configure global npm install location, use group to adapt to UID/GID changes
    # [...]
    # Clean up

Dockerfile

FROM mcr.microsoft.com/vscode/devcontainers/node

# Install node using nvm
ARG NODE_VERSION=node
RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION}"

# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
#     && apt-get -y install --no-install-recommends <your-package-list-here>

# [Optional] Uncomment if you want to install more global node modules
# RUN su node -c "npm install -g <your-package-list-here>"

I think this could potentially reduce image size and install time, while also improving the ability to control the Node.js version, and adding the ability to upgrade the Node.js version without rebuilding the container (as long as you configure this with .nvmrc instead of in Dockerfile).

I might pursue a solution locally based on your Debian or Ubuntu container; but an image like this seems like it would be worth including out of the box.

aaronadamsCA avatar Jul 13 '21 12:07 aaronadamsCA

As OP I'd say this is now taken care of with features.

Our old image was 1.06 GB:

FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0.203.4-16-bullseye

# Uninstall global ESLint package
RUN su node -c "npm uninstall -g eslint" \
    && npm cache clean --force > /dev/null 2>&1

RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
    # Install additional OS packages
    && apt-get -y install --no-install-recommends dnsutils \
    # Clean up to reduce image size
    && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/*

Our new image is a comparably svelte 564 MB:

FROM mcr.microsoft.com/vscode/devcontainers/base:0.202.3-focal

RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
    # Configure system with nvm, Yarn, and node-gyp
    && bash -c "$(curl -fsSL "https://raw.githubusercontent.com/microsoft/vscode-dev-containers/main/script-library/node-debian.sh")" -- "" "none" \
    # Install additional OS packages
    && apt-get -y install --no-install-recommends dnsutils \
    # Clean up to reduce image size
    && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/*

I still think an official image would be nice to ship; but you've given us the tools to create our own, which is the next-best thing.

aaronadamsCA avatar Apr 08 '22 07:04 aaronadamsCA