Automate Spring Boot Packaging and Deployment with Maven and a Custom Shell Tool
This guide explains how to configure Maven profiles and the assembly plugin to create a zip package for a Spring Boot application, then use a reusable shell script to unzip, start, stop, and restart the service on Linux, streamlining deployment across multiple environments.
This article shares a complete workflow for packaging a Spring Boot application and deploying it with a custom shell script, focusing on a reusable launch tool that simplifies the process.
Key Topics
Using Maven profiles to specify different environment configurations.
Creating a deployment zip with maven-assembly-plugin and maven-jar-plugin.
Introducing the shenniu_publish.sh script for automated unzip, start, stop, and restart operations on Linux.
Maven Profiles Configuration
<profiles>
<profile>
<id>node</id>
<properties>
<!-- Parameters passed to the script -->
<activeProfile>node</activeProfile>
<package-name>${scripts_packageName}</package-name>
<boot-main>${scripts_bootMain}</boot-main>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>node1</id>
<properties>
<activeProfile>node1</activeProfile>
<package-name>${scripts_packageName}</package-name>
<boot-main>${scripts_bootMain}</boot-main>
</properties>
</profile>
<profile>
<id>node2</id>
<properties>
<activeProfile>node2</activeProfile>
<package-name>${scripts_packageName}</package-name>
<boot-main>${scripts_bootMain}</boot-main>
</properties>
</profile>
</profiles>The id element selects the directory containing the configuration files for a specific environment. The properties node defines variables ( package-name, boot-main) that can be referenced in other configuration files or the shell script. activeByDefault marks the default profile.
Maven Assembly Plugin for Zip Packaging
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>${scripts_bootMain}</mainClass>
</manifest>
</archive>
<excludes>
<exclude>**/*.yml</exclude>
<exclude>**/*.properties</exclude>
<exclude>**/*.xml</exclude>
<exclude>**/*.sh</exclude>
</excludes>
</configuration>
<executions>
<execution>
<id>make-jar</id>
<phase>compile</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<configuration>
<descriptors>
<descriptor>${project.basedir}/src/main/assembly/assembly.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>Key points: mainClass specifies the entry point of the Spring Boot application. excludes removes configuration files from the main JAR so they can be packaged separately. descriptor points to the assembly.xml that defines the zip layout.
Assembly Descriptor (assembly.xml)
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<id>${activeProfile}</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
<outputDirectory>${package-name}-${activeProfile}/lib</outputDirectory>
<unpack>false</unpack>
</dependencySet>
</dependencySets>
<fileSets>
<fileSet>
<directory>${project.basedir}/src/main/profiles/${activeProfile}</directory>
<outputDirectory>${package-name}-${activeProfile}/conf</outputDirectory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.basedir}/src/main/scripts</directory>
<outputDirectory></outputDirectory>
<includes>
<include>**/*</include>
</includes>
<fileMode>777</fileMode>
<directoryMode>777</directoryMode>
<filtered>true</filtered>
</fileSet>
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory>${package-name}-${activeProfile}/</outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
</fileSets>
</assembly>Important nodes: formats defines the archive type (zip). fileMode sets execution permission (777) for the script files on Linux. filtered enables Maven to replace placeholders (e.g., ${package-name}) with actual values from the profile.
Using the Shell Script
The shenniu_publish.sh script provides four main commands:
unzip : extracts the zip package.
start : launches the Spring Boot jar (supports java -cp and java -jar modes).
stop : terminates the running process.
restart : stops and then starts the application.
#!/usr/bin/env bash
# Variable placeholders are filled by Maven
languageType="javac" # java, javac, netcore
baseZipName="${package-name}-${activeProfile}"
packageName="${package-name}"
mainclass="${boot-main}"
basePath=$(cd `dirname $0`/; pwd)
baseZipPath="$basePath/$baseZipName.zip"
baseDirPath="$basePath"
function shenniu_unzip() {
echo "Unzipping $baseZipPath..."
if [ ! -f "$baseZipPath" ]; then
echo "Zip not found: $baseZipPath"
return
fi
unzip -od "$baseDirPath/$baseZipName" "$baseZipPath"
chmod +x "$baseDirPath/$baseZipName/$packageName"
echo "Unzip completed."
}
function getPid() {
pid=$(ps -ef | grep -v grep | grep "$packageName" | awk '{print $2}')
if [ "$pid" ]; then
echo "Running pid: $pid"
else
echo "Not running"
fi
}
function start() {
stop
if [ "$pid" ]; then
echo "Failed to stop previous process, aborting start"
return
fi
echo "Starting program..."
read -p "Enter program type (java,javac,netcore) [${languageType}]: " read_languageType
if [ "$read_languageType" ]; then
languageType=$read_languageType
fi
echo "Selected type: $languageType"
cd "$baseDirPath/$baseZipName"
if [ "$languageType" == "javac" ]; then
if [ "$mainclass" ]; then
nohup java -cp conf:lib/*.jar:${packageName}.jar $mainclass > $baseDirPath/$packageName.out 2>&1 &
fi
elif [ "$languageType" == "java" ]; then
nohup java -jar $baseDirPath/$baseZipName/${packageName}.jar > /dev/null 2>&1 &
elif [ "$languageType" == "netcore" ]; then
nohup $baseDirPath/$baseZipName/${packageName} > /dev/null 2>&1 &
fi
getPid
if [ "$pid" ]; then
echo "Started"
tail -n 50 -f $baseDirPath/$packageName.out
else
echo "Start failed"
fi
}
function stop() {
getPid
if [ "$pid" ]; then
echo "Stopping program..."
kill -9 $pid
getPid
if [ "$pid" ]; then
echo "Stop failed"
else
echo "Stop succeeded"
fi
fi
}
if [ $# -ge 1 ]; then
case $1 in
"start") start ;;
"restart") start ;;
"stop") stop ;;
"unzip") shenniu_unzip ; start ;;
*) echo "$1: no action" ;;
esac
else
echo "Usage: ./shenniu_publish.sh [start|stop|restart|unzip]"
fiParameters such as package-name, activeProfile, and boot-main are injected from Maven profiles, so the script itself does not need manual edits.
Deployment Steps
Run mvn clean package -Pnode (or another profile) to generate the zip.
Upload the zip to the target Linux server.
Convert the script to Unix line endings if edited on Windows:
vim shenniu_publish.sh
set ff=unix
:wqExecute the script, e.g., ./shenniu_publish.sh unzip or ./shenniu_publish.sh start.
The script will unzip the package, set execution permissions, and start the Spring Boot service, displaying logs or reporting errors as needed.
Reference
Git repository with the full project and script: https://github.com/shenniubuxing3/springcloud-Finchley.SR2
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java High-Performance Architecture
Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
