독린이: Docker + 어린이를 뜻하는 합성어.

Spring Boot를 이용해 웹 백엔드 서버를 구축할 때,
1. 빌드를 통해 .jar 파일을 생성한다.
2. .jar 파일을 배포 서버에 전송한다.
3. 배포 서버에 접속한다.
4. nohup 명령어를 이용해 .jar 파일을 실행한다.
위 방식을 주로 사용했었다.
진행했던 프로젝트의 트래픽이 많지 않았기에, 다행히도 무사히 굴러갔던 것 같다.
하지만 서비스의 규모가 커지고, 트래픽이 많아진다면? 이런 방식으로 배포 환경을 관리하기는 어려울 것이라는 생각이 들었다.
나의 관심사는 자연스럽게 컨테이너 환경으로 이동했고, 컨테이너 환경의 대표 주자인 Docker부터 차근차근 익혀나가려고 한다.
전통적인 .jar 배포 vs Docker 컨테이너 배포
| 비교 항목 | 전통적인 .jar 배포 | Docker 컨테이너 배포 |
| 배포 단위 | 애플리케이션 실행 파일 (.jar) | 앱 코드 + 필요한 런타임·라이브러리·설정을 포함한 이미지 |
| 환경 의존성 | 서버에 Java(JDK/JRE)가 설치되어야 함 | 서버에 Docker만 있으면 됨, 이미지 안에 Java 런타임 포함 가능 |
| 환경 일관성 | 서버마다 설치된 Java 버전·OS 설정이 다르면 동작이 달라질 수 있음 | 이미지에 환경이 고정되어 어디서 실행해도 동작이 거의 동일함 |
| 격리 수준 | 일반 프로세스처럼 호스트 OS의 파일 시스템·프로세스 트리·네트워크 스택을 그대로 공유 (포트 충돌, 라이브러리 충돌 위험) | 리눅스 namespace로 파일 시스템·프로세스 트리·네트워크를 분리하고, cgroup으로 자원 사용을 제한하는 프로세스 수준 컨테이너 격리 (커널은 공유하지만 다른 앱과 상당 부분 분리) |
| 확장성 | 새로운 서버마다 OS, Java, 설정을 수동으로 맞춰야 해서 확장 작업이 번거로움 | 같은 이미지를 여러 컨테이너로 빠르게 띄워 수평 확장 가능 |
| 포트 처리 | 서버에서 애플리케이션이 직접 포트를 바인딩하여, 같은 포트를 쓰는 다른 앱과 충돌하기 쉬움 | 컨테이너 내부 포트와 호스트 포트를 매핑(-p 호스트:컨테이너)해, 하나의 컨테이너 포트를 여러 다른 호스트 포트로 분리 가능 |
| 관리 주체 | 개발자가 서버 인프라를 직접 관리 | 이미지를 빌드하고 IaC 도구로 인프라를 코드로 관리하는 패턴이 일반적임 |
프로세스 수준의 격리
호스트에서 실행되는 일반 프로세스들은 Host OS의 커널, 파일 시스템, 네트워크 스택을 그대로 공유한다.
이 때문에 해당 서버의 라이브러리나 런타임이 업데이트되거나, 다른 프로세스와 포트 번호가 겹치면 애플리케이션에 직접 영향을 줄 수 있다.
시스템 수준의 격리
컨테이너는 리눅스 namespace를 사용해 자신만의 파일 시스템, 프로세스 트리(PID), 네트워크 인터페이스(가상 네트워크)를 가진다.
서버에서 특정 라이브러리·런타임을 업데이트해도, 컨테이너 내부에는 이미지에 포함된 버전이 그대로 남아 있어 영향이 제한된다.
포트 충돌은 컨테이너 내부 포트와 호스트 포트를 포트 포워딩으로 매핑하고, 필요하면 서로 다른 호스트 포트를 사용함으로써 피할 수 있다.
그래서 컨테이너 환경의 장점은 다음과 같다
- 환경의 일관성: 로컬·테스트·운영 등 어느 환경에서나 같은 컨테이너 이미지를 실행해, 항상 동일한 실행 환경을 보장한다.
- 격리와 안전: 여러 애플리케이션을 한 서버에 올려도, 컨테이너마다 설정·라이브러리·네트워크가 분리되어 서로 간섭하거나 꼬이지 않는다.
- 확장성: 사용자가 늘어나면 동일 이미지를 기반으로 컨테이너 인스턴스를 복제해 수평 확장하면 되며, 오케스트레이터로 이를 자동화할 수 있다.
나에게 주어진 상황
팀원이 프로젝트 초기 세팅을 맡아, Spring Boot 프로젝트를 Docker를 이용해 빌드할 수 있도록 설정해두었다.
- docker-compose.local.yml
services:
database:
image: mysql:8.4.0
container_name: knu-80-database
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
MYSQL_DATABASE: ${DB_DATABASE}
MYSQL_USER: ${DB_USERNAME}
MYSQL_PASSWORD: ${DB_PASSWORD}
command:
--port=${DB_PORT} --max_allowed_packet=32M
expose:
- ${DB_PORT}
ports:
- ${DB_PORT}:${DB_PORT}
restart: always
volumes:
- <database_name>:/var/lib/mysql
networks:
- <bridge_name>
volumes:
<database_name>:
networks:
<bridge_name>:
driver: bridge
→ MySQL 서버를 docker compose 명령어를 이용해 컨테이너 환경으로 실행하는 것이 목표이다.
docker compose 명령어

