infra
Platform

모듈 맵

[Infra Ops] Maven/Gradle/npm 빌드와 산출물 관리

0 / 52 완료

펼치기
0 / 52 완료0%

Infra-ops · 35 / 52

[Infra Ops] Maven/Gradle/npm 빌드와 산출물 관리

Maven pom.xml, Gradle build.gradle, npm/pnpm 빌드, 의존성 충돌 해결, 빌드 산출물 관리까지 — 인프라 엔지니어가 배포를 위해 알아야 할 빌드 실무

🚨INCIDENT ALERT
HIGH

배포 요청이 왔습니다. 개발팀이 "배포 해주세요"라며 GitLab 저장소 링크만 던져줬습니다. 저장소를 받아보니 Maven 프로젝트와 React 프론트엔드가 섞여 있습니다. WAR 파일은 어떻게 만드는지, 운영 프로파일로 빌드해야 하는지, npm build는 뭘로 하는지 — 처음 보는 프로젝트를 빌드해야 하는 상황입니다.

이 모듈은 Java 빌드 도구(Maven/Gradle)와 프론트엔드 빌드 도구(npm/pnpm)의 실무 사용법을 다룹니다. 의존성 충돌 해결과 산출물 확인까지 포함합니다.

이번 챕터에서 배울 것
  • 1Maven 라이프사이클(clean/compile/test/package)과 운영 빌드 명령을 실행할 수 있다
  • 2Gradle ./gradlew clean build 로 빌드하고 산출물 위치를 확인할 수 있다
  • 3npm ci와 npm install의 차이를 설명하고 CI 환경에서 올바른 명령을 선택할 수 있다
  • 4mvn dependency:tree와 ./gradlew dependencies로 의존성 충돌을 확인할 수 있다
  • 5빌드 실패 원인을 컴파일 오류/테스트 실패/의존성 오류로 분류할 수 있다

Maven — Java 빌드 표준

💡개념

Maven 라이프사이클과 pom.xml

Maven은 Java 프로젝트 빌드의 표준입니다. 빌드 단계(라이프사이클)가 고정되어 있어 처음 보는 프로젝트도 패턴이 동일합니다. mvn package는 항상 target/ 디렉터리에 WAR/JAR를 만듭니다.

Maven 라이프사이클과 pom.xml

긴급 배포 요청이 왔습니다. 저장소를 클론했는데 mvn package를 실행하면 테스트가 30분씩 걸리고, 운영 프로파일로 빌드했는지 확인할 방법도 모릅니다. 빌드 로그 어딘가에 에러가 있는데 BUILD FAILURE 한 줄만 보입니다. 빌드 도구를 모르면 처음 보는 프로젝트를 배포할 때 판단 근거가 없습니다. Maven 라이프사이클을 이해하면 어느 단계에서 실패했는지, 테스트를 건너뛰려면 어떤 옵션을 쓰는지, 산출물이 어디 생기는지를 즉시 파악할 수 있습니다.

Maven 라이프사이클 (순서대로 실행):

clean → validate → compile → test → package → install → deploy
단계설명
cleantarget/ 디렉터리 삭제 (이전 빌드 산출물 제거)
compilesrc/main/java 소스 컴파일
testsrc/test/java 테스트 실행
packageWAR 또는 JAR 생성 → target/ 에 저장
install로컬 Maven 저장소(~/.m2/repository)에 설치
로컬 터미널
# 운영 빌드 표준 명령 (테스트는 CI에서 별도 실행한다고 가정)
mvn clean package -DskipTests -Pprod

# 옵션 해설
# clean       — 이전 빌드 산출물 제거 (필수)
# package     — compile → test(skip) → package 단계까지
# -DskipTests — 테스트 실행 건너뜀
# -Pprod      — prod 빌드 프로파일 활성화 (pom.xml에 정의된 경우)

