본문으로 건너뛰기
김신건의 로그

[Spring Boot] Deployment: 전통 WAR, Cloud, Systemd

· 수정 · 📖 약 2분 · 551자/단어 #spring-boot #deployment #war #cloud #heroku #cloud-foundry #systemd
Spring Boot Deployment, WAR deployment, Cloud Foundry Spring Boot, Heroku Spring Boot, AWS Beanstalk Spring Boot, systemd service Spring Boot, 스프링 부트 배포

정의

컨테이너 / K8s 외의 Spring Boot 배포 옵션:

  • Traditional WAR: 외부 Tomcat / Jetty (legacy 환경)
  • systemd service: VM / bare-metal
  • Cloud Foundry: PaaS, cf push
  • Heroku: PaaS, git push
  • AWS Elastic Beanstalk: AWS managed
  • AWS Lambda: serverless (native image 추천)
  • Azure App Service / Google App Engine

spring-boot-container-deployment 가 Docker/K8s 중심이라면 이 페이지는 그 외.

Traditional WAR (legacy)

기존 외부 Tomcat / WebSphere 인프라:

plugins {
    war
    id("org.springframework.boot") version "3.4.0"
}

dependencies {
    providedRuntime("org.springframework.boot:spring-boot-starter-tomcat")
    // 외부 Tomcat 이 제공
}

Application 클래스:

@SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder b) {
        return b.sources(MyApplication.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

SpringBootServletInitializer 가 servlet 3.0+ container 와 통합. 기존 Tomcat 의 webapps/ 에 war 복사.

대부분 신규 프로젝트는 executable jar + container 권장. WAR 은 legacy 호환 전용.

systemd Service (VM / bare-metal)

# /etc/systemd/system/my-app.service
[Unit]
Description=My Spring Boot App
After=network.target

[Service]
User=spring
Group=spring
WorkingDirectory=/opt/my-app
ExecStart=/usr/bin/java -jar /opt/my-app/app.jar
SuccessExitStatus=143
Restart=always
RestartSec=10
Environment="SPRING_PROFILES_ACTIVE=prod"
EnvironmentFile=/etc/my-app/env
StandardOutput=append:/var/log/my-app/app.log
StandardError=append:/var/log/my-app/error.log

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable my-app
sudo systemctl start my-app
sudo journalctl -u my-app -f

기능:

  • 자동 재시작 (Restart=always)
  • 로그 통합 (journald)
  • 환경 변수 분리
  • non-root 사용자

executable jar 를 service 로 등록한 가장 흔한 VM 배포 패턴.

자동 등록 (executable = true)

tasks.bootJar {
    archiveFileName.set("app.jar")
    manifest {
        attributes["Implementation-Title"] = "my-app"
    }
}

// Spring Boot 의 fully executable jar
springBoot {
    buildInfo()
}

// build.gradle.kts
tasks.bootJar {
    launchScript()    // jar 가 자체 실행 가능 → ./app.jar
}

./app.jar start | stop | status 가능. systemd 의 ExecStart=/opt/my-app/app.jar 만으로 충분.

Cloud Foundry

cf push my-app -p build/libs/app.jar

manifest.yml:

applications:
  - name: my-app
    memory: 1G
    instances: 3
    buildpack: java_buildpack
    env:
      SPRING_PROFILES_ACTIVE: prod
      JBP_CONFIG_OPEN_JDK_JRE: '{ jre: { version: 21.+ } }'
    services:
      - postgres-db
      - redis-cache
    routes:
      - route: api.example.com

Buildpack 이 JDK / app server 알아서 구성. Spring Boot 가 Cloud Foundry 환경 자동 감지 (spring-boot-cloudfoundry 의존성).

Heroku

# Procfile
web: java -Dserver.port=$PORT $JAVA_OPTS -jar build/libs/app.jar
git push heroku main

system.properties:

java.runtime.version=21

Heroku 가 빌드 + 실행. $PORT 환경 변수 자동 주입. JAVA_OPTS 로 메모리 / GC 튜닝.

application-heroku.yml:

server:
  port: ${PORT}
spring:
  datasource:
    url: ${JDBC_DATABASE_URL}

heroku addons:create heroku-postgresql 후 자동 환경 변수.

AWS Elastic Beanstalk

eb init -p java my-app
eb create my-app-prod
eb deploy

.ebextensions/01-options.config:

option_settings:
  aws:elasticbeanstalk:application:environment:
    SPRING_PROFILES_ACTIVE: prod
    JAVA_TOOL_OPTIONS: "-Xmx768m"
  aws:elasticbeanstalk:container:tomcat:jvmoptions:
    JVM Options: "-Xms256m"

WAR (Tomcat) 또는 jar (Procfile) 둘 다 지원. Auto Scaling Group + ELB + RDS 통합.

AWS Lambda (Native Image 권장)

GraalVM native:

plugins {
    id("org.graalvm.buildtools.native") version "0.10.6"
}

빠른 cold start (수십 ms). 또는 SnapStart (Java 21 Runtime):

@SpringBootApplication
public class LambdaHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
    private static final SpringApplicationBuilder builder = new SpringApplicationBuilder(LambdaHandler.class);
    private static ConfigurableApplicationContext ctx;

    @Override
    public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent event, Context context) {
        if (ctx == null) ctx = builder.web(WebApplicationType.NONE).run();
        // route to controller
        return new APIGatewayProxyResponseEvent().withStatusCode(200).withBody("hello");
    }
}

