"내 컴퓨터에서는 잘 되는데, 서버에서는 에러가 나요!"
개발자의 노트북, 테스트 서버, 운영 서버는 운영체제 버전, 자바 버전, 환경 변수 등이 모두 미세하게 다릅니다. 이 '의존성 지옥(Dependency Hell)'을 해결하기 위해 과거에는 가상머신(VM)을 썼지만 너무 무거웠습니다. 도커는 컨테이너(Container) 기술을 이용해 애플리케이션과 그 실행 환경 전체를 하나의 패키지(이미지)로 묶어 어디서든 100% 동일하게 실행되도록(멱등성 보장) 만들어줍니다.
| 비교 항목 | 가상머신 (Virtual Machine) | 도커 컨테이너 (Docker Container) |
|---|---|---|
| 게스트 OS | 각 VM마다 별도의 운영체제(Guest OS) 설치 필요 | 없음 (호스트 OS의 커널 공유) |
| 크기 및 리소스 | GB 단위 (무거움), 자원 할당량 고정 | MB 단위 (가벼움), 동적 자원 사용 |
| 부팅 속도 | OS 부팅이 필요하므로 수 분 소요 | 프로세스 실행과 동일하여 초 단위 소요 |
도커 CLI는 "대상(이미지, 컨테이너 등) + 행위(run, rm 등)"의 직관적인 구조를 가집니다.
백문이 불여일타! Nginx 웹서버를 도커로 띄워서 브라우저에 접속하는 전체 생애주기(Lifecycle)를 실습해봅시다. 로컬 컴퓨터에 Nginx를 복잡하게 직접 설치하지 않아도, 단 한 줄의 명령어면 강력한 웹서버 구동이 완료됩니다.
| 핵심 명령어 | 동작 개념 | 주요 옵션 |
|---|---|---|
| run | 이미지가 없으면 다운로드(Pull)하고, 컨테이너를 생성(Create)한 뒤 즉시 실행(Start)합니다. 가장 많이 쓰이는 마법의 명령어입니다. | -d (백그라운드 실행)-p (포트 매핑) |
| ps | 현재 실행 중인 컨테이너들의 목록과 상태(UP), 포트 정보 등을 확인합니다. 리눅스의 프로세스 확인 명령어(ps)와 동일한 역할을 합니다. | -a (중지된 컨테이너까지 모두 표시) |
| exec | 현재 돌고 있는 컨테이너 내부에 침투(?)하여 추가적인 명령을 내립니다. 보통 쉘(bash/sh)을 띄워서 내부를 디버깅할 때 사용합니다. | -it (터미널 입출력을 연결하여 쉘을 사용할 수 있게 해줌) |
| stop / rm | 실행 중인 프로세스를 안전하게 종료(stop)시키고, 중지된 컨테이너의 실체를 디스크에서 삭제(rm)합니다. | -f (강제로 바로 삭제) |
컨테이너는 자신만의 독립적인 격리된 네트워크 공간을 가집니다. 따라서 컨테이너 내부에 80포트로 Nginx가 띄워졌다고 해서 내 컴퓨터의 브라우저로 바로 접속할 수는 없습니다. 반드시 -p 8080:80 처럼 내 컴퓨터의 특정 포트(8080)로 들어오는 통신을 컨테이너 내부의 포트(80)로 "연결(Mapping)"해 주어야 외부에서 접근이 가능합니다.
우리가 직접 개발한 애플리케이션 코드를 도커 이미지로 구워내는(Baking) 과정입니다. Dockerfile이라는 스크립트 파일에 베이스 OS부터 소스 복사, 의존성 설치, 실행 명령까지 차례대로 적어두면 도커가 알아서 이미지를 만들어줍니다.
실무에서는 로컬 컴퓨터에 복잡하게 MySQL이나 Oracle을 직접 설치하지 않고, 필요할 때 도커 컨테이너로 띄워서 사용한 뒤 버립니다. DB 컨테이너를 구동할 때는 초기 비밀번호나 DB 이름 같은 설정값을 환경변수(-e)로 주입해야 정상 작동합니다.
컨테이너가 정상적으로 돌고 있다면 DBeaver나 DataGrip 클라이언트를 켜고 Host: localhost, Port: 3306, Username: root, Password: 1234를 입력하여 접속하면 됩니다. 로컬에 설치한 것과 완전히 똑같이 작동합니다!
앞선 실습에서 만든 MySQL 컨테이너를 지우면(docker rm) 저장했던 테이블과 데이터가 전부 날아갑니다. 컨테이너는 본질적으로 '일회성'이기 때문입니다.
이를 해결하기 위해 호스트 컴퓨터의 물리적 디스크 공간을 컨테이너 내부의 디렉토리와 연결(Mount)하는 도커 볼륨(Volume) 기술을 사용해야 합니다.
만약 Spring Boot 서버(컨테이너 A)가 MySQL DB(컨테이너 B)에 접속해야 한다면 어떻게 해야 할까요? localhost를 쓰면 안 됩니다! 각 컨테이너는 독립된 환경이므로 컨테이너 A의 localhost는 자기 자신을 의미하기 때문입니다.
따라서 두 컨테이너를 같은 도커 네트워크(Docker Network)에 묶어주고, IP 대신 '컨테이너 이름'을 도메인 주소처럼 사용해야 합니다 (도커 내장 DNS).
포트, 볼륨, 네트워크 옵션까지 길게 늘어선 docker run 명령어를 컨테이너 개수만큼 타이핑하는 것은 매우 고통스럽습니다.
이 복잡한 설정들을 하나의 docker-compose.yml 파일에 코드로 정의(IaC)해두고, 명령어 단 한 줄로 전체 아키텍처를 올리거나 내릴 수 있도록 만들어주는 도구가 바로 Docker Compose입니다.
실제 상용 서비스에서는 사용자가 서버 8080포트 등으로 직접 접속하지 못하게 막고, 앞단에 Nginx라는 문지기(Reverse Proxy)를 세워 80/443 포트로 트래픽을 몰아받은 뒤 뒷단의 여러 백엔드 서버로 분산(로드밸런싱)시켜줍니다.
이 심화 구성을 Docker Compose 하나로 완벽하게 구현할 수 있습니다.