Spring Boot 프로젝트를 어느 정도 만들고 나니 다음 문제가 생겼다.
내 컴퓨터에서는 잘 되는데, 다른 사람은 사용할 수 없었다.

localhost에서 잘 동작하는 것과 인터넷에서 실제로 서비스되는 건 다른 이야기였다.
결국 배포를 해야 했다.
처음에는
Spring Boot 프로젝트니까 서버에 jar 파일 올리고 실행하면 되는 거 아닌가?
정도로 생각했다.
틀린 말은 아니지만, 직접 배포해보니 그 사이에 신경 써야 할 게 생각보다 많았다.
이번 글에서는 내가 Spring Boot 프로젝트를 Docker로 묶고 실제 배포까지 진행하면서 확인했던 내용을 정리해보려고 한다.
프로젝트 자체의 기능보다는 Spring Boot + Docker 배포 과정에 집중했다.
전체 흐름부터 보면
내가 진행한 흐름은 대략 이랬다.
Spring Boot
↓
Gradle Build
↓
Docker Image
↓
GitHub
↓
Railway
↓
외부 URL
개발 환경은 다음과 같았다.
Java 17
Spring Boot
Gradle
Windows
WSL Ubuntu
Docker
Git / GitHub
Railway
로컬에서는 IntelliJ와 Windows를 주로 사용했고, 배포를 준비하면서 WSL Ubuntu도 같이 사용했다.
1. 먼저 Spring Boot 빌드가 되는지 확인한다
Docker부터 만들기 전에 제일 먼저 확인해야 할 건 의외로 단순하다.
Docker 없이 프로젝트 자체가 정상적으로 빌드되는가?
Gradle 프로젝트라면 프로젝트 루트에서 다음 명령어를 실행할 수 있다.
Windows:
gradlew.bat clean build
Linux / WSL / macOS:
./gradlew clean build
Spring Boot 실행용 jar만 만들고 싶다면:
./gradlew clean bootJar
빌드가 정상적으로 끝나면 보통 다음 위치에 jar 파일이 만들어진다.
build/libs/
예를 들면:
build/libs/my-app-0.0.1-SNAPSHOT.jar
여기서부터 문제가 생긴다면 Docker 문제를 보기 전에 Spring Boot 빌드 오류부터 해결하는 게 낫다.
2. Java 버전도 확인했다
내 프로젝트는 Java 17을 사용하고 있었다.
Gradle에서도 toolchain을 지정해두는 편이 좋다.
예를 들면:
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
로컬에서는 Java 17인데 Docker 안에서는 Java 21이라든지, 반대로 더 낮은 버전을 사용하면 예상하지 못한 문제가 생길 수 있다.
그래서 Docker 이미지의 Java 버전도 프로젝트와 맞춰줬다.
3. Dockerfile 만들기
Spring Boot jar를 실행하는 가장 단순한 형태의 Dockerfile은 이런 식으로 만들 수 있다.
FROM eclipse-temurin:17-jdk
WORKDIR /app
COPY build/libs/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
하나씩 보면 별것 없다.
FROM
FROM eclipse-temurin:17-jdk
컨테이너 안에서 사용할 Java 환경이다.
내 프로젝트가 Java 17이었기 때문에 17을 사용했다.
WORKDIR
WORKDIR /app
컨테이너 안에서 작업할 디렉터리를 지정한다.
COPY
COPY build/libs/*.jar app.jar
Gradle이 만들어준 jar 파일을 Docker 이미지 안으로 복사한다.
EXPOSE
EXPOSE 8080
Spring Boot가 사용할 포트를 표시한다.
다만 여기서 하나 주의할 게 있다.
EXPOSE를 적었다고 자동으로 인터넷에서 8080 포트가 열리는 건 아니다.
Docker에게
이 애플리케이션은 이 포트를 사용합니다.
라고 알려주는 의미에 가깝다.
ENTRYPOINT
ENTRYPOINT ["java", "-jar", "app.jar"]
컨테이너가 실행될 때 Spring Boot 애플리케이션을 실행한다.
4. 여기서 처음 실수하기 쉬운 부분
위 Dockerfile에는 전제가 하나 있다.
build/libs/*.jar
파일이 이미 존재해야 한다.
즉 Docker 이미지부터 만들기 전에
./gradlew clean bootJar
를 실행해야 한다.
그렇지 않으면 Docker 빌드 과정에서 이런 류의 오류를 만날 수 있다.
COPY failed
또는
no source files were specified
결국 Docker가 복사할 jar 파일을 못 찾는 것이다.
그래서 순서는:
./gradlew clean bootJar
그다음:
docker build -t my-spring-app .
이렇게 잡는 게 이해하기 쉽다.
5. .dockerignore도 만들었다
처음에는 굳이 이게 필요한가 싶었다.
프로젝트 전체를 Docker에 넣으면 되는 거 아닌가 싶었다.
그런데 실제로는 Docker 이미지에 필요 없는 파일이 굉장히 많다.
그래서 프로젝트 루트에 .dockerignore를 만들었다.
예를 들면:
.git
.gradle
.idea
*.log
.env
프로젝트에 따라서는 다음도 제외할 수 있다.
node_modules
다만 주의할 점이 있다.
앞에서 만든 Dockerfile이
COPY build/libs/*.jar app.jar
방식이라면 build/ 폴더 전체를 .dockerignore에서 제외해버리면 안 된다.
Docker가 jar를 복사해야 하기 때문이다.
이 부분은 Dockerfile 방식에 따라 달라진다.
6. Docker 이미지를 직접 만들어본다
Dockerfile이 준비됐다면 프로젝트 루트에서 실행한다.
docker build -t my-spring-app .
마지막의 .도 중요하다.
현재 디렉터리를 Docker build context로 사용한다는 의미다.
정상적으로 끝나면 이미지가 생성된다.
확인은:
docker images
7. 컨테이너 실행해보기
이미지를 만들었다고 바로 배포로 넘어가지 않고 로컬에서 먼저 실행해봤다.
docker run -p 8080:8080 my-spring-app
여기서
8080:8080
은
내 컴퓨터 포트 : 컨테이너 포트
라고 보면 된다.
브라우저에서:
http://localhost:8080
으로 접속해서 정상적으로 Spring Boot가 뜨는지 확인한다.
8. 로컬에서 8080 포트 충돌도 있었다
개발하면서 이미 다른 프로세스가 8080을 사용하고 있는 경우도 있었다.
Spring Boot에서는 흔히 보는 문제다.
예를 들어:
Port 8080 was already in use
이럴 때는 기존 프로세스를 종료하거나 다른 포트를 사용하면 된다.
Spring Boot에서는:
server.port=8081
처럼 바꿀 수 있다.
Docker 실행에서도:
docker run -p 8081:8080 my-spring-app
처럼 사용할 수 있다.
그러면
localhost:8081
로 접속하지만 컨테이너 안에서는 여전히 8080을 사용한다.
이걸 처음 보면 조금 헷갈린다.
8081 → 내 PC
8080 → Docker Container
라고 생각하면 편했다.
Spring Boot 자체에서 Port 8080 was already in use 오류가 발생했을 때 해결한 과정은 별도 글로 정리했다.
→ Spring Boot 8080 포트 충돌 해결 — Port 8080 was already in use 오류
9. Windows에서 WSL로 넘어가니 Git이 이상해졌다
배포 준비를 하면서 Windows에서 작업하던 프로젝트를 WSL Ubuntu에서 열었다.
그리고 평소처럼 실행했다.
git status
그런데 갑자기 내가 건드리지 않은 수많은 파일이 전부 modified로 표시됐다.
순간 프로젝트를 잘못 건드린 줄 알았다.
원인은 코드 변경이 아니라 Windows와 Linux의 줄바꿈 차이였다.
Windows에서는 주로 CRLF, Linux에서는 LF를 사용하는데 Git에서는 이 차이 때문에 파일 전체가 수정된 것처럼 보일 수 있다.
실제 변경된 파일만 확인하기 위해 나는 다음 명령어도 사용했다.
git diff --ignore-space-at-eol --stat
확인해보니 실제로 수정한 파일은 .dockerignore와 배포 관련 설정 정도였다.
Docker 자체의 문제는 아니지만 Windows + WSL + Git 조합으로 작업한다면 한 번쯤 만날 수 있는 문제였다.
이 문제는 별도 글에서 조금 더 자세히 정리했다.
→ Git status에서 모든 파일이 modified로 뜰 때 — WSL에서 만난 CRLF/LF 문제
10. 배포 환경에서는 PORT 처리를 확인해야 한다
로컬에서는 보통 다음처럼 고정된 포트를 사용해도 큰 문제가 없다.
server.port=8080
그런데 Railway 같은 클라우드 배포 환경에서는 실행할 포트를 환경변수로 전달한다.
그래서 Spring Boot에서는 다음과 같이 설정했다.
server.port=${PORT:8080}
의미는 단순하다.
- PORT 환경변수가 있으면 해당 값을 사용한다.
- 환경변수가 없다면 기본값으로 8080을 사용한다.
덕분에 로컬에서는 8080으로 실행하고, Railway에서는 플랫폼이 전달한 포트를 사용할 수 있었다.
처음에는 단순히 8080으로 고정하면 되는 줄 알았는데 배포 환경에서는 이 차이가 꽤 중요했다.
Railway에서 왜 이런 설정이 필요한지는 별도 글에서 더 자세히 정리했다.
→ Railway Spring Boot PORT 설정 — server.port=${PORT:8080}를 쓴 이유
Docker의 8081:8080처럼 호스트 포트와 컨테이너 포트가 헷갈린다면 이 글도 같이 보면 이해하기 쉽다.
→ Docker localhost 연결 안 될 때 — 8081:8080 포트 매핑부터 확인했다
11. GitHub에 올리기 전에 확인할 것
Docker 관련 파일까지 준비한 뒤 GitHub에 올렸다.
git add .
git commit -m "feat: add docker deployment config"
git push origin main
다만 바로 push하기 전에 한 번 확인하는 편이 좋았다.
git status
특히 .env, API Key, 비밀번호 같은 민감정보가 Git에 포함되지 않았는지 확인해야 한다.
.env는 최소한 .gitignore에 포함하고, Docker 이미지에 들어갈 필요가 없다면 .dockerignore에서도 제외하는 편이 좋다.
예를 들면:
.env
*.log
.idea
.gradle
로컬에서는 당연하게 존재하던 설정 파일이 GitHub에 올라가는 순간 외부에 노출될 수 있기 때문에 배포 직전에는 한 번 더 확인하는 습관이 필요했다.
나중에는 이런 항목들을 포함해서 Spring Boot MVP를 배포하기 전에 확인할 내용을 별도의 체크리스트로도 정리했다.
12. Railway와 GitHub 연결
나는 실제 배포에는 Railway를 사용했다.
전체 흐름은 대략 이랬다.
GitHub Repository
↓
Railway
↓
Build
↓
Docker Image
↓
Deploy
↓
Public URL
GitHub 저장소를 Railway와 연결하면 Railway가 소스 코드를 가져와 빌드하고 배포한다.
프로젝트에 Dockerfile이 있다면 이를 기준으로 이미지를 만들 수 있다.
처음에는 화면에 Build, Deploy 같은 상태가 뜨는 걸 보면서 그냥 기다렸다.
로컬 개발과 가장 다르게 느껴졌던 순간이었다.
내 컴퓨터에서 내가 직접 명령어를 입력하는 게 아니라,
다른 서버가 내 코드를 가져가서 빌드하고 있었다.
이때부터 배포라는 게 조금 더 현실적으로 느껴졌다.
실제로 이 방식으로 Railway에 배포했던 Security Lens의 구조와 구현 과정은 GitHub Case Study에 따로 정리해두었다.
원본 소스 코드는 공개하지 않았고, 프로젝트 구조와 기술적 의사결정을 정리한 저장소다.
→ Security Lens — Spring Boot Privacy Risk Analyzer Case Study
13. Build 성공이 Deploy 성공은 아니었다
여기서 하나 구분해서 보는 게 좋았다.
Build 성공
과
서비스 정상 실행
은 같은 말이 아니다.
Docker 이미지가 정상적으로 만들어졌더라도 Spring Boot 애플리케이션 실행 과정에서 오류가 발생할 수 있다.
예를 들면:
- 환경변수 누락
- DB 연결 실패
- 포트 설정 문제
- Java 실행 오류
- ApplicationContext 시작 실패
같은 문제들이다.
그래서 Railway 같은 배포 플랫폼에서는 Build 로그와 Runtime 로그를 따로 확인하는 편이 좋았다.
예를 들어 Docker 이미지 생성 단계에서 실패했다면 Build 쪽을 먼저 봐야 하고,
이미지는 정상적으로 만들어졌는데 서비스가 뜨지 않는다면 Spring Boot 실행 로그나 환경변수 설정을 확인해야 한다.
단순히 초록색 체크 하나가 떴다고 배포가 끝난 것은 아니었다.
14. localhost가 아닌 URL이 처음 열렸다
배포가 정상적으로 끝난 뒤 외부 URL이 생겼다.
그동안은 계속:
localhost
였다.
이번에는 실제 인터넷 주소를 통해 접속할 수 있었다.
PC에서도 열어보고,
휴대폰에서도 열어봤다.
내 컴퓨터에서 Spring Boot를 실행하지 않아도 화면이 나왔다.
이때 처음으로 Docker를 왜 사용하는지 조금 실감했다.
Docker 자체를 공부할 때는 이미지, 컨테이너, 레이어 같은 용어부터 나오니까 꽤 추상적으로 느껴졌다.
그런데
내 컴퓨터가 아닌 곳에서 내가 만든 Spring Boot 프로젝트를 실행해야 한다.
라는 문제가 먼저 생기고 나니 Docker가 훨씬 이해하기 쉬웠다.
Docker를 처음 제대로 사용하면서 느꼈던 이야기는 RE:개발자에도 따로 적어두었다.
→ 서버에 올리면 끝인 줄 알았다 — Docker를 처음 제대로 써봤다 | RE:개발자 #11
Spring Boot Docker 배포하면서 확인하면 좋은 순서
다시 한다면 나는 아래 순서로 확인할 것 같다.
1. Spring Boot가 로컬에서 실행되는가?
↓
2. Gradle build가 성공하는가?
↓
3. jar가 정상 생성되는가?
↓
4. Java 버전이 맞는가?
↓
5. Docker build가 되는가?
↓
6. Docker run으로 로컬 실행되는가?
↓
7. PORT 설정이 배포 환경에 맞는가?
↓
8. 환경변수가 빠진 건 없는가?
↓
9. GitHub push
↓
10. Railway Build Log 확인
↓
11. Runtime Log 확인
↓
12. 외부 URL 접속
이 순서대로 보면 문제가 생겼을 때 확인할 범위를 상당히 줄일 수 있다.
Docker build부터 안 되는데 Railway 설정을 계속 보고 있을 필요는 없다.
반대로 Docker에서는 정상적으로 실행되는데 배포 환경에서만 문제가 생긴다면 포트나 환경변수처럼 로컬과 서버 환경의 차이를 먼저 의심해볼 수 있다.
최소 구성만 다시 정리하면
Dockerfile
FROM eclipse-temurin:17-jdk
WORKDIR /app
COPY build/libs/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
application.properties
server.port=${PORT:8080}
.dockerignore
.git
.gradle
.idea
*.log
.env
Spring Boot jar 빌드
./gradlew clean bootJar
Docker 이미지 생성
docker build -t my-spring-app .
컨테이너 실행
docker run -p 8080:8080 my-spring-app
직접 해보니 Docker보다 더 중요했던 것
배포하기 전에는 Docker가 가장 어려운 부분일 거라고 생각했다.
막상 해보니 Dockerfile 자체는 생각보다 길지 않았다.
오히려 계속 문제를 만들었던 건 환경의 차이였다.
Windows와 Linux의 줄바꿈이 달랐고,
로컬과 Docker의 포트가 달랐고,
내 컴퓨터에는 당연히 있던 환경이 서버에는 없었다.
결국 배포라는 건 단순히
코드를 서버에 복사하는 것
보다는
내 컴퓨터가 없어도 이 프로그램이 실행될 수 있는 환경을 만드는 것
에 가까웠다.
Docker를 직접 사용하고 나서야 예전에 들었던
내 컴퓨터에서는 되는데요?
라는 말이 왜 개발자 농담처럼 쓰이는지 조금 더 이해하게 됐다.
이번에는 정말 내 컴퓨터가 아니어도 됐다.
'개발 노트' 카테고리의 다른 글
| Spring Boot 8080 포트 충돌 해결 — Port 8080 was already in use 오류 (0) | 2026.09.24 |
|---|---|
| 개발자 노트북 RAM 32GB·SSD 1TB면 충분할까? 그램 17 고르며 고민한 기준 (0) | 2026.09.23 |
| LG 그램 Pro 17 개발용으로 괜찮을까? AI·Ollama 때문에 RTX 5050까지 고민했다 (0) | 2026.09.22 |
| 2026 개발자 노트북 추천 — 입문용·실무용·휴대용으로 나눠봤다 (0) | 2026.09.22 |
| Git status에서 모든 파일이 modified로 뜰 때 — WSL에서 만난 CRLF/LF 문제 (0) | 2026.09.20 |