DEV Community

Cover image for Part III: Dockerizing Our Front- & Backend
Milan Wittpohl
Milan Wittpohl

Posted on • Originally published at milanwittpohl.com

Part III: Dockerizing Our Front- & Backend

In-Depth Tutorial: Building a Modern, Full-Stack Web App

In this series, I want to build a modern, extensible, yet simple set up that allows me to quickly build and deploy a web-frontend, -backend and database. While this project functions as a template for future projects, we still need some sort of goal. This is why we will create the simplest todo-app ever. Todo-apps are a good use case as it is simple but still covers most aspects of a modern application. We will have to:

  • connect a database to store todos
  • work with that database by reading, creating, updating and deleting entries
  • create a backend that exposes a REST-API for our frontend
  • secure our backend properly
  • build a frontend that works well with data from an API

There are several ways to build this modern web-application. I chose the following frameworks, each of which is covered in one tutorial:

Prerequisite

  • Good knowledge of object-oriented programming and java
  • Good knowledge of javascript
  • Basic knowledge of the terminal
  • A mac - While all of this should also work on windows I did not spend any time to check for or provide solutions for windows

Dockerizing Our Front- & Backend

At this point we have a front- and backend that runs perfectly on our local machine. While this tutorial will make more sense if you completed the previous parts, it can also be helpful in general. The goal of this part is that we want to prepare our web-apps for easy and modern deployment. We want to be able to quickly run our front- and backend on any machine and have the ability to scale the application if needed. As with everything else there are plenty of ways to accomplish it. For this series we will work with Docker as it has gained incredible popularity over the past years. This tutorial is split into four subparts:

  • What is Docker?
  • Dockerizing the frontend
  • Dockerizing the backend
  • Running it all at once

What is Docker?

There are millions of explanations on what docker is all over the internet. I want to touch the most important parts but I won't go into details here. My main points were taken from this video.

Lets say we built our backend as jar-file and tested it locally. Now we need to find a place in the cloud to run it. The first challenge that we encounter now is that we can not guarantee that our backend runs in the cloud just like it does locally. Only if the cloud environment is the same as our local environment, we could make such a promise. To get the gap between our local and the cloud environment as small as possible we need to tell our cloud-provider what we need. However, as a cloud provider you can not have a million developers tell you how their individual cloud setup should look like. That is why cloud-providers offer different packages. They vary between providing a (virtual) machine and providing us with a static environment to run our application in. In case of an individual machine it is now up to us, the developer, to make sure the machine behaves as our local machine. That costs too much time and is also expensive as we don't really need an entire machine. We just need a place to run our jar-file. In case of providing us with a static environment we would now have to make sure that our local environment behaves the same. This isn't useful either. Exactly here becomes docker useful.

Docker provides a common ground and is literally comparable with real-life-shipping-containers. A banana-company only worries about how to get their bananas into the container. Once it is closed it does't matter what is in there. It is basically treated like every other container and the shipping-companies know how to work with it. Docker provides a standard that is flexible enough but also makes sure the software runs in the cloud the same way it does locally.

We use docker to create a docker-image of our app. Just image we would burn it on a CD. That image is build using a dockerfile that defines how the docker-image should be built. That image can then be used inside a docker-container.

Docker is a powerful tool and provides more useful features (e.g. scaling). However this is not as relevant here.

Dockerizing the frontend

To dockerize the frontend please make sure that you have docker installed. Additionally we have to look at our base url in config.nuxt.js. You see, if we would deploy our app in the cloud as it is, it would always think our backend is reachable at localhost:8080. That is why we have to extract every environment specific variable.

Extracting environment specific variables

In our frontend we basically have one environment specific variable and that is the URL of our backend.

