milanwittpohl.com

I am Milan Wittpohl, deeply passionate about digital products, currently living in Edinburgh for my master studies.

Why did I publish this tutorial?

Programming fascinates me and I love to learn new frameworks, languages, patterns and so on. However, I tend to fool myself that I have understood something without fully understanding it. I guess I'm just too impatient. But, if I write about what I have learned, my knowledge gaps are revealed and force me to understand it. This article is about something I learned.

I am still learning

Please keep in mind that I am constantly learning. I would not consider myself an expert in any of the topics covered here. I'm simply trying my best. If you find any mistakes (content- or language-related) please contact me via twitter, instagram, or send me an email, thanks.

milanwittpohl.com

I am Milan Wittpohl, deeply passionate about digital products, currently living in Edinburgh for my master studies.

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:4.2.2 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 .



The complete code after completing all parts can be found here.