avatarIvan Polovyi

Summarize

Build and push an image using Docker Compose

Docker Compose was developed for defining and running containerized applications. Instead of executing multiple commands with multiple arguments in the special sequence, a Docker Compose allows us to define all in one file and run it by executing a single command.

Defining all instructions in the file has many advantages. We don't have to memorize commands, and their sequence, we can track changes to a file and roll back if needed, and we can pass around the file from one developer to another.

The Docker Compose definition file allows us to specify image build instructions in the buildsubsection, so Docker Compose can build an image according to those instructions.

Set up

Let's say we have an application that we want to ship in a container. I've created a simple Spring Boot application for this tutorial that handles customers' information. It exposes CRUD REST API and persists all information in an in-memory database.

To create a Docker image we have to define a Dockerfile. For this tutorial, I will use a complex Dockerfile. It will allow me to build and run the image using a single command. Even better this approach won't require additional installation of Maven or Java.

Here I will only post a Dockerfile without additional explanation because it is already explained in my other tutorial:

The Dockerfile:

FROM amazoncorretto:17-alpine-jdk

# 1- Add curlf
RUN apk add --no-cache curl

# 2- Define a constant with the version of Maven.
ARG MAVEN_VERSION=3.8.3

# 3- Define the SHA key to validate the maven download. EACH VERSION HAS ITS OWN SHA!!!
ARG SHA=1c12a5df43421795054874fd54bb8b37d242949133b5bf6052a063a13a93f13a20e6e9dae2b3d85b9c7034ec977bbc2b6e7f66832182b9c863711d78bfe60faa

# 4- Define a constant with the directory for Maven installation
ARG MAVEN_HOME_DIR=usr/share/maven

# 5- Define a constant with the working directory
ARG APP_DIR="app"

# 6- Define the URL where maven can be downloaded from
ARG BASE_URL=https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries

# 7- Create the directories, download Maven, validate the download, install it, remove the downloaded file, and set links
RUN mkdir -p /$MAVEN_HOME_DIR /$MAVEN_HOME_DIR/ref \
  && echo "[ECHO] Downloading maven" \
  && curl -fsSL -o /tmp/apache-maven.tar.gz ${BASE_URL}/apache-maven-${MAVEN_VERSION}-bin.tar.gz \
  \
  && echo "[ECHO] Checking download hash" \
  && echo "${SHA}  /tmp/apache-maven.tar.gz" | sha512sum -c - \
  \
  && echo "[ECHO] Unzipping maven" \
  && tar -xzf /tmp/apache-maven.tar.gz -C /$MAVEN_HOME_DIR --strip-components=1 \
  \
  && echo "[ECHO] Cleaning and setting links" \
  && rm -f /tmp/apache-maven.tar.gz \
  && ln -s /$MAVEN_HOME_DIR/bin/mvn /usr/bin/mvn

# 8- Define environmental variables required by Maven, like the Maven_Home directory and where the maven repo is located
ENV MAVEN_CONFIG "/${APP_DIR}/.m2"

# 9- Define app name an artifactId from POM
ENV APP_NAME spring-customer-app

# 10- Copy source code and POM
COPY ./src ./$APP_DIR/src
COPY pom.xml ./$APP_DIR

# 11- Define app directory as the working directory
WORKDIR /$APP_DIR

# 12- Build and package source code using Maven
RUN mvn clean package

# 13- Copy jar file to the work directory
RUN mv target/$APP_NAME.jar .

# 14- Remove Maven and source code of an application to make an image cleaner
RUN echo "[ECHO] Removing source code" \
    && rm -rf /$APP_DIR/src \
    \
    && echo "[ECHO] Removing pom.xml"  \
    && rm -f /$APP_DIR/pom.xml \
    \
    && echo "[ECHO] Removing output of the build"  \
    && rm -rf /$APP_DIR/target \
    \
    && echo "[ECHO] Removing local maven repository ${MAVEN_CONFIG}"  \
    && rm -rf $MAVEN_CONFIG \
    \
    && echo "[ECHO] Removing maven binaries"  \
    && rm -rf /$MAVEN_HOME_DIR \
    \
    && echo "[ECHO] Removing curl binaries"  \
    && apk del --no-cache curl

