So onwards to the backend.
I'll comment it back in this backend service
and now we need to configure this.
Now, let's start with the image.
On which image should this backend be based?
Keep in mind that the backend is our Node API.
Well, we have a Dockerfile in there
and we used that in the last section
to build a goals-node image.
So we could specify image: goals-node here.
Now, we have a problem though.
I removed all images
and therefore, this image doesn't exist anymore.
Of course, we can rebuild it
and then we could use it.
But Docker Compose also replaces this build step.
Instead of specifying a finished image,
you can also give Docker Compose all
the information it needs
to build an image.
And you do this below your container
to which the image belongs
with the build option.
And the build option now wants to know
where it finds the Dockerfile that should be built.
In its simplest form,
you can just use a relative path here
to, for example, say it's in the same folder
as the docker-compose.yaml file
or to say it's in the backend folder in this case.
And then, Docker Compose would look into that folder,
it would look for a file named Dockerfile
and it would build the image for this backend service,
this backend container with this Dockerfile.
And then once this is built,
it uses that image for this container.
Now here, actually this configuration is all we need
but I also want to show you the longer form.
The longer form is that you have an object here
that you have nested keys below build.
So indented below build
and you then can specify a context,
which is the path to the folder
that holds your Dockerfile.
And then also, a dockerfile key,
which specifies the file name.
Now, if your file is named Dockerfile,
there is no need to use this longer form
but if you named it differently,
for example, Dockerfile-dev,
then this is how you could tell Docker Compose,
which Dockerfile to use.
And also another note about the context.
It can be the path to your Dockerfile,
like this example here,
but it will also be the place
where the Dockerfile is built thereafter,
so where the image will be generated.
And that means that your context should be set
to a folder, which includes everything
the Dockerfile might be referring to.
So if in your Dockerfile, you are copying some folder
into the image, the context must be set to a folder
that includes that to be copied folder.
In this case, this would be the case.
The Dockerfile in the backend folder only refers
to other folders, which are included in the backend folder.
So setting the context to backend would be okay.
But if your Dockerfile would be in some other nested folder
and needs access to folders outside
of that nested folder,
then your context should be set to a higher level folder.
And in case that's super confusing
because it's just some theory right now,
in a later course section,
when we set up a Laravel application,
a Laravel project environment,
there you will see an example
where we need to set the context
to something else than the Dockerfile path.
Now, here I'll leave that here but comment it out.
I will use the shorter form
because my Dockerfile is named Dockerfile
and therefore, just pointing at the path
is all I need to do here.
In this longer form, just as an additional note,
you can add args here
and then below that, you can add your args
like some-arg with some value.
So in case your Dockerfile uses ARGS,
you might remember them from earlier in the course.
This is how you could specify them
when using Docker Compose.
But that's just a side note.
Here, we just need to point at the folder
and Docker Compose will be able to build this Dockerfile.
Okay, so this is the first step.
This will produce the image we need for the backend
but of course, it's not all.
It just replaces this step
where we build the image manually.
Now, for running goals-backend,
we also needed environment variables,
we needed three volumes,
removal and detach mode, a network and ports.
Well, let's go through that step by step.
Let's maybe start with the ports.
You can also specify published ports
in your docker-compose.yaml file.
So here, next to build, on the same level as build,
indented below backend,
I will add ports.
And the ports is very straightforward
and does just what the name implies.
It allows you to specify published ports.
You specify them as a list.
Therefore we need a dash here.
Where you simply add a string
with single or double quotes
where you then have your host port,
for example, 3000,
and your container internal port, 80, for example.
Now, in this case here,
the internal port, the Node API uses is 80
and I also wanna expose this on port 80 on the host machine
so I will use 80:80 here.
Now, if your application would use
and expose multiple ports,
you could also specify multiple ports here
but here we just need one.
Now, that's the ports.
Additionally, we have a network.
Now, I already explained
that Docker Compose creates a default network
and adds all containers to that network.
So therefore, there's nothing we need to do here.
It will just work, it will just be part
of a network automatically.
Now for -d and --rm,
I showed you that you automatically remove everything
if you stop your services with Docker Compose down
and that you start them in detach mode
by adding -d after docker-compose up.
So we don't need to add anything
to our configuration for that.
Now, what about the volumes?
We've got one named volume
and then a bind mount and an anonymous volume.
Well, we learned how to add volumes.
So therefore here, next to ports,
not nested but next to it,
on the same level as ports,
we add volumes.
And just as for MongoDB, we can add a volume
with such a dash here.
And then, for example, add the logs volume,
which points at app/logs.
So basically, as we did it here
when we used docker run.
Now, since it is a named volume,
we should also add it here to our top level volumes key
at the end of this file.
Now, that's not the only volume we need here though,
we also have a bind mount.
Now, with docker run,
we needed this absolute path.
With docker-compose, that actually gets way easier.
Here, we are allowed to use a relative path,
relative from the docker-compose.yaml file.
So in my case here,
I want to share the whole backend folder.
That's what I also did here.
I shared the whole backend folder
and hence here, I type ./backend
and bind that internally to /app
inside of the container.
That's the same we did here with docker run
but there we had to use the app's new path,
which was way longer.
Now we use a relative path,
relative scene from docker-compose.yaml.
And therefore, we just go into the backend folder
and share everything that's in the backend folder
with our app folder inside of the container.
Now, bind mounts don't need to be specified here
and can't be specified here.
So therefore, we don't need to add any entry
to volumes for that.
Now, we've got one last volume
and that's this anonymous volume.
We can just copy that
and add it like this to our volumes list like bind mounts,
anonymous volumes also can't and therefore don't need
to be added to this top-level volumes key.
So now we added these three volumes
to our backend container here.
And the only remaining thing now
are the two environment variables
and we learned how that works
and I will again use a file here.
I'll name it backend.env.
And in this file,
I wanna set up my MONGODB_USERNAME environment variable
and assign a value of max.
And my MONGODB_PASSWORD environment variable
and assign a value of secret.
And with that backend.env file stored
in the env folder,
we can attach it to this container.
Or we can use it from inside this container
by adding the env_file key here
and then simply pointing,
like we did it up there for MongoDB
to ./env/backend.env.
And now the environment variable's specified like this
in this .env file will be made available to this container
and the application running in the container.
And that should be all.
There is one extra key I also wanna add here though.
And that's the depends_on option.
This is an option, which we only have in Docker Compose,
not with docker run
because when we use docker run
and we run individual commands,
it makes no sense.
But with Docker Compose,
where we create and launch multiple services,
so multiple containers at the same time,
sometimes, one container might depend on another container
to be up and running already.
So for example here,
my backend actually depends on mongodb
being up and running already
because my backend container wants
to connect to MongoDB.
My Node API wants to connect to MongoDB
and therefore, I want express a dependency here
to let compose know
that it should first bring up mongodb
and only once that is up and running,
it should create this backend container.
Depends_on then takes such a list
with dashes and in there,
it then simply specify the name
of the service this service depends on.
So in this case, my backend service depends on mongodb.
So the name I chose here,
and that's the name we now use down there.
And that's all you need to do.
And of course, one service is also able
to depend on multiple services
in case you had this requirement.
Here we only have one dependency though.
And with that, I'm quite happy
with my backend container setup.
So I would say let's again try this.
Let's bring up the terminal
and let's again run docker-compose up -d.
And now this creates the networks,
pulls down the Node image
because it builds this backend image
because it doesn't exist yet.
So therefore, it downloads the Node image
and then uses the Dockerfile in the backend folder
to create the image for this backend container
and once all of that is done,
once it downloaded this, once it built this,
it will bring up this container,
this backend container
and of course, also the MongoDB container based
on the configuration we specified in docker-compose.yaml.
So let's quickly wait for this to finish.
Here we go.
It now downloaded everything.
Now it's running npm install in the backend Dockerfile
or for the backend image I should say.
And it's done.
Now with docker ps,
we can see, we've got two containers up and running
and we got one container named docker-complete.
So our project folder, _backend_1,
which is the container named as users here.
And then one container for mongodb.
Now, let's use that container name
for the backend, which was generated
and let's look into the logs there.
And we see it also connected to mongodb.
And this is really something I wanna emphasize here.
When we list all the containers,
we see that the name defined for this container
is actually this name,
this docker-complete_backend_1 name.
So it's not just backend or anything like that.
And for mongodb, it's not just mongodb
but instead this longer name.
Nonetheless, the names you specify here under services,
so mongodb and backend,
these names will also be memorized, you could say,
by Docker and you can use these names
inside your code to establish network connections.
That's why in app.js,
where I connect to mongodb as a name,
this still works even though the name assigned
by Docker Compose is a different one.
I can still connect to mongodb by that name
in my app.js file
because I used mongodb as a service name here.
So these service names are the names you can use
in your code to send requests
and to leverage the network Docker Compose creates for you.
That's just an important note I have here
and something I want you to be aware of.
Now, with that, we got these two services up and running,
the backend and the database.
Now, we, of course, also want to start the front end.
So let's again bring everything down
with docker-compose down
and let's then continue working
on our front end service here.