Step 1: Configuring Gradle for Web Archives (.war)
To create standard web archives, ensure the war plugin is applied in build.gradle. Additionally, we will configure a predictable output directory and file name so our automation script always knows where to locate the generated binary.
Add the following to build.gradle:
plugins {
id 'java'
id 'war'
}
group = 'com.demo'
version = '1.0.0'
repositories {
mavenCentral()
}
dependencies {
// Jakarta Servlet API (or javax.servlet for older Tomcat versions)
providedCompile 'jakarta.servlet:jakarta.servlet-api:6.0.0'
}
// Ensure predictable archive naming and destination
war {
archiveFileName = 'app.war'
destinationDirectory = file("$buildDir/dist")
}
By explicitly setting archiveFileName = 'app.war' and directing it to $buildDir/dist, we prevent dynamic version numbers (e.g., app-1.0.0.war) from breaking our downstream bash script.
Test the build configuration:
./gradlew war
You should see your compiled package at build/dist/app.war.
Step 2: Creating the Deployment Script (deploy.sh)
Now, we will write a Bash script to orchestrate the deployment workflow:
- Validate and build the
.warpackage. - Stop the local Tomcat instance safely.
- Clean old deployments from Tomcat's
webapps/directory. - Copy the new
.warfile. - Restart Tomcat and display process logs.
Create deploy.sh in your project root:
#!/usr/bin/env bash
# Exit immediately if a command exits with a non-zero status
set -e
# Configuration Paths - Adjust TOMCAT_HOME to your installation path
TOMCAT_HOME="${TOMCAT_HOME:-/opt/tomcat}"
WAR_SOURCE="build/dist/app.war"
DEPLOY_DIR="$TOMCAT_HOME/webapps"
APP_NAME="app"
# Terminal Formatting Colors
GREEN=' [0;32m'
BLUE=' [0;34m'
RED=' [0;31m'
NC=' [0m' # No Color
echo -e "${BLUE}[1/5] Building project archive with Gradle...${NC}"
./gradlew war
if [ ! -f "$WAR_SOURCE" ]; then
echo -e "${RED}[ERROR] Archive build failed. $WAR_SOURCE not found.${NC}"
exit 1
fi
echo -e "${BLUE}[2/5] Stopping Tomcat...${NC}"
if [ -f "$TOMCAT_HOME/bin/shutdown.sh" ]; then
"$TOMCAT_HOME/bin/shutdown.sh" || true
fi
echo -e "${BLUE}[3/5] Cleaning previous deployments...${NC}"
rm -rf "$DEPLOY_DIR/$APP_NAME"
rm -f "$DEPLOY_DIR/$APP_NAME.war"
echo -e "${BLUE}[4/5] Deploying new WAR file...${NC}"
cp "$WAR_SOURCE" "$DEPLOY_DIR/$APP_NAME.war"
echo -e "${BLUE}[5/5] Starting Tomcat...${NC}"
"$TOMCAT_HOME/bin/startup.sh"
echo -e "${GREEN} Successfully deployed $APP_NAME.war to Tomcat!${NC}"
echo -e "Access application at: http://localhost:8080/$APP_NAME/"
Make the script executable:
chmod +x deploy.sh
Step 3: Running and Verifying the Pipeline
Execute your single-command deployment:
./deploy.sh
Script Execution Flow
- Gradle compiles class files and packages
build/dist/app.war. - Tomcat is safely stopped to release file locks.
- Legacy context folders and stale
.warfiles are removed fromwebapps/. - The fresh build is placed into
webapps/app.war. - Tomcat restarts and auto-expands the package.
Verify by running:
curl -I http://localhost:8080/app/
Key Takeaways & Conclusion
-
Predictable Outputs: Setting deterministic output filenames in
build.gradlesimplifies automation scripts. -
Idempotency: Cleaning stale files (
rm -rf) before deploying prevents Tomcat from serving stale code or holding file locks. - Portability: This Bash pattern can easily be modified for remote servers over SSH or integrated into CI/CD pipelines like GitHub Actions or Jenkins.
Did you find this approach helpful? Check out my open-source projects on GitHub or connect with me on DEV Community.
Top comments (0)