# 빌드 산출물 확인
ls -lh target/*.war
ls -lh target/*.jar

pom.xml 핵심 구조:

XML
<!-- pom.xml -->
<project>
  <groupId>com.example</groupId>
  <artifactId>myapp</artifactId>
  <version>1.2.0</version>
  <packaging>war</packaging>   <!-- war 또는 jar -->

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>3.1.0</version>
    </dependency>
  </dependencies>

  <!-- 빌드 프로파일 -->
  <profiles>
    <profile>
      <id>prod</id>
      <properties>
        <spring.profiles.active>prod</spring.profiles.active>
      </properties>
    </profile>
  </profiles>
</project>

Gradle — 빠른 빌드와 유연한 DSL

💡개념

Gradle 빌드와 ./gradlew

Gradle은 Maven보다 빌드 속도가 빠릅니다. 변경된 파일만 재빌드하는 증분 빌드가 기본 동작합니다. ./gradlew(Gradle Wrapper)를 쓰면 Gradle을 별도로 설치하지 않아도 됩니다.

로컬 터미널
# Gradle Wrapper 빌드 (권장 — 프로젝트 내 버전 사용)
./gradlew clean build

# 테스트 건너뛰기
./gradlew clean build -x test

# 특정 태스크만 실행
./gradlew bootJar    # Spring Boot JAR 생성
./gradlew bootWar    # Spring Boot WAR 생성

# 산출물 위치 확인
ls -lh build/libs/
GROOVY
// build.gradle (기본 구조)
plugins {
    id 'org.springframework.boot' version '3.1.0'
    id 'java'
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

// 프로파일별 빌드 설정
bootJar {
    archiveFileName = "myapp-${version}.jar"
}

npm/pnpm — 프론트엔드 빌드

💡개념

npm 빌드와 산출물 관리

React/Vue 같은 프론트엔드 프로젝트는 npm으로 빌드합니다. 빌드 결과물은 dist/ 또는 build/ 디렉터리에 정적 파일로 생성됩니다. 이 파일들을 Nginx로 서빙합니다.

로컬 터미널
# 의존성 설치 (CI/CD 환경에서는 반드시 ci 사용)
npm ci                # package-lock.json 기준, 재현 가능한 설치
npm install           # 개발 로컬 환경에서 신규 패키지 추가 시

# 운영 빌드
npm run build
# 또는 커스텀 스크립트가 있으면
npm run build:prod

# 빌드 산출물 확인
ls -lh dist/
# 또는
ls -lh build/

# pnpm 사용 시
pnpm install --frozen-lockfile   # CI 환경
pnpm run build

package.json 스크립트 확인:

로컬 터미널
# 어떤 빌드 스크립트가 있는지 먼저 확인
cat package.json | python3 -m json.tool | grep -A20 '"scripts"'
JSON
{
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "build:prod": "REACT_APP_ENV=production react-scripts build",
    "test": "react-scripts test"
  }
}

CI 빌드 파이프라인 — Maven/Gradle 비교

실습

1Maven 운영 빌드 실행

실제 Maven 프로젝트가 있는 디렉터리에서 실행합니다. 빌드 로그가 길게 흐르고 마지막에 BUILD SUCCESS가 나오면 성공입니다.

로컬 터미널
# 빌드 전 — 이전 산출물 있는지 확인
ls target/ 2>/dev/null || echo "target 없음"

# 운영 빌드
mvn clean package -DskipTests -Pprod

# 빌드 후 산출물 확인
ls -lh target/*.war target/*.jar 2>/dev/null

# 빌드 시간 확인 (출력 마지막 줄)
# [INFO] BUILD SUCCESS
# [INFO] Total time: 42.318 s
OUTPUT
[INFO] BUILD SUCCESS
[INFO] Total time: 42.318 s
[INFO] Finished at: 2026-05-30T10:45:22+09:00
mvn clean package -DskipTests -Pprod
🔍실행 후 확인할 것
  • mvn clean package 마지막 줄이 BUILD SUCCESS 인가 (BUILD FAILURE 면 로그 스크롤 업해서 ERROR 줄 찾기)
  • ls -lh target/*.war 산출물 크기가 비정상적으로 작지 않은가 (1KB 이하면 빌드 문제)
  • -Pprod 옵션이 있을 경우 빌드 로그에 'The following profiles are active: prod' 가 나타나는가
  • 빌드 산출물 파일명에 버전 번호가 포함돼 있는가 (예: myapp-1.2.0.war)
2의존성 충돌 확인

의존성 충돌은 빌드가 성공해도 런타임에 ClassNotFoundException이나 NoSuchMethodError를 일으킬 수 있습니다. 빌드 성공 후에도 충돌 여부를 확인하는 것이 좋습니다.

로컬 터미널
# Maven 의존성 트리 전체 출력
mvn dependency:tree

# 충돌/제외된 의존성만 필터링
mvn dependency:tree | grep -E "omitted for conflict|omitted for duplicate"

# Gradle 의존성 확인
./gradlew dependencies | grep -E "conflict|FAILED"

# 특정 라이브러리 버전이 어디서 왔는지 확인
mvn dependency:tree -Dincludes=org.slf4j:slf4j-api
OUTPUT
[INFO] +- org.springframework.boot:spring-boot-starter-logging:jar:3.1.0:compile
[INFO] |  \- org.slf4j:slf4j-api:jar:2.0.7:compile
[INFO] \- com.example:legacy-module:jar:1.0.0:compile
[INFO]    \- (org.slf4j:slf4j-api:jar:1.7.36:compile - omitted for conflict with 2.0.7)
mvn dependency:tree | grep -E 'CONFLICT|WARNING|omitted'
🔍실행 후 확인할 것
  • 의존성 트리에 'omitted for conflict' 항목이 있는가 — 있으면 버전 차이 확인
  • 충돌이 있다면 상위 버전이 채택됐는지 확인 (대부분 Maven이 자동 해결함)
  • 충돌 라이브러리가 런타임 핵심 라이브러리(slf4j, jackson 등)라면 pom.xml exclusion 적용 검토
  • ./gradlew dependencies 출력에 FAILED 항목이 없는가

트러블슈팅

원인: 회사 내부 라이브러리를 사내 Nexus(내부 Maven 저장소)에서 받아야 하는데, Nexus 주소 설정이 없거나 네트워크가 차단됐습니다. 폐쇄망 서버에서 자주 발생합니다.

로컬 터미널
# 1. Maven 저장소 설정 확인
cat ~/.m2/settings.xml | grep -A10 "<mirror>"
# 또는 프로젝트 pom.xml 저장소 확인
grep -A5 "<repository>" pom.xml

# 2. Nexus 서버 접근 가능 여부 확인
curl -v http://nexus.internal.company.com:8081/repository/maven-public/
# 또는
wget -q --spider http://nexus.internal.company.com:8081/

# 3. 폐쇄망 환경: ~/.m2/settings.xml 에 미러 등록
# <settings>
#   <mirrors>
#     <mirror>
#       <id>internal-nexus</id>
#       <mirrorOf>*</mirrorOf>
#       <url>http://nexus.internal.company.com:8081/repository/maven-public/</url>
#     </mirror>
#   </mirrors>
# </settings>

# 4. 로컬 캐시 강제 갱신 (캐시 오염 의심 시)
mvn clean package -DskipTests -U
# -U : 최신 버전 강제 확인 (스냅샷 포함)

원인: 서로 다른 라이브러리에 동일한 클래스가 들어 있습니다. 의존성 버전 충돌로 같은 클래스가 클래스패스에 두 번 올라간 상태입니다.

로컬 터미널
# 1. 충돌 클래스가 어느 JAR에서 오는지 확인
mvn dependency:tree -Dverbose | grep "StringUtils"

# 2. 특정 라이브러리 의존성 경로 확인
mvn dependency:tree -Dincludes=com.example:legacy-util

# 3. pom.xml에서 exclusion으로 중복 제거
# <dependency>
#   <groupId>com.example</groupId>
#   <artifactId>framework-core</artifactId>
#   <version>2.0.0</version>
#   <exclusions>
#     <exclusion>
#       <groupId>com.example</groupId>
#       <artifactId>legacy-util</artifactId>
#     </exclusion>
#   </exclusions>
# </dependency>

# 4. 제거 후 재빌드
mvn clean package -DskipTests

# Gradle에서 같은 문제
./gradlew dependencies | grep "StringUtils"
# build.gradle에서 강제 버전 지정
# configurations.all {
#     resolutionStrategy.force 'com.example:util:2.0.0'
# }
💼
실무 맥락
현업 패턴

실제 업무에서 이 지식이 쓰이는 상황:

인프라 엔지니어가 빌드 도구를 직접 다루는 장면 세 가지입니다.

1. 배포 파이프라인 구성 시:

로컬 터미널
# Jenkins/GitLab CI에서 빌드 스테이지 예시
# Stage: Build
mvn clean package -DskipTests -Pprod -B
# -B : 배치 모드 (프롬프트 없음, CI 필수)

# 산출물 경로 확인 후 배포 단계로 전달
ARTIFACT=$(ls target/*.war | head -1)
echo "빌드 산출물: $ARTIFACT"
ls -lh "$ARTIFACT"

2. 빌드 실패 원인 분류 — 첫 30초 판단:

로컬 터미널
# Maven 빌드 실패 시 원인 찾기
mvn clean package -DskipTests 2>&1 | grep -E "^\\[ERROR\\]" | head -20

# 컴파일 오류
# [ERROR] .../UserService.java:[45,12] cannot find symbol
# → 소스 코드 문제, 개발팀 전달

# 의존성 오류
# [ERROR] Cannot access central (https://repo.maven.apache.org) ...
# → 네트워크/Nexus 문제, 인프라팀 처리

# 테스트 실패 (테스트 포함 빌드 시)
# [ERROR] Tests run: 10, Failures: 2, Errors: 0
# → 개발팀 전달 (인프라 문제 아님)

3. 빌드 산출물 버전 추적:

로컬 터미널
# WAR/JAR 내부에서 버전 확인
unzip -p target/myapp.war META-INF/MANIFEST.MF
# 또는
jar tf target/myapp.jar | grep -i "manifest\|pom.properties"
unzip -p target/myapp.jar META-INF/maven/com.example/myapp/pom.properties

빌드 도구를 이해하면 배포 파이프라인 구성과 빌드 실패 원인 분류를 개발팀 없이 처리할 수 있습니다. 다음 모듈에서는 이렇게 만든 산출물을 운영 DB에 반영하는 스키마 변경 절차를 다룹니다.

지식 확인

퀴즈 — 4문제

Q1

Maven과 Gradle의 실질적인 차이로 올바른 것은?

Q2

mvn clean package -DskipTests 에서 -DskipTests를 붙이는 실무적 이유는?

Q3

npm ci와 npm install의 차이로 올바른 것은?

Q4

빌드 프로파일(build profile)을 사용하는 이유는?

0 / 4 답변

🧪 실습으로 확인하기

Nginx 설치 및 기동

초급

Linux 서버에 Nginx를 설치하고 systemd 서비스로 등록하여 80포트에서 응답하는 상태까지 만든다.

30📋 3단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

infra-ops중급 · 65
[Infra Ops] WAR/JAR/정적파일 배포와 배포 스크립트 작성
인프라 서비스 운영 트랙 계속
linux입문 · 30
[Linux] 개발자가 왜 리눅스 서버와 커맨드라인을 반드시 배워야 하는가
Linux 트랙 시작점