Check out a free preview of the full Complete Intro to Containers (feat. Docker) course

The "Build a Node.js App" Lesson is part of the full, Complete Intro to Containers (feat. Docker) course featured in this preview video. Here's what you'd learn in this lesson:

Brian demonstrates how to build a Node.js application within a container, and writes a Dockerfile for a Node app.


Transcript from the "Build a Node.js App" Lesson

>> And I'm going to come in here and I'm gonna make a new directory. I'm gonna call this one build a nodejs-app. And change directories into there. I don't know why I'm doing this all from the command line. You could totally do it from here, from the File Explorer too.

So the first thing I want you to make a new file in here, I want you to call it index.js. And I want you to just come over to the course web page here. We're gonna go into the Build a Node JS App. And just copy and paste this.

Like I wrote just like the most like Hello World server ever. Go away ESLint, no one cares. This is not a Node JS class, I don't expect you to understand literally anything on the board there. This is more about packaging Node JS applications than it is about Node.

But suffice to say, it imports the HTTP core module in Node, creates a server that it'll console log every time it receives a request. And then they'll respond to the user with this age old wisdom of, my god, hi. And then it starts the server on port 3000.

There's lots of great Node JS content on Frontend Masters. So definitely check out that stuff if you wanna learn more about it. All right, so if you wanna just see, like if I say node index.js, you can see there's a server started. And if I go over here and I say local host 3000, you can see up there very tinily it says OMG hi.

You'll see that as it said receive request twice. That's because when a browser opens a webpage it also requests the favicon, that's why it's there twice. I know someone was gonna wonder about that, so it's the favicon. Okay, so Ctrl+C to stop that. Lets make a Docker file that containerizes this application and then runs the server for us, right?

So what I wanna do now is I'm gonna create another file in the same directory. And I'm gonna call it Dockerfile. I wanna say as you might imagine, FROM node:12-stretch. I'm gonna say copy. And we're gonna copy index.js to index.js, right? So copy it from source to destination, right?

And then I'm gonna say command node index.js. There you go. Okay, and now I need to build my docker file, right? So I'm gonna say docker build. Give it a tag, so -t instead of --tag if you're lazy like me. And I'm just gonna stick with my-node-app. This will overwrite whatever was there before, but I think we're okay to leave the console logs behind, right?

I don't know, some of you look pretty sad. So, docker build -t my-node-app and then we'll put period to let it know there we're in this directory here. And you can see there, from node, built that, COPY here, CMD here, good. Now you might be wondering, why is it putting these hashes in here?

That's because every one of these is actually technically a valid container at every single level, right? So I actually could just execute this one before I got here. And that's the way that Docker does its caching is like, okay, this is a valid container up to here. And then it just starts from here if you only change things in here, right?

So if I don't change index.js, it would just restart from that point again, right? So that's why those numbers are there. We'll get more into best practices of docker files, but for now just know that that's why those are there. [SOUND] So now I can say docker run my-node-app.

There you go, server started, right? So our node app is now running inside of a container. Problem though, if I come over here, it says unable to connect. Well, why is that? Well, we didn't share the network with it, right? So remember name spaces, right, that thing that we talked about a little while ago?

It's intentionally limited off from the outside world. We did not give explicit permission to this container to talk to the host network so it's all contained inside of it. And it's trying to reach out but the host operating system is like, no, you don't get to talk in that port.

So let's do that. Let's make it work on that. Here's another fun thing that I forgot to tell you about. Try hitting Ctrl+C. This is what I was telling before. Node does not respond to these sort of processes. So what's happening is I'm saying Ctrl+C to docker, docker is then passing that along to node, and node's like I don't care, right?

So there's a couple of things you can do here. The best thing to do is you should go inside of your node application. Again, this is not a node course so I'm not going to show you how to do it, but you would say, process.on, SIGTERM, right, and then you would go through and do all that kind of stuff.

Not gonna do it right now. But that's the correct way to do this for a node developer. So the first thing I'm gonna do is, let's actually stop this because it has to stop. So I'm gonna just say docker ps. And you can see here my-node-app right there.

pedantic_goldstine, please docker kill the pedantic_goldstine. So now if I go back over here, you'll see that it's gone, right? So the next thing I'm gonna show you, if you run this with docker run -- init, this runs up with it module called tini, T-I-N-I, which is init backwards, right?

And so it kind of proxies that process and then it will shut down for you and it will automatically shut down node for you. So if I do this again with that and I hit Ctrl+C, tini will actually go in and kill the node process for me. So it's kind of like a little hack that I don't have to go and handle those sig term events.

It'll just do it for me. So you'll start seeing me run that --init and probably --rm now, just for pure laziness factor, that I don't wanna go do that myself. You could run this in production. If you really are gonna run it in production with tini with a --init, I would suggest going into your Docker file, installing tini and then executing Node from tini so that it's just built into the Docker container.

Otherwise you'll forget, you'll run some production server and you can't shut it down. Kind of a problem, right? So all right, so now I wanna expose that port, right? I wanna punch a hole through that container so that I can expose that port 3000 to the outside world.

So what I'm gonna do is I'm gonna say --publish. And I wanna take port 3000, I wanna expose it on port 3000, right? I could do some routing and expose this on port 8000 right, but I am not going to. So now if I run this server started port 3000.

And now you can see here it says again, omg, right? And it says request received, so on so forth. And the way that we did that is by the publish 3000:3000.
>> So you don't put in a dockerfile you just put it into the c line?
>> So I think literally the next section is about, well, so there's a thing called expose and so you can put a command inside of docker file to do it.

But I would suggest doing it through publish cuz it's something more you wanna determine at runtime and expose just a necessary work that most people expect it to. So, let's just go with no, but good question.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now