VOLUME $APP_DIR/tmp
EXPOSE 8001

ENTRYPOINT exec java -jar $APP_NAME.jar -Djava.security.egd=file:/dev/./urandom $JAVA_OPTS

The project structure is as follows:

The complete code can be found here:

Build instruction

As I've mentioned previously to use Docker Compose we have to create a docker-compose file.

services:
  customer-app:
    ports:
      - "8001:8001"
    build:
      context: ./spring-customer-app
      dockerfile: ./Dockerfile
      no_cache: true
      labels:
        label_name: "test-build"
      tags:
        - "spring-customer-app:latest"

This is a simple file, it defines one service called customer-app. The service definition includes port mappings so that we can test our app and a build instruction.

In the build instruction, I've specified a context. Context is the folder with the code of the app. In a docker file section, I've set the path to a previously-mentioned Docker file. It is not necessary as by default Docker Compose gets the file with the name Dockerfile in the context directory. But I've specified it so you would know if for some reason the name of the file is different or/and it is not in the context directory.

When the Docker builds an image it cashed it to speed up subsequential builds or rebuilds of the same image. It is a very good feature, but for testing purposes, we may need to disable it and force the Docker to rebuild an image from scratch. For this, I set the no_cache instruction but commented it out. If you need it uncomment it and then the Docker will rebuild an image.

The build section allows us to specify labels and tags for our image.

Now we can execute the following command to create a container with our service running on it. Actually, first, it will build the code, then image with the code, and then create a container. And everything will be done only with one command.

Execute the following command from the directory where the docker-compose file is located:

$docker compose up

Because the definition file does not have an image specified, Docker starts to build an image process right away.

After a couple of minutes, the application will start on the container:

And now we can test it using Postman.

The build process will take a while so be patient, it depends on the configuration of your PC.

To stop the container you can press Ctrl+C and then execute the following command:

$docker compose down

This command will remove a container.

Now when we execute the command to list all images on our local Docker repository we can find our image

$docker image ls

It has two tags the one we specified in the tag section and another is the default one that matches the name of a root directory.

Subsequential runs will be much faster because the image is already built so Docker will just use it.

Push the image to a repository

We can build and push the image to a Docker Hub public repository, using Docker Compose.

First, let's create a public repository. Go to DockerHub and create a repository. If you don't have an account just create it it is free and easy to create.

Fill up the name, add an optional description and press Create.

And the repo will be created.

Let's create another docker-compose file.

services:
  customer-app:
    image: polovyiivan/spring-customer-app-v2:latest
    ports:
      - "8001:8001"
    build:
      context: ./spring-customer-app
      dockerfile: ./Dockerfile
#      no_cache: true
      labels:
        label_name: "test-build"
      tags:
        - "spring-customer-app-v2:latest"
        - "polovyiivan/spring-customer-app-v2:latest"

In this file, I specified an image option. To be able to push the image to the previously created repository we have to put the name of the repository. In your case, it will be different, because it includes a username you just need to put your here.

To be able to push it to a repository we have to log in to it using the terminal, by executing the command:

$docker login

This command will ask about credentials, we have to type the credentials of the DockerHub account.

Now we can execute the following command to build the image:

$docker compose -f docker-compose-v2.yaml build

Here we have to specify the name of the file because it is different than the default one.

The build process is started:

After the build process is done we can check the image:

Now we can execute the push command:

$docker compose -f docker-compose-v2.yaml push

After is done the image will be available in the repository:

To remove an image, execute the command:

$docker image ls

To list images with respective IDs:

After, execute the command to remove an image:

$docker image rm --force 9661a22d8ef7

To delete the repository go to the settings of the repository and press the Delete repository button.

Conclusion

The build instruction in the docker-compose file allows us to build an image before running the container based on it. This way we don't need to enter yet another docker command. This is very handy when you have to pass around your project, for other developers to use. In this case, you won't need to explain how to build your image as it will be incorporated into the run process.

Thank you for reading! Please like and follow. If you have any questions or suggestions, please feel free to write in the comments section or on my LinkedIn account.

Programming Languages
Programming
Docker
Docker Compose
Microservices
Recommended from ReadMedium