jdeb – 자바 빌드로 debian package 만들기

Java와 Spring Boot를 주력으로 개발하는 많은 백엔드 개발자에게 배포는 항상 중요한 과제다. JAR 파일을 만들고 리눅스 서버에 올린 뒤 쉘 스크립트를 짜서 실행하고 Systemd 서비스로 등록하는 과정은 생각보다 손이 많이 가는 일이다.
이번에 Baeldung에서 소개된 ‘jdeb를 활용한 Java 빌드 프로세스 개선’ 기사를 보고 이러한 배포 과정을 단순화 할 수 있는 좋은 솔루션이 있어서 간단히 정리해 봤다.

jdeb 주목해야 할 기능 3가지

jdeb는 기본적으로 java 프로젝트의 JAR 파일을 Debian 패키지 (흔히 Ubuntu나 Debian 계열에서 사용하는 설치 파일)로 변환해주는 라이브러리다.

크로스 플랫폼 ‘.deb’ 패키지 생성 지원

기존에는 deb 패키지를 만들려면 보통 Debian 또는 Ubuntu 같은 리눅스 환경 (혹은 VM)이 필요했다. 하지만 jdeb는 순수 java 라이브러리로 동작하며 별도의 UNIX 유틸리티 없이도 Windows나 macOS 같은 환경에서 완벽하게 deb 파일을 생성할 수 있다. 이는 개발 환경의 제약을 크게 줄여준다.

Maven 및 Gradle, Ant 빌드 사이클에 완벽 통합

jdeb는 Maven Plugin과 Ant Task를 모두 제공한다. Ant Task를 이용하여 Gradle에서 유연하게 구성할 수도 있다.

  • Maven: pom.xml 내의 <plugin> 설정으로 간단하게 추가되며 보통 package 빌드 페이즈에 바인딩하여 JAR 파일이 생성된 직후에 deb 패키지가 자동 생성되도록 설정할 수 있다.
  • Ant: <dev> 태스크 정의를 통해 빌드 파일에 통합된다.
  • Gradle: jdeb의 Ant Task 기능을 가져와서 유연하게 구성할 수 있다.

직관적인 파일 계층 구조 매핑 (dataSet과 mapper)

deb 파일 내부에는 패키지 정보가 담긴 control.tar와 실제 설치 파일이 담긴 data.tar가 존재한다. jdeb는 이 중 data.tar에 들어갈 파일을 설정하는 것을 dataSetmapper를 통해서 추상화한다.
예를 들어 최종 JAR파일을 /opt/myapp에 넣거나 실행 스크립트(Launcher)를 /usr/bin에 넣는 등의 복잡한 리눅스 경로 설정은 Maven/Ant 설정 파일 내에서 직관적인 XML/Task 정의로 처리할 수 있다. 특히 control 파일의 메타데이터(패키지명, 버전, 의존성 등) 정의를 지원하여 패키지 구성의 오류를 줄여준다.

jdeb 사용 방법

Maven과 Gradle에서 어떻게 jdeb를 사용하여 deb 패키지를 생성하는지 확인해 보자.

  • deb 패키지명 : deb-test-app
  • FAT JAR 파일명 : spring-boot-jdeb.jar
  • systemd service : my-app

jdeb-Maven

jdeb는 Maven 빌드 라이프사이클에 deb 패키지 생성을 직접 통합하는 Maven 플러그인을 제공한다. 프로젝트의 pom.xml에서 이를 구성할 수 있다. 일반적으로 패키징 단계에 바인딩하고 jdeb goal을 실행한다. 패키지 메타데이터와 data.tar 아카이브에 포함할 파일 세트를 정의해야 한다. 프로젝트를 패키징하면 deb 파일이 다른 아티팩트와 함께 생성된다.

deb 패키지 구성에 필요한 파일 위치

jdeb 샘플 파일 구성
deb 구성을 위한 파일 위치

deb 패키지 설치 및 삭제시 실행될 스크립트 위치는 src/main/resources/deb/control 이다.
deb 패키지 설치시 systemd에 등록하기 위한 service 파일위치는 src/main/resources/deb/systemd 이다.
파일 위치는 꼭 resources에 있을 필요는 없다. src/deb 밑에 구성해도 되고.. 경로 설정만 잘 맞추면 된다.

control 파일 생성

먼저 패키지의 메타데이터 파일을 만들어야 한다.
파일 경로는 src/main/resources/deb/control/control 이라고 해보자.

Package: deb-test-app
Version: 1.0.0
Section: misc
Priority: optional
Architecture: all
Maintainer: My Name <my.email@example.com>
Description: This is my Spring Boot Application package
 distribution via jdeb.
Plaintext

control에 필요한 필드 정보는 https://www.debian.org/doc/debian-policy/ch-controlfields.html를 참조.
위 예에서 패키지 명은 dep-test-app 이다.

postinst 스크립트 파일

deb 패키지 파일이 설치된 이후에 실행될 스크립트를 정의했다. 이름은 postinst 로 맞춰야 한다.

