Making Our Own Alpine Node.js Container
Let's take this exercise a bit further. Let's actually make our own Node.js Alpine container. NOTE: I'd suggest always using the official one. They'll keep it up to date with security fixes and they're real good at making containers. Better than I am, anyway. But this is a good exercise for us to go through to learn how to install system dependencies.
Start with this in a new Dockerfile. You can call it
my-node.Dockerfile. Some people will insist it should be
Dockerfile.my-node but the former doesn't break syntax highlighting and it doesn't matter since Docker doesn't actually care.
FROM alpine:3.10 RUN apk add --update nodejs npm
alpine:latest would nab you the latest Alpine (3.10 as of writing, if you run into issues with versions, continue with
alpine:3.10 instead of
alpine:latest. Otherwise feel free to truck on with
RUN apk add --update nodejs npm will use the Alpine package manager to grab Node.js and npm (they're bundled separately for Alpine.)
If you encounter error like this
/home/node/code/node_modules/@hapi/hapi/lib/core.js:51 actives = new WeakMap(); // Active requests being processed ^ SyntaxError: Unexpected token =
nodejs-current instead of
RUN apk add --update nodejs-current npm
Okay so now if you do
docker build -t my-node -f my-node.Dockerfile . it'll build your new image.
--file which is what tells Docker is the name of your Dockerfile you're using (otherwise it assumes you're using a file called exactly
Dockerfile.) Now try
docker run -it my-node. In here you should have a pretty bare bones Linux container but both
node -v and
npm -v should work.
Keep in mind that Alpine does not use bash for its shell; it uses a different shell called
ash or often just
sh. It's similar enough to bash but there are some differences. It's not really the point of this class so we'll keep the focus on learning just what's necessary.
Let's next make our
FROM alpine:3.10 RUN apk add --update nodejs npm RUN addgroup -S node && adduser -S node -G node USER node
I'm mimicking what the Node.js official container does, which is make a user group of
node with one user in it,
node. Feel free to name them different things if you feel so inclined. Notice we could conceivably combine the two
RUN instructions together but it's generally best practices to keep "ideas" separate. The first
RUN installs dependencies, the second one creates the
node user. Up to you how you do it, neither is wrong per se.
Now we can just copy the rest from the previous Dockerfile! Let's do that.
FROM alpine:3.10 RUN apk add --update nodejs npm RUN addgroup -S node && adduser -S node -G node USER node RUN mkdir /home/node/code WORKDIR /home/node/code COPY package-lock.json package.json ./ RUN npm ci COPY . . CMD ["node", "index.js"]
It works! We're down to 56MB (compared to 86MB with the official
node:12-alpine container). Honestly, I'm not entirely sure what we cut out from the other
node:12-alpine container but it's probably important. Again, I'd stick to the official containers where they exist. But hey, we learned how to add a user and install system dependencies! Let's make it even small because why the hell not.