Express & MariaDB With Docker Compose
By: Al Javier
Tags: MySQL, Tutorial, Docker Compose, Nodejs, Docker

The Dockerfile

In simple terms, the Dockerfile is what allows us to create our image. It uses a lot of the same commands from your terminal as you’ll see in a bit. Create a new file called, well, Dockerfile in your project root directory and open it up.

We have a bunch of stuff going on here so let’s go break it down one at a time. These commands are known as Layers and run from top to bottom.

FROM

This is what will specify the image that you want to use to speed things up in development. In this example, we’re pulling the official Node.js image that’s version 12.20.0. The two basic parts are first the name of the image, then the next one, after the colon, is the version of that image. If the version or tag is not specified, it will default to latest . You may check out the Docker Hub for more information regarding official images.

WORKDIR

The working directory is located inside the container, so in this instance it’s in the root of the container. If you use ~/app then that will be the container’s home directory.

COPY

Copy is kind of self-explanatory. The first parameter is your host or app directory, the second is the destination in your container’s directory.

The reason why we want to grab package.json first and install dependencies is because of docker build 's ability to cache steps that it has already succeeded in before. This will greatly increase the speed of building your image when you make changes to your application.

RUN

This allows you to run commands as you would in the terminal, which in this example is used to install dependencies with npm install .

CMD

There can be only a single CMD layer in a Dockerfile, as it’s the default for container execution. Basically put it at the last of the Dockerfile, below all the other layers. While there are exceptions, a simple app like ours will make do with this.

Building The Dockerfile

In order to have our image ready, we have to build it and thankfully that’s pretty easy.

$ docker build -t alphonso/docker-express-api .

The -t flag denotes your tag, or what’s essentially the name of this new image. You’ll normally come across the standard of naming it after one’s host machine username, or a nickname, or whatever workspace you and your team have. I just gave this one my name then the name of the image.

You’ll notice the dot at the end, that specifies the source of our Dockerfile, which is at the root of the project folder.

Once the build is successful, we’ll spin up a database and our application together using docker-compose to finish our stack. But before that, don’t forget to check out your new image using the following command:

$ docker image ls

You should see a list of image names, including yours.

Docker Compose

In this section we’ll combine both our app’s image and the docker-mariadb container into a single application stack. We’ll also define their ports, networks, and mounted volumes to preserve data.

Create a new file called docker-compose.yml at your project’s root.

Once you have this in place, let’s go ahead and discuss the important bits of this file.

Version is based on the Docker Compose version API, the documentation has a full list of what version run which commands. We’re using version 3 in this one.

Services are the complete list of applications that you will run with Docker Compose. Their names are entirely up to you and what makes the most sense to you and your team. I called them node and mariadb to make things clear and easy to understand.

Each Service needs an Image — the one built from the Dockerfile or the one pulled from Docker Hub. The Container Name is what this service will be called as a running container as seen with docker ps .

Ports are just like your standard application ports, you first define the container’s application port, then its equivalent at the host. Without this, you won’t be able to connect to your application as a container.

The depends_on key is what defines service dependency across your multi-container application. It makes docker-compose up start your services in specific order, in this case the mariadb service starts first before node .

Then you have Environment, which is the environment variables to use for that service. You may also use a .env file via an env_file: key.

Networks

We’re going to have to look at this a little bit more in-depth compared to the others, as they’re a lot more self-explanatory. Both networks and volumes require more detail to grasp the basics.

You first define a network name, in this case it’s docker-service . Next you have a driver , which uses the default one called bridge — which is commonly used on a single host.

Then you’ll define which Network to use on your Services, by using the networks key, followed by its name.

Volumes

Much like Networks, you’ll define a name for your Volume, then specify a driver to use. The local driver is the default. Why is a volume required for our app? It’s to preserve data in your database container. There are internal and external volumes available to use, in this example it’s internal, as set by default.

To use your created volume, define it under the service such as a database, and then its name followed by the directory (after the colon).

Running The Containers

That was a lot, but we’re not done yet. We’ll have to run our containers now using the command:

$ docker compose up

Once everything goes well, you should end up with both services outputting logs

Testing The Desserts Route

Alright, we should be about done at this point. All you have to do now is perform a GET request at the /desserts route.

And there you have it! A fully functioning multi-container application with Docker Compose.

Conclusion

Okay, that was a lot to take in, but hopefully you learned something new. If you made any mistakes during the Docker Compose sections, or one of your containers crashed, it’s okay to delete that container and re-run docker compose . No need to delete both of them, Docker will automatically attach existing containers to your services.

As always if you can suggest any improvements, please do so in the comments section. I’ll appreciate any feedback.

Also if you’re feeling particularly generous today, why not grab me a cup of coffee?

This would help me a lot and would allow me to keep making more easy-to-follow guides and tutorials for everyone! 😄️

The Github repository for this project is right here.

Anyway, thanks for reading, see you on the next one.