티스토리 뷰
회사에서 다 같이 devcontainer를 스터디하기로 해서 오늘은 혼자 간단한(?) 서버에 DB가 있는 컨테이너를 올려서 테스트 해보기로 했다.
우선 환경 정보는 다음과 같다.
- Mac (Intel)
- Java 21
- SpingBoot 3.4.0
- Gradle
- MySQL
Dev Container 란?
Development containers
A development container (or dev container for short) allows you to use a container as a full-featured development environment. It can be used to run an application, to separate tools, libraries, or runtimes needed for working with a codebase, and to aid in
containers.dev
- Dev Container는 Docker 컨테이너 안에 개발 환경을 구성하여 로컬 환경을 오염시키지 않고, 팀원 간 동일한 개발 환경을 유지할 수 있도록 한다.
- Docker Compose와 연결하면 DB, API 서버 등도 함께 컨테이너로 실행 가능.
📌 Dev Container vs docker-compose.yml
1️⃣ docker-compose.yml 개념 및 용도
✅ 개념
- 여러 Docker 컨테이너를 정의하고 동시에 실행하기 위한 도구.
- 서비스 정의, 네트워크 구성, 볼륨 공유 등을 한 곳에서 설정 가능.
✅ 용도
- 멀티 컨테이너 애플리케이션 구성
- 예: API 서버, DB(MySQL, PostgreSQL 등), Redis, Nginx 등을 하나의 구성 파일에서 정의하고 관리.
- 로컬 개발 및 테스트 환경 설정
- 로컬에서 쉽게 테스트 환경 구축 및 로컬 네트워크 구성.
- 배포 전 동일한 환경 구축
- 개발, 테스트, 프로덕션 환경을 동일하게 맞추기 쉽게 해줌.
✅ 주요 명령어
- docker-compose up -d : 백그라운드에서 모든 서비스 실행
- docker-compose down : 모든 서비스 중단 및 네트워크 정리
- docker-compose build : Dockerfile 기반으로 이미지 빌드
2️⃣ devcontainer 개념 및 용도
✅ 개념
- 개발 환경을 코드로 정의하는 도구. 주로 VS Code 및 IntelliJ와 같은 IDE에서 사용.
- 개발 환경 통일성을 유지하고, 개발자가 일관된 환경에서 작업할 수 있게 함.
✅ 용도
- 일관된 개발 환경 제공
- 팀원들이 동일한 개발 환경에서 작업할 수 있게 해줌.
- IDE와 통합된 개발 경험
- VS Code 또는 IntelliJ에서 직접 Dev Container를 열고 개발 가능.
- 추가적인 개발 도구 설치
- 디버거, Linter, 포맷터 등을 컨테이너 내부에 미리 설치 가능.
3️⃣ docker-compose와 devcontainer의 차이점
특징 | docker-compose | devcontainer |
주요 목적 | 여러 서비스 실행 및 관리 | 개발 환경 설정 및 통합 |
설정 파일 | docker-compose.yml | devcontainer.json + docker-compose.yml |
실행 방법 | CLI 명령어 사용 | IDE 통합 실행 (VS Code, IntelliJ) |
주요 사용 사례 | 멀티 컨테이너 배포 및 로컬 테스트 | 일관된 개발 환경 제공 및 IDE 통합 |
네트워크 구성 | 독립적인 Docker 네트워크 생성 | docker-compose와 동일한 네트워크 사용 |
개발 도구 통합 | 기본적으로 없음 | IDE 확장, 디버거 등 통합 가능 |
배포 활용 | 배포 및 여러 환경 지원 | 주로 로컬 개발 |
현재 디렉토리 구조
starter-server/
│── gradlew
│── gradlew.bat
│── gradle/ # Gradle 관련 폴더
│── starter-api/ # Spring Boot API 모듈
│ ├── .devcontainer/ # Dev Container 설정 폴더 (여기에 있음!)
│ │ ├── devcontainer.json
│ │ ├── docker-compose.yml
│ │ ├── Dockerfile
│── starter-common/ # 공통 모듈 (domain, entity, db 설정)
│── build.gradle.kts
│── settings.gradle.kts
1) .devcontainer 디렉토리 생성
먼저 starter-server 내에 다른 모듈이 들어올 수 있고 해당 모듈들을 각각 컨테이너로 생성할 가능성이 높기 때문에 우선 starter-api 내부에 devcontainer를 위한 디렉토리를 생성했다.
2) devcontianer.json, docker-compose.yml, Dockerfile 생성
그리고 해당 디렉토리 아래 다음과 같이 3가지 파일을 추가하였다.
devcontainer.json
{
"name": "API Dev Container",
"dockerComposeFile": "docker-compose.yml",
"service": "api",
"workspaceFolder": "/workspace",
"forwardPorts": [8080, 3306],
"postCreateCommand": "./gradlew build"
}
docker-compose.yml
version: "3.8"
services:
api:
build:
context: ../.. # starter-server를 기준으로 빌드
dockerfile: starter-api/.devcontainer/Dockerfile
container_name: dev-container-api
ports:
- "8080:8080"
environment:
SPRING_PROFILES_ACTIVE: default,dev
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/mydb
SPRING_DATASOURCE_USERNAME: user
SPRING_DATASOURCE_PASSWORD: password
depends_on:
- db
volumes:
- ../../starter-api/src:/workspace/starter-api/src
- ../../gradlew:/workspace/gradlew
db:
image: mysql:8.0
container_name: dev-container-db
restart: always
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: mydb
MYSQL_USER: user
MYSQL_PASSWORD: password
volumes:
- mysql-data:/var/lib/mysql
volumes:
mysql-data:
Dockerfile
# ✅ JDK 21 사용 (Debian 기반)
FROM eclipse-temurin:21-jdk
WORKDIR /workspace
# ✅ `findutils` (xargs 포함) 설치 (Debian 기반)
RUN apt-get update && apt-get install -y findutils
# ✅ `xargs`가 설치되었는지 확인 (빌드 로그에 출력)
RUN xargs --version || (echo "⚠️ xargs is NOT available!" && exit 1)
# ✅ starter-server 전체 복사 (starter-api + starter-common 포함)
COPY ../../ /workspace/
# ✅ 실행 권한 부여 (gradlew 실행 가능하도록)
RUN chmod +x /workspace/gradlew
# ✅ Gradle 빌드 실행 (빌드 중 에러 발생 시 로그 확인)
RUN /workspace/gradlew :starter-api:build --stacktrace
# ✅ JAR 파일 이동 및 존재 여부 확인
RUN ls -l /workspace/starter-api/build/libs/
RUN cp /workspace/starter-api/build/libs/starter-api-0.0.1-SNAPSHOT.jar /workspace/starter-api.jar
# ✅ JAR 파일 실행 권한 부여
RUN chmod +x /workspace/starter-api.jar
# ✅ 실행할 JAR 파일 지정
CMD ["java", "-jar", "/workspace/starter-api.jar"]
3-1) 명령어로 실행하기
그리고 .devcontainer 디렉토리로 이동한 뒤 다음 명령어를 통해 docker container를 생성하고 실행할 수 있다.
$ docker-compose build --no-cache # 빌드 (이미지 생성)
$ docker-compose up -d # 실행 (컨테이너 실행)
$ docker-compose down # 종료 (컨테이너 종료 및 삭제)
3-2) IntelliJ 에서 실행하기
devcontainer 를 사용하는 것은 IDE를 통해 편리한 실행을 위한 것이므로 IntelliJ에서 조금 더 쉽게 실행이 가능하다.
1) 플러그인으로 docker를 설치하고
2) Setting → Build, Execution, Deployment → Docker 에서 +로 기본 추가
3) 우측 상단 Run/Dedug Configuration 에서 edit 하고 docker 추가 후 위에서 작성한 docker-compose.yml 파일 위치를 설정
4) 추가한 Docker Configuration 을 선택하고 Run(초록색 화살표)을 클릭하여 build → up 가능
docker 실행에 대해서는 이것말고 View → Tool Windows → Docker 에서 할 수 있다고 하는데 Docker를 찾을 수가 없어서 위 방법으로 우선 하고 다시 찾게 되면 실행 방법을 업데이트 할 예정이다. 😂
이슈
진행하면서 여러가지 이슈가 있었다... docker도 멀티모듈도 모두 낯설어서 그런가...ㅠㅠ
1. JDK 버전 및 xargs 이슈
먼저 Dockerfile에서 FROM openjdk:21-jdk 를 사용하려고 했다. 하지만 build 과정에서 xargs is not available jdk 21 이라는 에러가 발생했다...
찾아보니 그래들 7.5 버전부터 xargs 에 대한 명시적인 체크가 이루어진다고 한다. 그리고 xargs의 존재(설치) 여부는 POSIX standard의 일부이기 때문에 없는 것은 극히 드문 경우라고 한다.
그렇지만 openjdk의 14버전 이상의 도커 이미지는 xargs를 포함하지 않는데 그 이유는 사용자제(deprecated)되었기 때문이라고 한다.
그래서 어쩔 수 없이 다른 21을 사용하고 xargs를 설치하기로 했다... 그래서 아래와 같이 Dockerfile이 수정되었다.
[Before]
# ✅ JDK 21 사용 (Debian 기반)
FROM openjdk:21-jdk
WORKDIR /workspace
[After]
# ✅ JDK 21 사용 (Debian 기반)
FROM eclipse-temurin:21-jdk
WORKDIR /workspace
# ✅ `findutils` (xargs 포함) 설치 (Debian 기반)
RUN apt-get update && apt-get install -y findutils
# ✅ `xargs`가 설치되었는지 확인 (빌드 로그에 출력)
RUN xargs --version || (echo "⚠️ xargs is NOT available!" && exit 1)
2. Error: Unable to access jarfile xxx.jar
다음으로 빌드가 성공을 했는데 docker-compose up -d 로 컨테이너를 띄우니 db 컨테이너만 뜨고 api 컨테이너가 뜨지 않았다...ㅠㅠ 에러는 다음과 같았다. Error: Unable to access jarfile xxx.jar
다른 내용도 없이 위와 같은 에러가 떠서 어리둥절... 결론부터 말하면 volume 설정으로 인해 jar 파일이 복사되지 않는 이슈가 있었다...
하나씩 확인하는 과정을 거쳤는데 살펴보면 다음과 같다.
# 1. 먼저 실행 중인 컨테이너 내리고 삭제
$ docker-compose down
# 2. 컨테이너 실행과 동시에 명령어로 디렉토리 확인
$ docker-compose run --rm api ls -lh /workspace
$ docker-compose run --rm api ls -lh /workspace/starter-api/build/libs
또는
$ docker-compose run --rm api sh -c "ls -lh /workspace/starter-api/build/libs && ls -lh /workspace"
# 3. 결과 확인 - jar 파일은 생성되었지만 복사가 되지 않았음을 확인
Creating network "devcontainer_default" with the default driver
Creating dev-container-db ... done
Creating devcontainer_api_run ... done
total 63072
-rw-r--r-- 1 root root 64567552 Jan 30 13:28 starter-api-0.0.1-SNAPSHOT.jar
-rw-r--r-- 1 root root 12322 Jan 30 13:28 starter-api-0.0.1-SNAPSHOT-plain.jar
total 63116
drwxr-xr-x 1 root root 4096 Jan 30 08:46 build
-rw-r--r-- 1 root root 2697 Dec 2 11:55 build.gradle.kts
drwxr-xr-x 3 root root 4096 Jan 30 08:46 gradle
-rw-r--r-- 1 root root 509 Dec 2 11:55 gradle.properties
-rwxr-xr-x 1 root root 8762 Dec 2 11:55 gradlew
-rw-r--r-- 1 root root 2966 Dec 2 11:55 gradlew.bat
-rw-r--r-- 1 root root 375 Dec 2 11:55 README.md
-rw-r--r-- 1 root root 812 Dec 2 11:55 settings.gradle.kts
drwxr-xr-x 2 root root 64 Jan 30 15:08 src
drwxr-xr-x 1 root root 4096 Jan 30 08:46 starter-api
drwxr-xr-x 4 root root 4096 Jan 30 08:46 starter-common
# 4. 직접 복사해서 넣기
$ docker-compose down
$ docker-compose run --rm api sh -c "cp /workspace/starter-api/build/libs/starter-api-0.0.1-SNAPSHOT.jar /workspace/starter-api.jar && ls -lh /workspace"
# 5. 실행 중인 컨테이너 종료 후 다시 실행
$ docker-compose down
$ docker-compose up -d
하지만 매번 빌드하고 파일 복사하고 다시 실행할 수 없기 때문에 원인을 알아보니 docker-compose.yml 에 설정한 volumes: - ../..:/workspace 로 설정한게 문제였다.
volumes: - ../..:/workspace 이런 식으로 설정되어 있다면, 도커 빌드 단계에서 만든 /workspace/starter-api/build/libs/...jar 파일이 로컬 측 디렉토리 구조로 인해 덮어쓰이거나, 로컬 디렉토리 구조가 우선 적용될 수 있다.
즉, 빌드 단계에서 jar를 만들었더라도, 다음 레이어에서 동일 경로를 볼륨 마운트로 덮어버리면 컨테이너 안에서 jar가 없는 상태가 된다.
따라서 RUN cp가 실행될 시점에는 파일이 사라져 있는 경우가 많다고 한다.
그래서 해결책으로 volumes 를 아예 없애거나 필요한 디렉토리 위치만 설정하는 것이 좋다고 한다. 하지만 volumes를 아예 없애면 개발환경에서 변경한 소스가 반영되지 않을 수 있기 때문에 필요한 파일만 지정하는 방식으로 변경하였다.
[Before]
version: "3.8"
services:
api:
...
volumes:
- ../..:/workspace
[After]
version: "3.8"
services:
api:
...
volumes:
- ../../starter-api/src:/workspace/starter-api/src
- ../../gradlew:/workspace/gradlew
3. Error: Invalid or corrupt jarfile xxx.jar
위 문제들을 해결하던 중 volumes 설정을 바꾸는데 다음과 같이 적용했더니 jar 디렉토리가 생기면서 복사가 안되었고 그 상태에서 실행하려고 했더니 생긴 이슈였다. 위에서 volume 설정을 변경해주면서 해결이 되었다.
Dev Container 적용에 대해 알아보고 있었는데... 도커의 동작 방식이나 하나하나의 설정이 가지는 의미, 그리고 왜 이런 설정이 필요한지를 더 명확히 알고 있다면 큰 도움이 될 것 같았다. 도커의 volume과 빌드, 실행 과정 하나하나 어떻게 동작하는지 더 알아볼 예정이다...
To be continued...🫠
- Total
- Today
- Yesterday
- CodePipeline
- CodeDeploy
- sort
- BFS
- AWS
- permutation
- search
- cloudfront
- CodeCommit
- map
- 조합
- 프로그래머스
- Dynamic Programming
- array
- Baekjoon
- 수학
- ECR
- 소수
- Combination
- Algorithm
- 에라토스테네스의 체
- DFS
- ionic
- string
- programmers
- java
- 순열
- EC2
- spring
- SWIFT
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |