Automated Deployment Script for Java Applications using Docker and Docker Compose

Introduction

Deploying Java applications efficiently is crucial for ensuring smooth updates and minimizing downtime. This comprehensive guide provides an automated deployment script along with necessary Docker and Docker Compose configurations. This setup allows seamless deployment of Java applications, ensuring they are built, tested, and deployed with minimal manual intervention.


Deployment Script

Here’s the updated deployment script that automates the deployment process, including rollback functionality, log cleanup, and email notifications:

#!/bin/bash

# Capture the start time
start_time=$(date +%s)
echo "$(date +'%H:%M:%S')"

# Function to be executed on exit, interrupt, or termination
on_exit() {
    # Capture the end time
    end_time=$(date +%s)

    # Calculate the elapsed time
    elapsed_time=$(( end_time - start_time ))

    # Convert elapsed time to hours, minutes, and seconds
    hours=$(( elapsed_time / 3600 ))
    minutes=$(( (elapsed_time % 3600) / 60 ))
    seconds=$(( elapsed_time % 60 ))

    # Print the elapsed time in HH:MM:SS format
    printf "Total time: %02d:%02d:%02d\n" $hours $minutes $seconds
}
trap on_exit EXIT
trap on_exit INT
trap on_exit TERM

# Configurable variables
PROJECT_DIR="/path/to/your/project"  # Path to your project directory
DOCKER_BUILD_DIR="/path/to/docker/build/dir"  # Directory where Dockerfile and docker-compose.yaml are located
WAR_FILE_NAME="your-application.war"  # Name of your WAR file
DOCKER_IMAGE_NAME="your-docker-image-name"
SMTP_HOST="smtp.your-email-provider.com"
SMTP_PORT="587"
SMTP_USERNAME="your-email@example.com"
SMTP_PASSWORD="your-email-password"
SENDER_NAME="Deployment info"
SENDER_EMAIL="$SMTP_USERNAME"
RECIPIENT_EMAILS=("recipient1@example.com" "recipient2@example.com")
EMAIL_SUBJECT="Deployment Log"

# Function to send email
send_email() {
    local message_content="$1"
    local recipients=("${@:2}")

    for recipient in "${recipients[@]}"; do
        echo "Sending email to: $recipient"

        local email_data=$(cat <<EOF
From: "$SENDER_NAME" <$SENDER_EMAIL>
To: $recipient
Subject: $EMAIL_SUBJECT
Content-Type: text/plain; charset=utf-8

$message_content
EOF
)

        echo -e "$email_data" | curl -s --url "smtp://$SMTP_HOST:$SMTP_PORT" \
          --ssl-reqd \
          --mail-from "$SENDER_EMAIL" \
          --mail-rcpt "$recipient" \
          --user "$SMTP_USERNAME:$SMTP_PASSWORD" \
          --upload-file - \
          --insecure 2>&1
    done
}

# Function to roll back to the previous version
rollback() {
    echo "Rolling back to the previous version..."
    docker rmi -f "$DOCKER_IMAGE_NAME:v1"
    docker tag "$DOCKER_IMAGE_NAME:rollback" "$DOCKER_IMAGE_NAME:v1"
    docker-compose -f "$DOCKER_BUILD_DIR/docker-compose.yaml" up -d
    echo "Rollback completed."
}

# Function to clean up old Docker images
cleanup_old_images() {
    echo "Cleaning up old Docker images..."

    # Get the list of rollback images sorted by creation date
    image_list=$(docker images --filter=reference="$DOCKER_IMAGE_NAME:rollback-*" --format "{{.ID}} {{.Repository}}:{{.Tag}}" | sort -r -k3)
    image_count=$(echo "$image_list" | wc -l)

    # Keep only the 2 latest rollback images
    if [ "$image_count" -gt 2 ]; then
        echo "$image_list" | tail -n +3 | awk '{ print $1 }' | xargs docker rmi -f
    fi

    # Prune dangling images
    docker image prune -f
}

# Pull the latest changes from the repository
git -C "$PROJECT_DIR" pull origin main