You may recall that we useed the proxy module in the nuxt.config.js file. All we have to do here is add the environment variables. If no value is present we set it to a default value (http://localhost:8080/)

    proxy: {
        '/api/': process.env.PROXY_API || 'http://localhost:8080/'
    },

Creating the dockerfile

Next we will create a dockerfile in our frontend folder called frontend.dockerfile. Our docker file contains the following code.

    # We don't want to start from scratch.
    # That is why we tell node here to use the current node image as base.
    FROM node:alpine3.11

    # Create an application directory
    RUN mkdir -p /app

    # The /app directory should act as the main application directory
    WORKDIR /app

    # Copy the app package and package-lock.json file
    COPY frontend/package*.json ./

    # Install node packages
    RUN npm install

    # Copy or project directory (locally) in the current directory of our docker image (/app)
    COPY frontend/ .

    # Build the app
    RUN npm run build

    # Expose $PORT on container.
    # We use a varibale here as the port is something that can differ on the environment.
    EXPOSE $PORT

    # Set host to localhost / the docker image
    ENV NUXT_HOST=0.0.0.0

    # Set app port
    ENV NUXT_PORT=$PORT

    # Set the base url
    ENV PROXY_API=$PROXY_API

    # Set the browser base url
    ENV PROXY_LOGIN=$PROXY_LOGIN

    # Start the app
    CMD [ "npm", "start" ]

Hopefully, the comments on each line explain what's going on. To build the image we simply execute this command on the terminal. Make sure to run it from your projects root directory!

    docker build --file=frontend/frontend.dockerfile  -t playground-web-frontend .
  • —file → The file to use for the build
  • -t → To identify our image we tag it
  • . → The location of the build context (the app). In our case the current directory, referenced as .

Dockerizing the backend

Similar to our frontend we have to extract every environment specific variable before we can dockerize or backend.

Extracting environment specific variables

We have two environment specific variables in our backend. The url of or frontend and the url of our database.

All environment specific variables are set in the application.properties file under resources. Each line contains the key and the value. For the value we will set it to an environment variable (that we will provide through docker) or a default value. Insert the following code.

    spring.data.mongodb.uri=${MONGODB_URI:mongodb://localhost:27017/todo}
    server.port=${PORT:8080}

You may wonder why we haven't set the URI for mogoDB before. That is because spring per default assumed to find the mongoDB under that URI. However, that will change once we deploy it. That is why we extract it. The server port will be used in the next part of the tutorial by heroku.

Creating the dockerfile

Next we will create a dockerfile in our backend folder called backend.dockerfile. Our docker file contains the following code.

    # We don't want to start from scratch.
    # That is why we tell node here to use the openjdk image with java 12 as base.
    FROM openjdk:13

    # Create an application directory
    RUN mkdir -p /app

    # The /app directory should act as the main application directory
    WORKDIR /app

    # Copy or project directory (locally) in the current directory of our docker image (/app)
    COPY backend/build/libs/*.jar ./app.jar

    # Expose $PORT on container.
    # We use a varibale here as the port is something that can differ on the environment.
    EXPOSE $PORT

    # Start the app
    CMD [ "java", "-jar", "./app.jar" ]

Hopefully, the comments on each line explain what's going on.

There is one big difference between the frontend- and the backend-dockerfile. The former contains code to build the application. If we make changes to the backend, we will need to build it first using this command.

    gradle build

To build the image we simply execute this command on the terminal. Again, make sure to run it from your projects root directory!

    docker build --file=backend/backend.dockerfile  -t playground-web-backend .
  • —file → The file to use for the build
  • -t → To identify our image we tag it
  • . → The location of the build context (the app). In our case the current directory, referenced as .

Running it all at once

Now that we have everything we need we will use docker-compose to fire everything up. The docker compose tells docker which services (with which images) to start and also sets the environment variables. Create a new file docker-compose.yml in your project's root folder.

    version: '3'
    services:
      playground-web-db:
        image: mongo:latest
        environment:
          MONGO_INITDB_DATABASE: playground-web
        ports:
          - 27017:27017
      playground-web-frontend:
        image: playground-web-frontend:latest
        environment:
          PORT: 3000
          PROXY_API: http://playground-web-backend:8080/
        ports:
          - 3000:3000
      playground-web-backend:
        image: playground-web-backend:latest
        environment:
          MONGODB_URI: mongodb://playground-web-db:27017/playground-web
        ports:
          - 8080:8080

To run the app execute

    docker-compose -f docker-compose.yml up

Your application should start and run successfully.

Congratulations for completing this tutorial!!!

As this is my first tutorial series, I would really appreciate feedback. You can find me on twitter, instagram or send me an email.

This tutorial was originally published on my personal website.

Top comments (0)