본문 바로가기
카테고리 없음

Docker 커스텀 이미지 (Dockerfile, 컨테이너, docker-compose)

by ricepuppy9733 2026. 6. 12.

Dockerfile 하나면 java 설치부터 jar 실행까지 전부 자동화됩니다. 처음 이 사실을 알았을 때 솔직히 이건 예상 밖이었습니다. 매번 서버에 접속해서 손으로 설치하던 게 파일 한 장으로 끝난다는 게 믿기지 않았거든요. 직접 써보고 나서야 "왜 진작 이걸 안 썼지"라는 생각이 들었습니다.

Dockerfile

Dockerfile로 백엔드·프론트 이미지 직접 만들기

일반적으로 Docker는 MariaDB나 Nginx 같은 공식 이미지를 그냥 내려받아 쓰는 도구라고만 생각하는 분들도 있는데, 실제로 써보니 내가 만든 프로젝트 자체를 이미지로 패키징해서 배포하는 게 핵심이었습니다.

백엔드 기준으로 기존 배포 방식을 먼저 짚어보면, Spring Boot 프로젝트를 bootJar로 빌드해서 jar 파일을 만들고, 서버에 java를 직접 설치한 뒤 java -jar로 실행하는 흐름입니다. 이걸 컨테이너 안에서 그대로 재현하면 되는 거라 구조 자체는 어렵지 않습니다.

여기서 베이스 이미지(Base Image)란 컨테이너를 만들 때 시작점이 되는 이미지를 말합니다. 제가 백엔드에 사용한 베이스 이미지는 openjdk:17-jdk-slim인데, Docker Hub에서 java 실행 환경이 이미 세팅된 채로 제공되기 때문에 별도로 java를 설치하는 RUN 명령어를 쓸 필요가 없습니다. 이 점이 수동 배포와 비교했을 때 가장 체감 차이가 큰 부분이었습니다.

Dockerfile에서 자주 쓰는 명령어를 정리하면 다음과 같습니다.

  • FROM: 베이스 이미지를 지정합니다. 모든 Dockerfile의 시작점입니다.
  • COPY / ADD: 로컬 파일을 컨테이너 안으로 복사합니다. ADD는 압축 해제나 URL 다운로드 기능이 추가된 버전입니다.
  • RUN: 이미지 빌드 시점에 실행할 명령어로, 패키지 설치 같은 환경 준비에 씁니다.
  • CMD / ENTRYPOINT: 컨테이너가 실제로 실행될 때 동작할 명령어입니다. CMD는 docker run 뒤에 명령어를 붙이면 덮어써지고, ENTRYPOINT는 덮어써지지 않고 유지됩니다.
  • ENV: 환경 변수를 설정하며, 기본값 명시와 문서화 역할을 동시에 합니다.
  • EXPOSE: 컨테이너가 사용하는 포트를 선언하는 문서화 역할입니다.

프론트엔드는 npm run build로 dist 폴더를 만든 뒤 nginx 베이스 이미지 위에 얹는 구조입니다. 여기서 Nginx란 정적 파일 서빙과 리버스 프록시(Reverse Proxy) 기능을 제공하는 웹 서버로, SPA(Single Page Application) 배포에 사실상 표준처럼 쓰입니다. nginx.conf 파일에서 try_files $uri $uri/ /index.html 설정을 해두면 React나 Vue의 클라이언트 사이드 라우팅이 정상 동작합니다. 제가 직접 써봤는데 이 설정 한 줄 빠지면 새로고침할 때마다 404가 뜨는 황당한 상황이 생깁니다.

이미지 빌드는 docker build --tag 아이디/레포지토리:태그 . 형식으로 실행하고, 빌드된 이미지는 docker push로 Docker Hub에 올려두면 어느 서버에서든 내려받아 쓸 수 있습니다. Docker Hub란 GitHub처럼 이미지를 저장하고 공유하는 원격 레지스트리(Registry)입니다. 팀 프로젝트에서 이미지를 Hub에 올려두고 팀원들이 각자 pull 받아 쓰니 "내 로컬에서는 되는데 서버에서 안 된다"는 상황 자체가 거의 사라졌습니다.

docker-compose로 멀티 컨테이너 한 번에 관리하기

백엔드, 프론트, DB를 따로따로 docker run으로 실행하다 보면 금방 한계가 옵니다. 제 경험상 이건 좀 다릅니다. 컨테이너가 3개만 넘어가도 포트 번호, 네트워크 연결, 실행 순서를 일일이 챙기는 게 손이 너무 많이 갑니다.