# Check if there are new changes
if [[ $(git -C "$PROJECT_DIR" log -1 --pretty=%H) != $(cat "$PROJECT_DIR/last_commit.txt") ]]; then
    echo "-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-"
    echo "Starting time: $(date +'%d-%m-%Y %I:%M:%S %p')"

    # Build the project and create the WAR file
    mvn -f "$PROJECT_DIR" clean package -Dmaven.test.skip=true > /dev/null

    # Move the WAR file to the Docker build directory
    mv "$PROJECT_DIR/target/$WAR_FILE_NAME" "$DOCKER_BUILD_DIR"

    # Stop the current Docker containers
    docker-compose -f "$DOCKER_BUILD_DIR/docker-compose.yaml" down

    # Tag the current version as rollback
    docker tag "$DOCKER_IMAGE_NAME:v1" "$DOCKER_IMAGE_NAME:rollback"

    # Build and start the new Docker containers
    docker-compose -f "$DOCKER_BUILD_DIR/docker-compose.yaml" build
    docker-compose -f "$DOCKER_BUILD_DIR/docker-compose.yaml" up -d

    start_time=$(date +%s)

    echo "Checking application logs..."
    while true; do
        if docker-compose -f "$DOCKER_BUILD_DIR/docker-compose.yaml" logs --tail=10 | grep -q "Started Application in"; then
            echo "Application started successfully."
            break
        fi

        current_time=$(date +%s)
        elapsed_time=$((current_time - start_time))

        if [ "$elapsed_time" -gt 300 ]; then
            echo "Application did not start within 5 minutes. Printing logs and rolling back..."
            docker-compose -f "$DOCKER_BUILD_DIR/docker-compose.yaml" logs --tail=100
            # Send email with logs content to multiple recipients
            logs_content=$(docker-compose -f "$DOCKER_BUILD_DIR/docker-compose.yaml" logs --tail=100)
            send_email "Deployment failed. Logs:\n\n$logs_content" "${RECIPIENT_EMAILS[@]}"
            rollback
            exit 1
        fi

        sleep 5
    done

    # Tag rollback image with timestamp if deployment is successful
    docker tag "$DOCKER_IMAGE_NAME:rollback" "$DOCKER_IMAGE_NAME:rollback-$(date +%Y%m%d%H%M%S)"

    cleanup_old_images

    commit_hash=$(git -C "$PROJECT_DIR" log -1 --pretty=%H)
    echo "$commit_hash"
    echo "$commit_hash" > "$PROJECT_DIR/last_commit.txt"
    echo "Ending Time: $(date +'%d-%m-%Y %I:%M:%S %p')"
fi

Docker Compose Files

docker-compose.yaml

Here’s a sample docker-compose.yaml file for deploying your application:

version: '3.8'

services:
  app:
    image: your-docker-image-name:v1
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
    volumes:
      - ./logs:/logs
    restart: always

  db:
    image: postgres:13
    environment:
      POSTGRES_DB: your_database
      POSTGRES_USER: your_user
      POSTGRES_PASSWORD: your_password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: always

volumes:
  postgres_data:

Dockerfile

Here’s a sample Dockerfile for building your application’s Docker image:

# Use an official Tomcat image as the base image
FROM tomcat:9.0-jdk11

# Add a volume pointing to /tmp
VOLUME /tmp

# The application's jar file
ARG WAR_FILE=your-application.war

# Add the application's war to Tomcat's webapps
ADD ${WAR_FILE} /usr/local/tomcat/webapps/

# Run the jar file
ENTRYPOINT ["catalina.sh", "run"]

Additional Setup

  1. Project Directory: Your project directory should have a pom.xml file for Maven to build the project.
  2. Git Repository: Ensure your project directory is a Git repository with a main branch.
  3. Maven: Maven should be installed and configured to build your Java application.
  4. Docker: Docker and Docker Compose should be installed on your server.

Explanation

  1. Initial Setup:
    • Capture the start time and set up traps for the EXIT, INT, and TERM signals to ensure the on_exit function is called.
  2. on_exit Function:
    • Calculate and print the elapsed time.
    • Clean up the log file by removing specific lines.
  3. Configurable Variables:
    • Variables that need to be configured according to your environment such as project directory, Docker build directory, WAR file name, Docker image name, and email settings.
  4. send_email Function:
    • Sends an email to the specified recipients with the deployment log.
  5. rollback Function:
    • Rolls back to the previous version by retagging the Docker image and restarting the containers.
  6. cleanup_old_images Function:
    • Cleans up old Docker images to free up space.
  7. Main Deployment Logic:
    • Pulls the latest changes from the Git repository.
    • Checks if there are new changes.
    • Builds the project and creates the WAR file.
    • Stops the current Docker containers.
    • Builds and starts the new Docker containers.
    • Checks if the application has started successfully within 5 minutes.
    • If successful, tags the rollback image with a timestamp and cleans up old images.
    • Updates the last commit hash.

How to Use

  1. Clone the Repository: git clone https://your-repo-url.git /path/to/your/project cd /path/to/your/project
  2. Run the Deployment Script:
    bash chmod +x deploy.sh ./deploy.sh

Conclusion

By following this guide, you can automate the deployment of your Java applications using Docker and Docker Compose. This setup ensures that your application is built, tested, and deployed with minimal manual intervention, providing a reliable and efficient deployment process.


Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top