All Articles

Dockerizing React and Kotlin Applications

Docker
Easily package lightweight apps with Docker

Containerization brings many advantages. They allow you to quickly kill applications and spin up new instances, independently of the hardware they are running on. Containerization is absolutely crucial in DevOps pipelines, as the apps can be run, integration tested against, and shut down very quickly.

Docker is losing some ground against its competitors, but it is still king among containerization tool.

In this article, I will go over how to dockerize both our frontend and backend application.

Docker

What is Docker?

Docker is an open source containerization platform. It enables developers to package applications into containers, along with the OS and dependencies required to run that code - in any environment. It’s irrelevant whether your server is running on Windows, Linux or Mac, your dockerized application will run on whatever it’s configured to run on.

This freedom of environment enables teams of developers to work on their own OS of choice, let’s say Windows, start the application in pipelines and run it afterwards on Linux servers, for example. The dependencies that are shipped along with the deployable Docker image define everything that’s required, and will create their own little environment.

Docker is so popular that “Docker” and “Containers” are used interchangeably.

Docker lingo

Docker has some specific terms that are quite important to know when talking to other developers. Here are the main ones:

  • Dockerfile: A simple text file that defines the steps required to create a docker image
  • Docker image: Executable source code, along with libraries and dependencies the code needs to run in the container
  • Docker containers: The running instances of the Docker images
  • Docker daemon: The service running on your machine that creates, manages and watches the images
  • Docker Hub: A public repository of images, where you can share or simply store the images you created$

Docker orchestration

Once a container is live, it is technically running in its own little bubble, unbeknownst to the rest of the world. However, usually you need to be able to communicate with the container. If you have an application landscape or several containers, you can ensure that these containers can talk to one another freely, but other applications would have only one entry point.

The main docker orchestration tools are the following, but I will not go into detail, as each of these warrants their own series of blog posts:

  • Docker Compose: Not a real orchestration, but nonetheless very useful to manage containers, if all containers reside on the same host
  • Docker Swarm: The Docker orchestration tool, where containers can be deployed across multiple nodes
  • Kubernetes: The technology developed by Google to elegantly handle container lifecycles

Now that some theory is out of the way, let’s get started and containerize our first application!

Dockerizing React App

While you can build any Docker image from scratch, you usually do not want to do that. Just like in most parts of software development, you can really rely on the community to help you out. That very community has created a lot of base images, essentially small packages of dependencies and libraries or operating systems, upon which you only need to add your source code, some properties, and voila - you have your application containerized!

Any Docker image starts out with a Dockerfile. So let’s create such a file in our React Application right in the root of the project:

# Stage 1
FROM node:alpine as dev
WORKDIR /app
COPY package.json ./
COPY package-lock.json ./
COPY ./ ./
RUN npm i
CMD ["npm", "run", "start"]

What this Dockerfile does is take the node image as the base image. Inside this image, it creates the working directory as /app. When the image is then run, it will create this directory into Docker, and copy the package.json, the package-lock.json and everything from the root folder into that working directory, where everything is installed. Finally, we define that the command for the container is npm run start.

Now, to create this image, we run docker build -t frontend . (yes, with the .). This will take some time, especially the first time it’s run. In order to speed it up, we can add a .dockerignore to the repository also:

node_modules
build
.dockerignore
Dockerfile
Dockerfile.prod

Still, don’t expect it to be very fast…

Now, once the image is successfully created, we need to verify that it works. To spin up the docker image, simply run docker run -p 3020:3000 -it frontend. This will open port 3000 (the one the React app runs on) to the outside world on port 3020 (random number I picked). If we now navigate to this page, we will see that it is indeed working.

Frontend running in docker
The frontend is now being served by docker

In order to retrieve more information about all docker containers that are currently running, you can go to another terminal and type docker ps. You will then see something along the lines of what you see below:

Docker container information
All currently running docker containers

However, one very important thing in frontend development is hot reloading. If you make any changes in the codebase, you will see that those are not automatically applied. This would make the development very tedious…

Fortunately, there is an easy way of fixing this issue, by telling the app which volume (directory) to watch. If you add -v ${PWD}:/app to the docker run command, making it docker run -p 3020:3000 -v ${PWD}:/app -it frontend, your docker will know to check for changes in the volume, and serve the newest code base at all times.

Note: On Windows, you may need to add -e CHOKIDAR_USEPOLLING=true also, depending on the version of your OS.

Dockerizing Kotlin App

Let’s move on to the backend application. This will be a bit simpler, as JVM based applications only need a jar file to run.

The procedure is the same though, we start with the Dockerfile:

FROM openjdk:11-jre
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]

As the base image, we take a Java 11 image. We define where the jar file is situated, and then copy that into the image under the name app.jar. Next, we tell the container that, inside the container, port 8080 is exposed, and the entrypoint command is java -jar /app.jar. This is it already.

So let’s try it out.

  • mvn clean install -DskipTests
  • docker build -t backend .
  • docker run -p 8081:8080 -it backend

To verify that everything is working correctly, navigate to http://localhost:8081/actuator/health, and you will see that your app is up and reachable!

In the next article, we will see how to make all these containers work together using a docker-compose file, add the DB to it so that it becomes reachable by the backend, but not by the outside world. This way, we’ll have a full-on application landscape ready to deploy anywhere already!