docker-compose란 여러 컨테이너를 YAML 파일 하나로 정의하고 docker-compose up 명령어 한 번으로 전체를 한꺼번에 띄울 수 있는 도구입니다. 여기서 YAML 파일이란 들여쓰기 기반의 설정 파일 형식으로, 서비스별 이미지, 포트, 환경 변수, 볼륨, 의존 관계를 계층적으로 기술합니다.

특히 제가 직접 적용해보면서 효과를 확실히 느낀 부분은 DB 마스터-슬레이브 복제 구성입니다. 일반적으로 DB 레플리케이션(Replication)은 세팅하기 까다롭다고 알려져 있지만, 실제로 docker-compose와 쉘 스크립트를 조합하니 훨씬 수월했습니다. 여기서 레플리케이션이란 마스터 DB에 쓰인 데이터를 슬레이브 DB가 실시간으로 복제하는 구조로, 읽기 부하를 분산하거나 장애 대비용으로 활용합니다(출처: MariaDB 공식 문서).

master-init.sh에서 slave 전용 계정을 생성하고 복제 권한을 부여한 뒤, slave-init.sh에서 마스터의 바이너리 로그(Binary Log) 파일명과 포지션(Position)을 읽어와 CHANGE MASTER TO로 연결하는 방식입니다. 바이너리 로그란 MariaDB/MySQL이 데이터 변경 사항을 순서대로 기록하는 파일로, 슬레이브가 이 로그를 읽어 마스터와 동일한 상태를 유지하는 데 사용됩니다. depends_on 설정으로 마스터가 완전히 뜬 다음 슬레이브가 초기화되도록 순서를 잡아두면, 스크립트가 마스터에 접속 실패하는 문제를 방지할 수 있습니다.

MSA(Microservice Architecture) 구성에서는 docker-compose의 위력이 더 커집니다. Eureka 기반 서비스 디스커버리(Service Discovery)를 사용하는 환경에서 api-gateway, api-discovery, api-board, api-user 같은 서비스들을 하나의 docker-compose.yml로 묶어두면, 팀원 누구든 GitHub에서 클론한 뒤 명령어 한 줄로 전체 환경을 세팅할 수 있습니다. 여기서 서비스 디스커버리란 컨테이너 환경처럼 IP가 동적으로 바뀌는 상황에서 각 서비스가 서로를 이름으로 찾아 통신할 수 있게 해주는 메커니즘입니다(출처: Spring Cloud Netflix 공식 문서).

com.palantir.docker Gradle 플러그인을 활용하면 bootJar 빌드와 Docker 이미지 빌드를 하나의 태스크로 묶을 수도 있습니다. build.gradle에 docker 블록을 추가하고 tasks.bootJar.outputs.files를 연결해두면, Gradle 빌드 한 번에 jar 생성부터 이미지 태깅까지 자동으로 처리됩니다.

docker-compose를 써보고 나서 가장 크게 느낀 점은 "환경 세팅 문서를 따로 쓸 필요가 없다"는 겁니다. YAML 파일 자체가 문서이고, 그게 실제로 동작하는 인프라 코드가 됩니다. 이 부분은 일반적으로 생각하는 것보다 훨씬 실용적인 이점입니다.

결국 Dockerfile과 docker-compose의 조합은 "내 로컬에서만 되는 프로젝트"를 "어디서든 동일하게 동작하는 프로젝트"로 바꿔주는 핵심 도구입니다. 처음엔 설정 파일이 낯설게 느껴질 수 있지만, 한 번 구조를 잡아두면 그다음부터는 복사해서 수정하는 수준으로 활용할 수 있습니다. 멀티 모듈이나 MSA로 확장할 때도 이 기반이 탄탄하게 잡혀 있어야 흔들리지 않으니, 지금 배포 자동화를 고민 중이라면 Dockerfile부터 직접 작성해보시길 권합니다.


참고: https://dltldnr2563.tistory.com/entry/%EC%BD%94%EB%94%A9%EA%B3%B5%EB%B6%8020250909-docker-%EC%9D%B4%EB%AF%B8%EC%A7%80-%ED%8C%8C%EC%9D%BC-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%89%98-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-DB-%ED%99%9C%EC%9A%A9-%EB%A9%80%ED%8B%B0%EB%AA%A8%EB%93%88-%ED%99%9C%EC%9A%A9


소개 및 문의 · 개인정보처리방침 · 면책조항

© 2026 자동식단생성 연관 블로그