To build this custom image,
we need to go to the folder that contains our code.
And in there we need to create a new file.
A file named Dockerfile.
This is a special name which will be identified by Docker.
Now to get the best possible support
for writing such Docker files,
I recommend that in Visual Studio Code,
you go to the extensions area,
and there you search for Docker
and you install this Docker extension
which I already have here.
Make sure you install this Docker extension
because this will help you with writing Docker
called Docker instructions.
It's not a must have but it will make your life easier.
And thereafter you can go back to the explorer view.
So now we got this Dockerfile
and what do we put into this file now?
This file will contain the instructions for Docker
that we wanna execute when we build our own image.
So it contains the setup instructions for our own image.
And typically here you start with the FROM instruction.
All caps FROM.
This allows you to build your image
up on another base image.
And this is what you typically do.
Theoretically of course
you could build a Docker image from scratch,
but you always want some kind
of operating system layer in there,
one some kind of other tool, which your code needs.
So therefore here I wanna build up on the node image.
And I can do this with, from node,
just entering the image name of an image
which either exists on your system,
or under that name on Docker hub.
And this image now exists on Docker hub.
And actually since we already executed a container based
on this image at the moment it also exists
on our local machine because when we ran a container
based on this Docker hub image for the first time,
this image was downloaded and cached locally.
So now this is basically both a local
and a Docker hub image.
The most important thing is though
that it will be recognized.
That node is a name Docker will be able to find
that there will be an image named node.
So now we're telling Docker, hey, in my own image,
I wanna start by pulling in that node image,
and then I wanna continue.
So that's not the first step, as a next step,
we wanna tell Docker
which files that live here on our local machine
should go into the image.
And for Docker get to copy command.
And here a very simple instruction we could execute,
is copy.dot.
Now what does this mean?
You basically specify two paths here.
The first path is to path outside of the container,
outside of the image where the files leave
that should be copied into the image.
And if you just specify a dot here
that basically tells Docker that it's the same folder
that contains the Dockerfile.
Excluding the Dockerfile though.
So in this case dot would tell Docker
this first dot would tell Docker
that all the folders, sub folders
and files here in this project should be copied into DMH.
And now the second dot is the path inside of the image
where those files should be stored.
Every image and for also every container
created based on an image,
has its own internal file system
which is totally detached
from your file system on your machine.
It's hidden away inside of the Docker container.
And actually here it is a good idea to not use
the root folder, the root entry in your Docker container
but some sub folder which is totally up to you.
You can name it however you want.
And here I will name this slash app.
Now all the files here in the same folder as the Dockerfile
and all the sub folders there as well,
will be copied into an app folder inside of the container.
And this folder will simply be created
in the image and container if it doesn't exist yet.
So that's one key step.
Now, as a next step, we need to run NPM install, right?
Because that is what we had to do
outside of the container as well.
For node applications, we had to run NPM install
in order to install all the dependencies
of our node application.
And you also have an instruction for that,
which you can give to Docker.
You can tell it to after copying all the local files
into the image, you want to run a command in the image.
In this case, NPM install.
However, there is a gotcha here.
By default, all those commands
will be executed in the working directory
off your Docker container and image.
And by default, that working directory is the root folder
in that container file system.
Since I'm copying my code into the app folder here
I actually want to run NPM install
inside of the app folder as well.
And a convenient way of telling Docker
that all commands should be executed in that folder,
is that you set another instruction here
before you copy everything.
And that's the workdir instruction
for a setting the working directory of the Docker container
and setting this to slash app.
And this tells Docker that all the subsequent commands
will be executed from inside that folder.
Which makes sense because that is
where we will have our code later.
And now as a side node, given the fact that we now did set
the working directory to slash app,
we could also change copy to copy
everything from the path to Docker file is in,
to just dot or dot slash which basically means
due to current working directory
inside of our Docker container.
Since we changed that working directory to slash app
not just to run, but also copy will execute relative
to this working directory.
So now inside of the container internal file system
this relative path now points at slash app.
But we can also be more explicit here
and set this to the absolute slash app path like this.
And I'm a fan of doing that since this makes it very clear
where we're going to copy our files,
and we don't have to guess or look into that file
to see what the current working directory is.
Of course that is a simple file.
It's easy to see what the workdir is,
but if this would be a more complex file,
it could be harder.
And therefore I personally prefer
setting this to slash app here as well.
Now with that,
we have a lot of important setup instructions.
The last instruction is that when all of that is done,
we want to start our server.
For that we could add run node server
but this would actually be incorrect node server.js.
This would be incorrect because this would be executed
whenever this image is being built.
All these here are instructions to Docker
for setting up the image.
Now keep in mind the image should be the template
for the container.
The image is not what you run in the end,
you run a container based on an image.
And therefore with this command,
we would try to start the server in the image,
so in the template, but that's not what we want.
We want to install all the dependencies there, yes,
we want to have all the code in there, yes,
but we only wanna start a server
if we start a container based on an image.
Also so that if we start multiple containers
on one and the same image,
we also start multiple node servers.
So therefore here we have another instruction
and that's the CMD instruction, which stands for a command.
The difference to run is that this will now not be executed
when the image is created,
but when a container is started based on the image.
And that's what we want.
Then we want to run our node server.
However, for CMD, the syntax is a bit differently.
Here we wanna pass an array you could say,
and then we have two strings in there
where we split our command like this.
So that's how we now tell Docker
that whenever a container is created
based on that image, we use the node command,
which exists inside of that container
to run the server js file.
Now if we want to try to run it like this,
we would not be able to see our application though
for one key reason.
This node web server listens on port 80.
And I mentioned and emphasized multiple times already
that a Docker container is isolated.
It's isolated from our local environment.
And as a result, it also has its own internal network.
And when we listen to port 80
in the node application inside of our container,
the container does not expose that port
to our local machine.
So we won't be able to listen on the port
just because something's listening inside of a container.
Therefore in the Docker file after setting everything up,
before specifying the command,
which should always be the last instruction
in your Dockerfile,
we can add to the expose instruction to let Docker know
that when this container is started,
we wanna expose a certain port to our local system.
So to our machine here which will run this container.
And then we'll be able to run
the container (indistinct) we listen on this port.
Now with that, we finished our Dockerfile
with all the setup instructions for a Docker image.
Now let's see how we can utilize this custom image.
build it and how we can run it.