Docker Project: Building a 3-Tier Application with Docker Compose and Optimizing Image Size Using Multi-Stage Builds

Docker Project: Building a 3-Tier Application with Docker Compose and Optimizing Image Size Using Multi-Stage Builds

Introduction

  • In this blog, we will explore how Docker Compose and Multi-stage Docker builds can help streamline the development workflow and optimize your containerized applications.

  • First, we'll look at some theory behind these tools and then guide you through a practical example, demonstrating their use in a Java-based expense tracker application.

Docker Compose

  • Docker Compose is a tool for defining and managing multi-container Docker applications. It allows you to define a multi-container application in a single YAML file, making it easier to configure and manage containers.

  • Compose simplifies the orchestration of multiple services, such as databases, backend services, and front-end apps, by automating the management of their networking, volumes, and dependencies.

Key benefits of Docker Compose:

  • Multi-container management: Define all the services (containers) for your application in a single docker-compose.yml file.

  • Networking: Services in the same Compose application can communicate with each other using simple service names, without having to worry about IP addresses.

  • Scaling: Docker Compose makes it easy to scale services by adjusting the number of replicas for a container.

  • Environment variables: Simplifies configuration by using environment variables to keep the application portable.

Multi-stage Docker Builds

  • A multi-stage Docker build is a technique that allows you to create smaller and more efficient Docker images by separating the build process into multiple stages. Each stage can use a different base image, and only the necessary artifacts (such as compiled binaries or assets) are copied into the final image. This results in smaller images and faster build times.

Key benefits of Multi-stage Docker builds:

  • Reduced image size: By discarding unnecessary dependencies and tools after the build stage, you can significantly reduce the size of the final image.

  • Separation of concerns: Each stage is isolated, which leads to better organization of the build process.

  • Improved security: Only the necessary files are included in the final image, reducing the attack surface of your application.

Docker File and Docker Compose Setup

  • In this section, we will create and configure the necessary files to build a Java-based expense tracker application using Docker. We will walk through each file, including the Dockerfile for building the application, the multi-stage Dockerfile for optimized image creation, and the Docker Compose file for managing services like MySQL and the Java app.

Prerequisites

  • Before diving into the tutorial, ensure you have Docker and Git installed on your machine.

Step 1: Clone the Project Repository

  • To get started, the first step is to clone the project repository to your machine. This ensures you have all the necessary source files for the application.
git clone https://github.com/RutvikMangukiya/Expenses-Tracker-WebApp.git

  • Both the Dockerfile and docker-compose.yml files have been recreated from scratch after removing the previous versions.

Step 2: Create a Dockerfile and Build the Image

  • Next, let's create a Dockerfile for building the application image.

  • Dockerfile Preview:

# Maven image
FROM maven:3.8.3-openjdk-17 AS builder 

# Set working directory
WORKDIR /app

# Copy source code from local to container
COPY . /app

# Build application and skip test cases
EXPOSE 8080

RUN mvn clean install -DskipTests=true

CMD ["java", "-jar", "/expenseapp.jar"]
  • This Dockerfile uses Maven to build the Java application and creates a simple image with the final artifact. However, we can optimize it further by splitting the build process into stages.

Step 3: Multi-stage Dockerfile for Optimized Build

  • In the multi-stage build approach, we separate the build process into two stages, one for compiling the application and another for running it.

  • Multi-stage Dockerfile Preview:

# Stage 1 - Build the JAR (Java Application Runtime) using image of maven
FROM maven:3.8.3-openjdk-17 AS builder

# Setup working directory
WORKDIR /app

# Copy source code from host machine to container
COPY . /app

# Build application and skip test cases

#EXPOSE 8080

# Create JAR File
RUN mvn clean install -DskipTests=true

#CMD ["java", "-jar", "/expenseapp.jar"]

#--------------------------------------------
# Stage 2 - Execute JAR File from above stage
#--------------------------------------------

# Use a lightweight Java base image
FROM openjdk:17-alpine

WORKDIR /app

# Copy build from stage 1 (builder)
COPY --from=builder /app/target/*.jar /app/target/expenseapp.jar

# Expose application port
EXPOSE 8080

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

In this setup:

  • Stage 1 uses Maven to compile the application and create the JAR file.

  • Stage 2 uses a smaller base image (openjdk:17-alpine), which significantly reduces the size of the final image by only including the necessary runtime files.

Step 4: Create a Docker Compose File

  • With Docker Compose, we can manage multiple services, such as the Java application and MySQL database, in a single YAML configuration file.

  • Docker-Compose.yml file preview:

version: "3.8"
services:
  java_app:
    build: 
      context: .
    container_name: "expenses-tracker-app"
    environment:
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_URL: "jdbc:mysql://mysql:3306/expenses_tracker?allowPublicKeyRetrieval=true&useSSL=false"
      SPRING_DATASOURCE_PASSWORD: Test@123
    ports:
      - "8080:8080"
    networks:
      - expenses-app-nw
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:8000 || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 60s
    depends_on:
      - mysql_db
    restart: always  

  mysql_db:
    image: mysql
    container_name: "mysql"
    environment:
      MYSQL_ROOT_PASSWORD: Test@123
      MYSQL_DATABASE: expenses_tracker
    volumes:
      - ./mysql-data:/var/lib/mysql
    ports:
      - "3306:3306"
    networks:
      - expenses-app-nw
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-pTest@123"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 60s
    restart: always

networks:
  expenses-app-nw:

volumes:
  mysql-data:

In this file:

  • The java_app service is responsible for building and running the Java application.

  • The mysql_db service handles the MySQL database and the depends_on keyword ensures that the database container is ready before the Java application starts.

  • Health checks are defined to ensure both services are running properly.

Step 5: Run the Application with Docker Compose

  • After setting up the Docker Compose file, you can now build and run the application with a single command:
docker-compose up
  • This command will build the Docker images for the services defined in the docker-compose.yml file and start the containers.

Step 6: Verify the Output

  • Once the containers are up and running, you can access the application at http://<ec2-public-ip>:8080

  • You should now see your Java-based expense tracker application running, with the MySQL database connected to it.

Summary

  • In this blog, we demonstrated the use of Docker Compose and Multi-stage Docker builds to streamline the process of containerizing a Java-based expense tracker application.

  • By using Docker Compose, we easily managed multiple services, such as the Java app and MySQL database, while the multi-stage Docker build process helped optimize the application’s container image. These practices lead to smaller, more efficient images and simplify the management of complex, multi-container applications.

  • With Docker Compose and Multi-stage builds you can achieve better performance, reduce build times, and improve the maintainability of your Dockerized applications.

Happy Learning! :)