A More Complicated Node.js App
Okay, all looking good so far. Let's make this app go one step further. Let's have it have an npm install step! In the directory where your app is, put this:
// more-or-less the example code from the hapi-pino repo
const hapi = require("@hapi/hapi");
async function start() {
const server = hapi.server({
host: "0.0.0.0",
port: process.env.PORT || 3000
});
server.route({
method: "GET",
path: "/",
handler() {
return { success: true };
}
});
await server.register({
plugin: require("hapi-pino"),
options: {
prettyPrint: true
}
});
await server.start();
return server;
}
start().catch(err => {
console.log(err);
process.exit(1);
});
This is a [hapi.js][hapi] server. Hapi is a server-side framework (like Express) for Node.js and my personal favorite. This is going to require that we npm install
the dependencies. So in your project do the following
npm init -y # this will create a package.json for you without asking any questions
npm install @hapi/hapi hapi-pino
Now try running node index.js
to run the Node.js server. You should see it running and logging out info whenever you hit an endpoint. Cool, so now that we have a full featured Node.js app, let's containerize it.
If we tried to build it and run it right now it'd fail because we didn't npm install
the dependencies. So now right after the COPY
we'll add a RUN
.
FROM node:12-stretch
USER node
WORKDIR /home/node/code
COPY . .
RUN npm ci
CMD ["node", "index.js"]
We changed the COPY
to copy everything in the directory. Right now you probably a node_modules
but if you're building a container directly from a repo it won't copy the node_modules
so we have to operate under the assumption that those won't be there. Feel free even to delete them if you want.
Let's go ahead and add a .dockerignore
file to the root of the project that prevents Docker from copying the node_modules
. This has the same format as a .gitignore
.
node_modules/
.git/
We then added a RUN
instruction to run a command inside of the container. If you're not familiar with npm ci
it's very similar to npm install
with a few key differences: it'll follow the package-lock.json
exactly (where npm install
will ignore it and update it if newer patch versions of your dependencies are available) and it'll automatically delete node_modules
if it exists. npm ci
is made for situations like this.
Now if you try to build again, it'll fail with permissions issues. Why? Well, when you have WORKDIR
create a directory, it does so as root which means that the node user won't have enough permissions to modify that directory. We could either use RUN
to change the user or we could use RUN
to make the directory in the first place as node. Let's do the latter.
FROM node:12-stretch
USER node
RUN mkdir /home/node/code
WORKDIR /home/node/code
COPY . .
RUN npm ci
CMD ["node", "index.js"]
Now try building and running your container. It should work now! Yay!
NOTE: make sure you don't bind your app to host localhost
(like if you put localhost
instead of 0.0.0.0
in the host in our hapi app.) This will make it so the app is only available inside the container. If you see connection reset
instead of when you're expecting a response, this a good candidate for what's happening (because this definitely didn't just happen to me 😂.)