#!/bin/sh
# 패키지 파일이 /opt/myapp에 설치된 직후 실행되는 스크립트
# 에러 발생 시 중단
set -e

# 설치(configure) 단계일 때만 실행
if [ "$1" = "configure" ]; then
    echo "Systemd 서비스 등록 및 재시작 중..."

    # 1. systemd 설정 새로고침 (새로운 .service 파일 인식)
    if command -v systemctl >/dev/null; then
        sudo systemctl daemon-reload

        # 2. 부팅 시 자동 실행 등록
        sudo systemctl enable my-app

        # 3. 서비스 (재)시작
        sudo systemctl restart my-app
    fi

    # 4. 권한 보정 (필요한 경우)
    chown -R root:root /opt/myapp
fi

exit 0
Bash

위 예에서 systemd 서비스명은 my-app 이다.

prerm 스크립트 파일

deb 패키지가 제거 되거나 업그레이드 되는 경우 실행되는 스크립트다.

#!/bin/sh
# 패키지를 삭제(remove) 하거나 업그레이드 직전에 실행됨
set -e

# 삭제(remove) 또는 업그레이드(upgrade) 전 실행
if [ "$1" = "remove" ] || [ "$1" = "upgrade" ]; then
    echo "서비스 중지 중..."

    if command -v systemctl >/dev/null; then
        sudo systemctl stop my-app
    fi
fi

exit 0
Bash

systemd service 등록 파일

[Unit]
Description=My Spring Boot Application
After=syslog.target

[Service]
User=root
# 실제 설치될 경로를 적어야 합니다!
ExecStart=/usr/bin/java -jar /opt/myapp/spring-boot-jdeb.jar
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target
Bash

위 예에서 fat jar 파일명은 spring-boot-jdeb.jar 이다.

pom.xml plugin 설정

<plugin>
    <groupId>org.vafer</groupId>
    <artifactId>jdeb</artifactId>
    <version>1.14</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals><goal>jdeb</goal></goals>
            <configuration>
                <!-- control 파일 경로를 지정한다. 디렉토리까지 지정하는 것에 유의!! -->
                <controlDir>${project.basedir}/src/main/resources/deb/control</controlDir>
                <!-- 배포할 파일들을 dataSet에 정의한다. -->
                <dataSet>
                    <data>
                        <!-- systemd service 등록 파일 위치를 설정한다. -->
                        <src>${project.basedir}/src/main/resources/deb/systemd/my-app.service</src>
                        <type>file</type>
                        <mapper>
                            <type>perm</type>
                            <prefix>/lib/systemd/system</prefix>
                        </mapper>
                    </data>
                    <data>
                        <src>${project.build.directory}/${project.build.finalName}.jar</src>
                        <type>file</type>
                        <mapper>
                            <type>perm</type>
                            <prefix>/opt/myapp</prefix>
                        </mapper>
                    </data>
                </dataSet>
            </configuration>
        </execution>
    </executions>
</plugin>
XML

packaing 하기

$> ./mvnw clean package
Bash

package를 수행하면 target 디렉토리에 다음과 같은 패키지 파일이 생성된다.

  • spring-boot-jdeb_0.0.1~SNAPSHOT_all.deb
  • spring-boot-jdeb.jar

spring-boot-jdeb_0.0.1~SNAPSHOT_all.deb 파일이 바로 deb 패키지 파일이다. 해당 패키지 파일 내용은 다음과 같다.

ubuntu@ubuntu-jammy:~$ dpkg -c spring-boot-jdeb_0.0.1~SNAPSHOT_all.deb
drwxr-xr-x root/root         0 2025-12-08 22:13 ./lib/
drwxr-xr-x root/root         0 2025-12-08 22:13 ./lib/systemd/
drwxr-xr-x root/root         0 2025-12-08 22:13 ./lib/systemd/system/
-rw-r--r-- root/root       257 2025-12-08 22:13 ./lib/systemd/system/my-app.service
drwxr-xr-x root/root         0 2025-12-08 22:13 ./opt/
drwxr-xr-x root/root         0 2025-12-08 22:13 ./opt/myapp/
-rw-r--r-- root/root  20840680 2025-12-08 22:13 ./opt/myapp/spring-boot-jdeb.jar
Bash

deb 파일을 설치하면 my-app.service는 /lib/systemd/system에 설치되고 spring-boot-jdeb.jar는 /opt/myapp에 설치된다.

설치해보기

먼저 패키지 정보를 확인해 보자.

ubuntu@ubuntu-jammy:~$ dpkg --info spring-boot-jdeb_0.0.1~SNAPSHOT_all.deb
 new Debian package, version 2.0.
 size 21000796 bytes: control archive=1006 bytes.
     233 bytes,     9 lines      control
     133 bytes,     2 lines      md5sums
     664 bytes,    25 lines   *  postinst             #!/bin/sh
     341 bytes,    14 lines   *  prerm                #!/bin/sh
 Package: deb-test-app
 Version: 1.0.0
 Section: misc
 Priority: optional
 Architecture: all
 Installed-Size: 22716
 Maintainer: My Name <my.email@example.com>
 Description: This is my Spring Boot Application package
  distribution via jdeb.