spring-cloud-function-adapter-aws 가 자동 통합:

implementation("org.springframework.cloud:spring-cloud-function-adapter-aws")

application.yml:

spring:
  main:
    web-application-type: none
  cloud:
    function:
      definition: handleRequest

서버리스에서 cold start 가 critical 이면 native image, 안 그러면 SnapStart 로도 충분.

Reverse Proxy + load balancer

운영 환경 일반 패턴:

Client → CDN / WAF → ALB / nginx → Spring Boot (Tomcat) → DB
                  ↑ TLS terminate    ↑ plain HTTP

Spring 의 forward-headers-strategy: native 가 X-Forwarded-* 처리:

server:
  forward-headers-strategy: native

또는 framework 가 신뢰하는 reverse proxy 명시:

server.tomcat:
  remoteip:
    remote-ip-header: X-Forwarded-For
    protocol-header: X-Forwarded-Proto
    internal-proxies: 10\\.\\d+\\.\\d+\\.\\d+

CI / CD Pipeline

전형적인 GitHub Actions:

name: deploy
on:
  push:
    branches: [main]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: 21
          distribution: temurin
      - uses: gradle/actions/setup-gradle@v4
      - run: ./gradlew test bootJar
      - run: ./gradlew bootBuildImage --publishImage \
          -Pjib.to.image=ghcr.io/${{ github.repository }}:${{ github.sha }}
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      - run: aws ecs update-service --cluster prod --service my-app --force-new-deployment

함정과 베스트 프랙티스

  • 신규 프로젝트는 jar + container: WAR 는 legacy
  • systemd 로 VM 배포: 자동 재시작 + 로그 통합
  • PaaS (Heroku, CF, App Engine): 빠른 시작, 비용 / vendor lock-in
  • reverse proxy 사용: TLS / WAF / rate limit / static cache 분리
  • forward-headers-strategy: native: 클라이언트 IP / scheme 정확 추출
  • buildpack / OCI image: Dockerfile 부담 ↓ (spring-boot-build-plugins)
  • graceful shutdown: 모든 환경에서 in-flight 요청 보호
  • native image (Lambda 등 cold start critical): 서버리스 적합
  • logs 는 stdout (12-factor): 파일 + rotation 은 OS 책임

관련 위키

이 글의 용어 (6개)
[Spring Boot] Build Plugins: Maven, Gradle, Executable JAR, OCI Imagespring
정의 Spring Boot 의 빌드 플러그인은 표준 jar 가 아닌 executable jar (fat jar / uber jar) 생성. 추가로 OCI 이미지, native i…
[Spring Boot] Docker / Kubernetes 배포spring
정의 Spring Boot 앱을 컨테이너로 배포. 두 가지 주류: - Docker / Docker Compose: 단일 호스트, 개발 + 소규모 운영 - Kubernetes: 멀…
[Spring Boot] Embedded Web Server 설정과 튜닝spring
정의 Spring Boot 의 starter 가 내장 서버를 함께 번들. 별도 WAR 배포 / 외부 Tomcat 불필요. 기본 매핑: - → Tomcat (기본) - → Nett…
[Spring Boot] Properties 외부화: PropertySource, Profile, @ConfigurationPropertiesspring
정의 Spring Boot 는 설정값을 15가지 소스에서 우선순위로 병합. 같은 키가 여러 곳에 있으면 우선순위 높은 게 이김. 코드 변경 없이 환경별 설정 가능. 핵심 소스 (…
[Spring Cloud] Eureka, Config, Gateway, OpenFeignspring
정의 Spring Cloud는 Spring Boot 기반 마이크로서비스 인프라 패턴 (service discovery, config server, gateway, circuit …
[Spring] Native Image: GraalVM, 빠른 시작spring
정의 Spring Native (3.0+)는 Spring Boot 앱을 GraalVM Native Image로 컴파일해 즉시 시작·낮은 메모리·작은 바이너리를 얻는다. JIT 없…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기