Skip to content

Dockerízate, part 3

Share on twitter
Share on linkedin
Share on email
Share on whatsapp
Dockerízate - Part 3

If you have followed the previous posts we are about to finish this. In this third part we'll see how to generate an image of our software, put it in a container and distribute it. We have a virtualized environment with Vagrant, Docker and Docker-Compose installed and a MongoDB container working as a Service.

1. What about my containers?

We have finished the first version of our web application. The time has come to move into the certification environment and we don't want to have to copy configuration files between machines, send the war via FTP or generate tedious installation manuals for the client. We are ready to create our first container.

We remind you that the containers are based on software images. Therefore, the first step is to generate an image of our web application. In the example we have a Java application developed with SpringBoot and an embedded Tomcat server, which allows our application to be executable with a "java -jar miwebapp.war". SpringBoot will raise the tomcat automatically.

Let's suppose that our web application consists of two external configuration files (logback.xml and environment.properties) and the War with the code and the embedded Tomcat (WebApp.war). Then, we go to the /workspace/shared folder of our vagrant, create a new /workspace/shared/webapp folder (by organization) and copy it to that folder (we use a new folder because all the files must be at the same hierarchical level).

To generate an image, we have to create a new Dockerfile in the root of the folder. If we list the files we would have, at the same level:

  • logback.xml
  • environment.properties
  • WebApp.war
  • Dockerfile

The Dockerfile indicates that it will contain the image. For our web application to run we need Java installed, the War and the configuration files. Once we have everything, we would have the following configuration:

# FROM - Specifies the base image that the Dockerfile will use to build a new image.
FROM openjdk:8-jdk-alpine

# MAINTAINER - Specifies the Dockerfile Author Name and his/her email.
MAINTAINER  Pedro Delgado <pdelgado@futurespace.es>

# RUN - Runs any UNIX command to build the image.
COPY WebApp.war /lib/
COPY environment.properties /var/conf/
COPY logback.xml /var/conf/

# EXPOSE - This instruction exposes specified port to the host machine.
EXPOSE 8085

ENTRYPOINT ["java","-jar","/lib/WebApp.war"]

The FROM directive indicates the base image from which to start. In our case, we need an image that provides java8. Again, DockerHub to the rescue: https://hub.docker.com/_/openjdk/. The COPY statements add to the image all the files needed to run the web application and with EXPOSE we open to the world the port 8085 that we will have previously configured in the embedded Tomcat as an HTTP listening port. Finally, ENTRYPOINT defines which actions will be executed as an entry point.

The documentation (https://docs.docker.com/engine/reference/builder/#usage) contains a more comprehensive detail of the Dockerfile directives. It is generated once, and distributed unchanged. In our case we are copying configuration files within the image that are rarely maintained between environments. The solution is to outsource the image files and have the container on duty access them on the host through a volume. But, that is beyond the scope of this post.

We're ready to generate the image now. We use the Docker "build" command to create the image in our local repository (of the Ubuntu virtual machine we are working on). We go back to our Vagrant and run:

$/shared/webapp$ docker build -t miwebapp .
Sending build context to Docker daemon  50.14MB
Step 1/7 : FROM openjdk:8-jdk-alpine
[...]
Successfully built ab4c219b27bd
Successfully tagged miwebapp:latest

Perfect, we already have our application as an image ready to be "containerized".

2. Container Up!

The idea is that our container travels between environments, but first we will try in our virtual environment a "snapshot" of what we will distribute to the customer. The image we have created of our web application is accessible in the local repository, so we edit the Docker-Compose configuration file to add the new service:

version: '3'
services:
    mongodb:
        image: mongo:latest
        container_name: "mongodb"
        volumes:
          - ./data/:/data/
        ports:
          - 27017:27017
        expose:
            - 27017
        networks:
            - webapp-network
    miwebapp:
        image: miwebapp:latest
        container_name: "miwebapp"
        volumes:
          - webapp-logs:/var/logs/
        ports:
          - 8085:8085
        expose:
            - 8085
        links:
            - mongodb
        depends_on:
            - mongodb
        networks:
            - webapp-network
networks:
    webapp-network:
        driver: bridge
volumes:
    webapp-logs:
        external: true

We have made several changes. The most significant are:

  • Links: They allow us to communicate between containers. From the miwebapp service we will have access to MongoDB using the service name as host: http://mongodb:27017.
  • Networks: Create a network of services that communicate with each other.
  • Volumes: The information generated inside a container is not persistent and disappears when the service is stopped. To persist such information, we must create a volume where to save it. The volumes are created on the host and associated to the indicated path of the container. There are several types of volumes depending on the use case: https://docs.docker.com/storage/volumes/. In our case, Mongo uses the /data folder to store the database and MyWebApp displays the /var/logs path where it generates the usage traces.

To access the volume as root user in the path:

$>cd /var/lib/docker/volumes/webapp-logs/_data/

Once the container configuration is finished, we start the services from the /docker folder, where the docker-compose.yml is located:

$> docker-compose up -d
Starting mongodb ... done
Starting miwebapp... done
$> docker-compose ps
 Name                Command               State            Ports
---------------------------------------------------------------------------
mongodb   docker-entrypoint.sh mongod  Up      0.0.0.0:27017->27017/tcp
miwebapp java -jar /lib/WebApp.war      Up      0.0.0.0:8085->8085/tcp

We'd have everything up and running by now. Finally, we would only have to map port 8087 in the Vagrantfile configuration file, to be able to access the web application from outside the virtual environment:

# MiWebApp
  config.vm.network "forwarded_port", guest: 8085, host: 8087

Now we can access, from our local Windows system, the web application, deployed as a container in the virtual environment, through port 8087: http://localhost:8087/miwebapp/login.xhtml.

3. Container for production

All that remains is for us to carry our virtual environment to other environments. In the case of a general use, we would have to deploy our applications in Certification, Pre-Production and Production. To do this, we have to make our image visible outside our premises and add it to a repository that has access to the client. We have several options, being the most used:

  • DockerHub (https://hub.docker.com/)
  • Amazon Web Services Elastic Container Registry (https://aws.amazon.com/es/ecr/)
  • Google Container Registry (https://cloud.google.com/container-registry/)
  • Azure Container Registry (https://azure.microsoft.com/es-es/services/container-registry/)

Of course, if the developments are for a final client, we are interested in a private repository to which only our organization has access. Since we have configured the images with the "latest" version, with each new deployment, we would simply have to re-run the services with "docker-compose up" and the containers would be updated to the latest version we had uploaded to the repository. The days of deployments are over! In addition, Docker is fully integrable with a CI/CD continuous integration system so, by adding a Jenkins and SonarQube to the equation, we can generate automated and quality deployments, reducing the process to a few clicks.

Share the article

Share on twitter
Twitter
Share on linkedin
LinkedIn
Share on email
Email
Share on whatsapp
WhatsApp

A new generation of technological services and products for our customers