Full Stack for Front-End Engineers, v3 Creating a Docker Container
Transcript from the "Creating a Docker Container" Lesson
>> Let's make a Docker container. Let's containerize our application that we just wrote, and then we're gonna spend a multiple instances of it. That way, we'll have three or four servers running on our server. So we're gonna create a Docker container for our app. There's a link here with a complete Dockerfile, but we'll just do this right now.
[00:00:19] I won't expect you to know how to write a Dockerfile by yourself. And it's only a couple lines, and I'll talk about what every line is doing. So I'm gonna jump back to my computer. Actually, for this one, I'm gonna write this on the server. It's saving me a little bit of trouble.
[00:00:35] Because I'm gonna install Docker on my server, I don't really like Docker on my computer cuz it's a pretty heavy application. It takes up a lot of resources. But I'll run it on my server because that's where I need it. That's what's actually going to power my container.
[00:00:48] So, let's see, where am I right now? home/jem, that's not where I wanna be. I want to be in cd /var/www/app/. Okay, we're all here. So next, let's create a Dockerfile. So we just call vi Dockerfile, Okay? So, the first thing we need to do is say, what type of OS it's running on.
[00:01:28] So I said earlier, the OS doesn't really matter. Well, it does, cuz there's always an operating system somewhere in the layer. You can't just have an application that talks to nothing. But in this case, we're gonna use Node. Node has an official Docker image And it uses something called Alpine Linux, or Alpine, depending on where you are in the world, which is a really, really, really, really lightweight version of Linux.
[00:01:51] So it's superfast. No fluff. It only exists to run this one application. So we're gonna use the FROM directive there. Say FROM, and I wanna use node 19. So I'll say node:19-alpine.3.16. If you're saying, where did I get this exact node version and package name? Just look up Node Docker, and it will link to the official repository.
[00:02:17] They maintain this image for us. So, we don't have to do any of that work for us. It's already been built. Next, we have to set up Docker properly. So, Docker in this image by default is gonna run with a user called Node. It doesn't run as sudo or cells, it runs as its own user.
[00:02:37] So that means we have to set that up for us in the operating system. So we can say, RUN, oops, on the next line, RUN, mkdir. Remember what that does, it makes a directory, -p. That's the command to make a string of directories if the first one doesn't exist, so we make that at home/node.
[00:03:00] And Docker likes to run from the home directory. I like running from var dub dub dub. Personal preference, doesn't really matter too much. But in this case, we're gonna run our application within this operating system from the home directory of node. And we wanna make a directory for our node modules, so /home/node/app/node_modules.
[00:03:23] And we want to chown, change the ownership of the node modules to the node, user, node and node. And that wouldn't change the ownership entirely in the app directory node. There we go. So, now, we created a node directory, or we created an app directory within the node home repo, or home directory, and then we changed ownership to the node owner.
[00:03:55] Next, we need to set a working directory of where we're actually gonna do all the work with a Docker container is actually gonna load up the application. That's pretty easy cuz we just created it. So home/node/app.
>> You need a K in your make directory line.
>> Thank you.
[00:04:13] Again, these things that I'll get all the way to the end, and I'll see if it works, and then it won't work. And I'll be like, why? Good catch. Need a K in mkdir, not mdir. So we set our working directory. Next, and this is where it kind of messes with your head a little bit.
[00:04:29] Because we're making a Docker image from our existing application that we already wrote, we need to copy the files from our directory, right now, our actual computer, to the Docker container. So these are just sets of instructions for Docker when we build the Docker container, a little note to do.
[00:04:45] So we need to copy our package *.json. So, it's gonna copy package json package lock. And we're gonna copy everything else in that directory as well. Just being really explicit here. We are just gonna copy it to the Home app, should I say. So, no, we didn't copy node modules or anything else.
[00:05:15] We can run that with the npm script. It's gonna install the node modules specific for this operating system. Next, we're gonna set the user to node. And now, we can run npm install from the package that we copied over.
>> We said on line three that our working directory was the node application.
[00:05:40] But on line four we have the relative address from our www/app directory. [INAUDIBLE] Relative directory things is referenced off of where the Dockerfile gets run from.
>> Yeah, the Dockerfile is always gonna live in the application that you're trying to make, containerize, so it makes it easy. You don't have to do any weird till days and things like that.
[00:06:05] Just a little bit easier to reason about. Good question. So, next, we just we switch the user to node. We're gonna run npm install as part of the Docker build. Next, we're gonna copy everything over. I'm going to say chown node, change ownership to everything. So we're copying, the dot dot is everything in our current directory now.
[00:06:33] The reason why we did the package json first where we copy that over, and then we ran npm install is so all the packages can install first, and then we copy the rest of our applications over. You could probably do this in a little bit of different order, but this is usually the best way to do it.
[00:06:47] So, everything we copied over we also did a chown, so we change the ownership from our current operating system which is owned by me over to the node user as far as when we copy things over. And that's for feel you start to get a little tricky cuz sometimes you're within the Docker application, and sometimes you're without.
[00:07:05] And that's why I think it's the hardest part to wrap around our own Dockerfiles. But in this case, we are copying from our home directory. The last thing we need to do is, what's the point of writing a web application if we don't expose a port to talk to?
[00:07:20] So we're gonna expose port 3000, so our container can communicate through that. And the last thing we're gonna to do is we're going to execute the command, we're going to run node, and in this case, we're gonna run app.js. If you recall from the earlier exercises, app.js is our simple server.
[00:07:44] Yeah, we can bring up the WebSocket server, we can try copying that, but as we saw, there's a little bit of bumps there and that might take time to dive into. But you're welcome to run the index/ws.jsfile. You can cut Docker container out of that. But for now I'm gonna run my really simple server cuz I know for a fact, it'll run every time with no issues.
[00:08:06] Then we write quit, and that's our Dockerfile Everybody there so far? Yeah, okay, now it gets exciting. So do we have Docker installed? I don't know. Let's check. Let's run Docker. Not installed. Earlier I said you can use Snap or APT. I did this when I ran through this previous times, I used Snap.
[00:08:32] It got a little funky. So we're gonna use APT because we know how it works. So apt docker.io, oops, instil And let's see if docker is available. All right, so for those who missed it, we just apt installed Docker. So now let's try to build our Dockerfile. Again, let's hope nothing went wrong.
[00:09:31] I didn't make a typo somewhere in there, which has been a common theme. But let's find out. So we're gonna say Docker build. It's gonna look for our Dockerfile where it is. We're just gonna say -t to give it a name. We'll call it node full stack front end, just for now.
[00:09:49] We don't have to give it a name. It just makes it a little bit easier. So docker build dash t node full stack front end. Let's see if this works. I didn't tell it where to look. So let's try this again. Docker build -t, and the dots. The dot is saying look right where we are in our current place.
[00:10:10] So we don't necessarily have to do that, we could read through it from another directory, but this makes it easier. The dot's really easy to miss. Permission denied. Classic, classic. So, you shouldn't have to do this, but I'm gonna sudo it because that's going to make sure it works.
[00:10:27] Sudo bang bang. It's probably cuz when Docker was installed, it with sudo apt install. There's a way around that. There's a typo in there. Manifest for node alpine 3.6 not found. So let me go and open my Dockerfile again to make sure I didn't type it wrong. From node Alpine 6, I'm just gonna look it up just to double-check that I have the rights.
[00:10:57] So I'm gonna say node docker. Let's try to find the right image 19 alpine, 3.6. Yeah, that should be the right one. And let's double-check, see what I wrote. From node alpine 3.6. Yeah, that should be correct.
>> So this is one of the default docker environments?
>> Yeah, this is the official image for node.js.
[00:11:36] Let's check this out from alpine 3.6. Yeah, we don't need that. So if you wanna look and see what's happening, you know how we imported the image from Docker, or at least we're still trying to, this is the Dockerfile for that image where it runs through. It sets an environment.
[00:12:03] It sets the operating system, alpine 3.6. It creates that node user. Remember we said this Docker container creates a node user. It runs through all these commands we see here, it downloads the node.js binary from nodejs.org. It goes through all these things. So it's Dockerfiles on top of Dockerfiles on top of Dockerfiles.
[00:12:24] And that's awesome cuz we can compose things really well. And then that way in some future reference, if we wanna create a new application, we just import our Dockerfile into that one. node: is it a dash here?
>> Go back, get rid of the dash after alpine.
>> Just straight.
[00:12:49] Yep, it's a little things. It's a little things. I'm glad you all are here.
>> Do you need to chown those when you're copying them over?
>> I don't think so. But you might be right. Good call. Doesn't hurt node:node. Okay, let's try that one. Yeah, good call.
[00:13:41] Good call. So we forgot to change owner. And we're getting a permission error. We've got to change the owner to node again, because it copied over the packages, but it still had me as the owner. And I don't exist in this operating system, so that's why it failed.
[00:13:59] All right, so let's say docker image. Is that the one that'll Tell me all my docker is, I think? Docker image ls. Docker image ls. So I just ran sudo docker image ls. In a better world, we would not have to sudo. But cuz I installed it with sudo, after now we're on sudo, I think it's okay.
[00:14:38] So now, we can see that image we created here. And because we gave it a name, again, you don't have to, but it just makes it easier to look at. And it also created that node Alpine image that we pulled in. And the none, I'm not sure what this is because there's no name on it.
[00:14:55] So it could have just been like a bad run from earlier, I don't know. So, next, let's try to run this thing, and see if we made a mistake somewhere along the way. By we, I mean probably me at some point. So, we can run it with Docker What is the exact command?
[00:15:19] I might need to look this up again. Yes, docker run. And sudo, it's just same, say docker run, we're gonna say -d. -d will run in the background, that way it'll run, and then it'll close itself out. Otherwise, it'll stay open, then we have to open another terminal to do anything on that server.
[00:15:41] And then we have to do the -p, which is probably the most important one. So -p specifies the open ports. So we expose 3000, which is fine, but we need to tell it which port to expose on our server itself, our current server. So the format is outside port, our current server then the inside port, our containers ports.
[00:16:06] And we're just gonna say 3000:3000, pretty straightforward. And our image name, we can also use image ID. We'll just use image name, full stack front end. And there's already something running on port 3000, so we need to pm2 stop our old server, index-ws. There we go. And let's go ahead and try to run this Docker container again.
[00:16:37] All right, but let's see if it works. Yeah, and we're back to our simple server again. But pretty cool. So now we just containerized our application. Great, that seemed like a lot more work, Jem, to do the thing we already did. Okay, you're right. Let's try this. What if we create another Docker container or another docker instance, but we give it different ports?
[00:17:10] So now we can say docker ps, oops, always sudo. So now, we have two docker containers. One is running on port 3000, one is running on port 3001. I can create ten more as long as they're running on different ports, we now have ten different instances of our application on our one server.
[00:17:34] Okay, so what now? How do we tell nginx to route it to that? Well, first, let me talk a little bit about orchestration. How you generally do these things then we'll round off with how do we get nginx to actually connect to each of our Docker containers? So, it's connecting to our default one on 3000, how do we get it to balance out the connection to all of our other servers?