Bash

앞에서 정의한 control, postinst, prerm 파일이 포함된 것을 확인할 수 있다.
dataSet에 정의한 파일은 배포 대상 파일이므로 메타 정보(–info)에는 포함되지 않는다.

이제 패키지를 설치 해보자.

ubuntu@ubuntu-jammy:~$ sudo dpkg -i spring-boot-jdeb_0.0.1~SNAPSHOT_all.deb
Selecting previously unselected package deb-test-app.
(Reading database ... 66578 files and directories currently installed.)
Preparing to unpack spring-boot-jdeb_0.0.1~SNAPSHOT_all.deb ...
Unpacking deb-test-app (1.0.0) ...
Setting up deb-test-app (1.0.0) ...
Systemd 서비스 등록  재시작 중...
Created symlink /etc/systemd/system/multi-user.target.wants/my-app.service  /lib/systemd/system/my-app.service.
Bash

마지막 ‘서비스 등록 및 재시작 중…’ 메시지가 출력되는 걸로 봐서 우리가 정의한 postinst 스크립트 실행이 되는 것을 확인 할 수 있을 것이다.

my-app 이름으로 systemd 서비스 등록이 잘 되었는지 확인 해 보자.

ubuntu@ubuntu-jammy:~$ systemctl status my-app
 my-app.service - My Spring Boot Application
     Loaded: loaded (/lib/systemd/system/my-app.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2025-12-08 23:06:31 KST; 11s ago
   Main PID: 2536 (java)
      Tasks: 30 (limit: 1066)
     Memory: 101.8M
        CPU: 2.461s
     CGroup: /system.slice/my-app.service
             └─2536 /usr/bin/java -jar /opt/myapp/spring-boot-jdeb.jar
Bash

dep 패키지 이름은 control 파일의 package 필드에 정의한 내용이다. 우리는 ‘deb-test-app’ 으로 정의 했으므로 설치 여부를 확인해 보자.

ubuntu@ubuntu-jammy:/opt/myapp$ dpkg -l | grep deb-test-app
ii  deb-test-app    1.0.0       all          This is my Spring Boot Application package
Bash

control 파일에 정의한 메타 정보가 출력되는 것을 확인할 수 있다.

jdeb – Gradle

control, postinst, prerm, my-app.service 는 maven 과 동일한 경로에 있다고 가정한다.
gradle에서 jdeb 정의는 다음과 같다.

plugins {
    ...
    // bootJar 태스크
    id 'org.springframework.boot' version '3.5.7'
    id 'io.spring.dependency-management' version '1.1.7'
    ...
}

configurations{ jdeb }
dependencies { jdeb 'org.vafer:jdeb:1.14' }

task buildDeb {
    group = 'distributions'
    description = 'Creates a Debian package using jdeb'

    dependsOn 'bootJar'

    doLast {
        // 1. jdeb 라이브러리 로드
        ant.taskdef(name: 'deb', classname: 'org.vafer.jdeb.ant.DebAntTask', classpath: configurations.jdeb.asPath)

        // 2. bootJar로 생성된 파일 가져오기
        def jarFile = tasks.bootJar.archiveFile.get().asFile

        // 3. deb 패키지 생성 시작
        ant.deb(destfile: "${buildDir}/distributions/spring-boot-jdeb.deb", control: "src/main/resources/deb/control") {
            // jar 파일 배로 설정
            data(src: jarFile, type: "file") {
                mapper(type: "perm", prefix: "/opt/myapp")
            }

            // systemd 서비스 파일 배포 설정
            data(src: file("src/main/resources/deb/systemd/my-app.service"), type: "file") {
                mapper(type: "perm", prefix: "/lib/systemd/system", user: "root", group: "root", filemode: "644")
            }
        }
    }
}
Groovy

distributions > buildDeb 태스크가 생성된다. gradle의 경우에는 maven과 달리 bootJar 태스크를 통해 생성된 fat jar가 필요하다.(dependsOn ‘bootJar’)

IntelliJ를 사용한다면 우측 Gradle 메뉴 > Tasks > distributions > buildDeb 를 실행 한다.
CLI 명령은 아래 명령을 실행한다.

$> ./gradlew clean buildDeb
Bash

결과는 build/distributions에 deb 파일이 생성된다.


Java 개발이 주로 컨테이너(Docker) 환경에서 이루어지는 추세지만, 온프레미스 환경에 직접 애플리케이션을 배포 해야 하는 상황은 여전히 많을 것이라고 생각한다. 이 때 jdeb는 JAR 파일을 리눅스 패키지 배포의 표준인 deb 형태로 자동 변환하여 수동 배포 및 스크립트 관리의 부담을 줄여 줄 것이라고 기대한다.
배포의 안정성과 생산성을 동시에 잡고 싶다면 ‘jdeb’ 플러그인을 Maven, Ant, Gradle 빌드 프로세스에 도입해 보는 것을 강력끄하게 추천한다.

참고 사이트

https://www.baeldung.com/java-debian-deb-packages-jdeb