Okay, so we're ready to start actually creating additional microservices, in this case, the authentication
microservice.
However, before we go ahead and do that, I want to actually start Dockerizing our existing reservations
application.
And the reason for this is because when we have to run multiple applications at once, I want to take
advantage of Docker compose to be able to run multiple images at once.
So let's go ahead and get started in Dockerizing our application.
So this is a pretty straightforward process.
Of course you need to make sure you have Docker installed on your machine.
You of course need to make sure you have Docker installed on your machine, which of course I'll have
a separate video going over how to set up and install.
Assuming you have Docker already set up, I'm going to go into the reservations folder and in the root
folder I'm going to create a new file here called a Docker file.
Docker file is responsible for the instruct Asians on how to actually build the application.
You can think of it almost as like a template.
So we're going to provide the template for how to build the application and actually create the container
that we want to run.
So in Docker we can actually extend pre-existing images using the from keyword.
So this is going to be the base image we want to extend from.
So I want to use the Node Alpine image, which is a base alpine image that already has node inside of
it and which of course we're going to need to run our application.
So I'm going to label this base image development because we're going to break up the stages of this
image into two different stages.
The first will be development.
Next, I'm going to set the working directory for our container to be in the user source app directory.
Next, we're going to use the copy command here to copy our package.json into the working directory.
So after we specify the working directory here, you can see here all the subsequent commands that we
run will be run in this directory.
So this directory here is our working directory.
So we copy the package.json.
Let's also copy the npm loc yaml and then I'm going to run a command here to actually install NPM.
So we'll use NPM to install pmpm globally and then we can actually run npm install here.
So that will install all of our dependencies.
After installing all the dependencies we need to build the application, we're going to actually copy
over our whole app into the root of our Docker container and then we can finally run here, NPM run
build, which of course we know will execute the nest build command here to actually build our app with
all of our dependencies.
So after we've done that, we're going to move on to the next stage of our Docker image.
So to do that we'll do the same from command here to extend the Node Alpine image and we'll label this
the production image.
And importantly, we're going to set an argument here where the node env is equal to production and
the ARG keyword here is going to allow us to specify this as a variable that we can override at build
time if we want to with a default value.
So by default this will be production here.
And then what we're going to do is expose this as an actual environment variable.
So we'll do node N is equal to the node variable we just created right here.
So if node env is production here, we're going to make sure that we build this in a production environment,
which is important to get some runtime optimizations in Node, we'll have to again specify our working
directory as the user source app folder and then we will, as we've done before, copy our Package.json
and the NPM lock.
We'll then actually install NPM again.
And then importantly here we will run NPM install with the.
Prod flag and the prod flag makes sure we only install dependencies specified here and not the development
dependencies, which is going to make this production image much lighter.
Then we're going to run this copy command where we specify the stage we want to copy from.
In this case development.
I want to copy from our development stage up here, specify that I want to copy in that user source
app dist folder.
I want to copy that to our dist folder.
So in this first stage here, when we run NPM build, it's going to build our app to the dist directory
here using the development dependencies.
And then we want to take that app and put it into our production stage which doesn't have the development
dependencies and plop it in the dist folder.
So then we can finally run our command here to actually execute the running container.
So in this case we know we're going to execute Node and specify the path to our application.
In this case we know it's the dist folder because we just copied in the dist folder.
We're going to go inside of the apps reservations and then look for the main file and we can see this
outside of a Docker container.
If we want to run NPM run, build right now to actually just build our reservations app.
We should see in the dist folder here this same path, right?
Dist apps, reservations main.js.
And this is our compiled main.js.
So importantly, when we copy over everything here in this stage of the Docker image, we don't want
to copy over our node modules.
So in order to accomplish this we'll create a docker ignore in the root of our folder here and specify
we don't want to copy over the node modules folder.
So now we're ready to actually test out this Docker image, go back inside of our terminal.
So in my terminal here, I'm going to CD into the apps folder and then CD into the reservations folder
where we actually have the Docker file.
So to actually build this, we're going to run Docker build and then I'm going to specify the actual
context of where I want this build to run from.
And if you recall, I want to actually run it from the root of our project so that we can copy over
important files like the Libs directory, the TS config and so on.
So we can just specify the context path right here where we want to execute it from and then we'll specify
the actual Docker file with the dash F, which in this case is in the current directory.
Finally, we'll specify a tag and I'll call this sleeper underscore reservations.
So we'll go ahead and let this Docker image build.
So after our Docker image has finished building, we can actually test it out by running Docker Run
and then provide the name of the tag.
We just created Sleeper Reservations, so this will run the Docker container.
And now we can see here when our app starts up, of course we're failing because the MongoDB URI is
not specified as an environment variable.
However, we can see the app actually starting up, which is great.
So now that we have this Docker image actually building, how do we actually run it and specify environment
variables?
Well, we can use Docker compose for that.
So Docker compose is just another CLI tool that will allow us to run our Docker containers in a nice,
well known format and allow us to specify environment variables and other configuration.
So let's see how this works.
We'll specify a docker-compose.yaml in the root of our project here, and then we're going to start
off by creating a new services key here.
And this is where we specify the containers we want to run.
So of course, the first one we want to run here is our reservations container that we just created.
Now this is going to have a build key here.
Okay.
Now inside of here, we're going to specify a bit of information.
Firstly, the context, which if you remember from when we ran on the CLI is essentially where we want
to run the Docker build from.
So we'll specify the context here.
And in this case, well we're already in the root of the project, so we'll just keep the context at
the root.
Then I'll specify the path to the Docker file.
And of course, you know, this will be in the apps folder in the reservations folder and then just
the Docker file there.
Now importantly, we can specify the target on the Docker file that we actually want to run.
So what do I mean by this?
Well, if we look at the Docker file, remember we have two different stages here.
We have the development stage and the production stage.
And so I want to run the image from the development stage here so that we have all of the development
dependencies we need when we are actually developing the app.
So to do this, all we have to do is specify the.
Target here, which is the target stage.
In our case, it will be development.
Next, we can specify a command here which will be the command to actually run the container.
So in our case, instead of running the default command of running this dist main file, I want to actually
override that and I want to have that hot reloading we're used to.
So we're going to run P.
NPM run start dev and then we're going to specify the name of the application because eventually we're
going to have multiple apps and we need to specify the name of the app.
So in this case it will be reservations.
And so now we will get that hot reloading.
Now also make sure that this command here is not in the build context.
So this is actually going to be outside of the build.
Now, we're also going to go ahead and specify a port section here just to specify that we know we're
going to be exposing Port 3000 and that's going to map to 3000 on our host machine.
And then importantly, we need to specify a volume section here and we need to specify that we want
to create a new volume that mounts the entire project in our local machine to the user source app directory
inside of the container so that when we make changes to our application here, it gets reflected and
overridden inside of the running container.
We can test this out by running Docker compose up, which will build and create our container as well
as attach to the running container here.
So you should see the app start up here, which is great.
And we can actually test to make sure that it will still hot reload if we go into our main.ts for reservations
and just put a console log statement in here, just write test.
We should see that the application restarts and we see our test message here.
So if we wait a few seconds, we can obviously see that we're having trouble connecting to the database
now because the Docker container isn't going to be able to access MongoDB, which is running locally
on our machine.
Instead, what we need to do is create a new Docker image that runs Mongo and then we can connect our
running app to that mongo image.
So let's go back here.
I've gotten rid of that console dot log statement and firstly, let's also just update our Docker ignore
here to also make sure we ignore our disk directory so we're not copying over any pre-built dist folder
here.
And we now need to go to our Docker compose and add a new service and this will be the Mongo service.
So we're going to have a new Mongo service here and for the image we can just use the official mongo
image that's hosted on Docker Hub.
That's all we need.
Now if we go back to our terminal and if we want, we can also tear down our containers by running Docker
compose down.
However, we don't really need to do that.
We can just run Docker, compose up again and now we can see that we're actually pulling this Mongo
image.
And so now after we start up, we should see a new mongo container with its logs alongside with our
reservations container, which is great.
However, we're still pointing at our old MongoDB URI in our dot m file.
So let's go ahead and change that.
Instead of pointing to our local host here, what we need to do is actually change the host name here
out for the name of the service.
So of course we specify this name here, Mongo.
And so networking in Docker, these containers can talk to each other by the name of the service.
In this case it's going to be Mongo.
So we just need to talk to Mongo here still at Port 27017 and the same database.
So now if we restart our containers and bring them back up, we should see that the reservations application
fully starts up and we can see the nest application has successfully started, which is great to see.
And of course we can also see our mongo logs alongside with it specifying that a new client was connected.
So we have our application fully running alongside with a MongoDB container in Docker, which is great.
We are fully ready to start creating additional microservices and allow them to be running alongside.