docker-compose, docker compose 둘 다 사용가능하다. (docker-compose 명령어는 옛날 버전이다.)
아래 명령어로 실행 가능하다.
docker compose -f docker-compose.local.yml up -d
: docker-compose.local.yml에 정의된 모든 서비스를 컨테이너로 생성·실행하고, 이들을 백그라운드에서 돌려라!
- -f docker-compose.local.yml
- 현재 디렉터리의 기본 docker-compose.yml 대신, docker-compose.local.yml 파일을 컴포즈 설정 파일로 사용하겠다는 의미
- 이 파일 안에 정의된 서비스, 네트워크, 볼륨 구성을 기준으로 컨테이너를 띄움
- up
- 해당 compose 파일에 정의된 서비스용 컨테이너들을 생성하고(없으면), 필요한 이미지를 빌드·다운로드한 뒤 순서에 맞게 실행
- 여러 서비스(web, db 등)가 정의돼 있다면 한 번에 모두 올림.
- -d (detached 모드)
- 컨테이너를 백그라운드에서 실행 → 터미널을 점유하지 않고, 명령이 끝나면 셸 프롬프트가 바로 돌아옴
환경변수 설정
docker-compose.local.yml 코드를 보면 ${DB_USERNAME} 같은 플레이스 홀더가 존재한다. 그렇기에, 당연히 위 docker compose 명령어를 실행하려면 해당 플레이스 홀더를 채워줄 환경 변수 값이 필수이다.
환경 변수를 주입하는 두 가지 방법을 소개하겠다.
로컬에서는 보통 둘 중 한 가지 방법을 택해서 사용한다.
- .env 파일을 통해 환경변수 주입
- docker compose 명령어의 --env-file <env 파일 경로/이름> 옵션을 통해 env 파일을 지정할 수 있다.
- 해당 옵션을 넣지 않을 경우, 프로젝트 루트 폴더에서 .env 이름의 파일을 찾아 그 내용을 주입한다.
- 따라서, 아래 내용을 .env 파일에 저장한 후 명령어(docker compose ... )를 수행하면 정상 작동한다.
-
더보기DB_PASSWORD=aaaa
DB_DATABASE=temp
DB_USERNAME=temp
DB_PORT=3306
- (IntelliJ 사용 시) IntelliJ Edit Configuration으로 환경 변수 직접 주입

IntelliJ 환경 변수로 주입했기 때문에, 터미널에서 docker compose ... 명령어를 입력하면 환경 변수를 찾지 못한다.

그렇기 때문에 위 사진처럼 database: 왼쪽에 있는 버튼을 눌러 docker compose 명령어를 실행하여야 한다.
결과

.env/환경 변수 설정 덕분에 compose 파일 안의 플레이스홀더가 치환되어 컨테이너가 제대로 뜬다.
Docker compose 명령어를 이용해서 MySQL 컨테이너를 정상적으로 동작시켰다.