[홈서버 구축] Docker로 백엔드 개발 서버 띄우기

오젼·2025년 1월 14일
0

[홈서버 구축]

목록 보기
2/5

수강신청 연습 사이트의 개발 서버가 필요했다.

Spring Boot + mariadb 백엔드 개발 서버를 docker container로 띄우고, nginx를 통해 외부에 expose하는 방법을 단계별로 정리한다.

도메인 설정

도메인 설정

디렉토리 구조

.
├── build.gradle
├── docker-compose.yml
├── docker-files
│   ├── backend
│   └── nginx
├── gradle
│   └── wrapper
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
    ├── main
    └── test

docker-compose.yml

컨테이너들을 관리하는 파일이다.

services:
  backend:
    build:
      context: . # 현재 디렉토리를 빌드 컨텍스트로 사용
      dockerfile: docker-files/backend/Dockerfile # 백엔드 도커파일 위치 지정
    container_name: cr-backend
    ports:
      - "${BACKEND_PORT}:8080" # 호스트의 환경변수 포트를 컨테이너의 8080포트와 매핑
    volumes:
      - .:/workspace/app # 현재 디렉토리를 컨테이너의 /workspace/app에 마운트하여 실시간 코드 반영
    networks:
      - app-network # 컨테이너들 간의 통신을 위한 네트워크 설정
    restart: unless-stopped # 컨테이너가 중단되면 자동으로 재시작 (수동 중단 제외)

  nginx:
    image: nginx:alpine # 경량화된 nginx 알파인 리눅스 버전 사용
    container_name:  cr-nginx
    ports:
      - "${NGINX_PORT}:80" # 호스트의 환경변수 포트를 nginx의 80포트와 매핑
    volumes:
      - ./docker-files/nginx/nginx.conf:/etc/nginx/conf.d/default.conf # nginx 설정 파일 마운트
    depends_on:
      - backend # 백엔드 컨테이너가 시작된 후에 nginx 컨테이너 시작
    networks:
      - app-network # 다른 서비스들과 통신하기 위한 네트워크 연결
    restart: unless-stopped # 컨테이너 장애 시 자동 재시작

  mariadb:
    image: mariadb:latest # 최신 버전의 MariaDB 이미지 사용
    container_name: cr-mariadb
    environment: # MariaDB 설정을 위한 환경변수들
      MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} # root 비밀번호
      MARIADB_DATABASE: ${DB_NAME} # 생성할 데이터베이스 이름
      MARIADB_USER: ${DB_USER} # 생성할 사용자 이름
      MARIADB_PASSWORD: ${DB_PASSWORD} # 사용자 비밀번호
    ports:
      - "${DB_PORT}:3306" # 호스트의 환경변수 포트를 MariaDB의 3306포트와 매핑
    volumes:
      - mariadb-data:/var/lib/mysql # 데이터 영속성을 위한 볼륨 마운트
    networks:
      - app-network # 다른 서비스들과의 통신을 위한 네트워크 연결
    restart: unless-stopped # 컨테이너 장애 시 자동 재시작

networks:
  app-network:
    driver: bridge # 컨테이너들이 서로 통신할 수 있게 해주는 브릿지 네트워크 사용

volumes:
  mariadb-data: # MariaDB 데이터를 영구적으로 저장하기 위한 도커 볼륨 정의

Backend: Spring Boot 애플리케이션
Web Server: Nginx
Database: MariaDB

주요 개념 설명

ports: 외부포트:내부포트 형식
예: "8081:8080"은 컴퓨터의 8081포트로 접속하면 컨테이너의 8080포트로 연결

volumes: 데이터를 저장하거나 파일을 공유할 때 사용. 컨테이너가 삭제되어도 데이터는 유지됨

networks: 컨테이너들이 서로 통신할 수 있게 해주기 위해 설정. 여기서는 모든 프로그램이 같은 네트워크를 사용

depends_on: 특정 서비스가 시작된 후에 실행
예: nginx는 backend가 시작된 후에 실행

외부 요청 처리 과정

공유기 포트포워딩을 설정하고 nginx를 통해 외부 요청을 프록시했다.

외부 요청 -> 집 공유기(XXXX) -> 내 노트북(XXXX) -> Docker의 nginx 컨테이너(80) -> Docker의 backend 컨테이너(8080)

cr-backend 설정

./cr-backend/Dockerfile

# 빌드 스테이지
# 빌드 스테이지
FROM amazoncorretto:17-alpine as build

WORKDIR /workspace/app

COPY gradle gradle
COPY build.gradle settings.gradle gradlew ./
COPY src src

RUN chmod +x ./gradlew
RUN ./gradlew bootJar
RUN mkdir -p build/libs

# 실행 스테이지
FROM amazoncorretto:17-alpine

VOLUME /tmp

COPY --from=build /workspace/app/build/libs/*.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=dev", "/app.jar"]

위 Dockerfile은 두 단계로 나누어져 있다. Spring Boot 애플리케이션을 실행 가능한 jar 파일로 만드는 '빌드 단계'와 만들어진 jar 파일을 실제로 실행하는 '실행 단계'이다.

빌드 스테이지 설명

  1. FROM amazoncorretto:17-alpine as build

    • Java 17 버전이 설치된 가벼운 Linux 환경을 사용한다.
    • as build로 이 스테이지에 이름을 지정하여 다음 스테이지에서 참조할 수 있게 했다.
  2. WORKDIR /workspace/app

    • 컨테이너 내부의 작업 디렉토리 설정. 이걸 설정하지 않으면 COPY를 할 때 루트 디렉토리로 카피가 된다. WORKDIR를 설정하면 현재 디렉토리 위치가 설정한 곳으로 고정되는 것.
  3. COPY 명령어들

    • 이제 Spring Boot 프로젝트의 모든 필요한 파일들을 WORKDIR로 복사한다.
    • gradle 폴더: 빌드 도구 관련 파일들
    • build.gradle, settings.gradle: 프로젝트 설정 파일들
    • gradlew: gradle 실행 파일
    • src: 우리가 작성한 실제 코드
  4. RUN 명령어들

    • gradlew에 실행 권한 부여
    • Spring Boot 애플리케이션을 jar 파일로 빌드
    • jar 파일이 생성될 디렉토리 생성

실행 스테이지 설명

  1. FROM amazoncorretto:17-alpine

    • 새로운 스테이지를 깨끗한 상태로 시작한다.
    • 이번에는 빌드 도구나 소스 코드 없이 실행 환경만 포함한다.
  2. VOLUME /tmp

    • 컨테이너의 /tmp 디렉토리를 볼륨으로 설정
    • 임시 파일들을 컨테이너 외부에 저장하여 성능 향상
  3. COPY --from=build /workspace/app/build/libs/*.jar app.jar

    • 아까 빌드 스테이지에서 생성된 jar 파일만 복사한다. (--from=build로 이전 스테이지의 파일 참조)
  4. EXPOSE 8080

    • 컨테이너가 8080 포트를 사용함을 문서화
    • 실제 포트 매핑은 docker run -p나 docker-compose.yml에서 설정해야 한다.
  5. ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=dev", "/app.jar"]

    • 컨테이너 시작 시 실행할 명령어 설정
    • Spring 프로파일을 dev로 설정하여 개발 환경으로 실행하게 했다.

ENTRYPOINT vs CMD

Docker 컨테이너의 실행 명령어를 설정하는 방법으로 CMD와 ENTRYPOINT가 있다.

CMD는 컨테이너 실행 시 다른 명령어로 오버라이드가 가능하다. 대신 ENTRYPOINT는 오버라이드가 불가능하다.

컨테이너 실행 시점에 명령어를 다른 명령어로 바꿀 수 있냐, 없냐의 차이다.

예시)

# CMD를 사용한 경우
CMD ["java", "-jar", "/app.jar"]
docker run myapp other-command  # java -jar /app.jar 대신 other-command 실행

# ENTRYPOINT를 사용한 경우
ENTRYPOINT ["java", "-jar", "/app.jar"]
docker run myapp -Xmx512m  # java -jar /app.jar -Xmx512m 실행

# 추가로 ENTRYPOINT와 CMD를 같이 사용할 수 있다.
# 이 경우 CMD는 ENTRYPOINT의 기본 파라미터가 된다.
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
docker run myapp # nginx -g daemon off; 실행

그래서 ENTRYPOINT는 컨테이너가 시작될 때 항상 실행돼야 하는 명령어를 설정할 때 쓰인다.

이 경우 jar 파일 실행은 항상 필요하므로 ENTRYPOINT를 사용한다.

멀티 스테이지 빌드를 사용한 이유

  • 빌드 스테이지: gradle을 통한 jar 파일 생성에 필요한 파일들과 환경 구성
  • 실행 스테이지: 빌드된 jar 파일만 복사하여 실행에 필요한 최소한의 환경만 구성

최종 이미지는 실행에 필요한 파일만 포함하게 되어 이미지 크기가 크게 감소하고 보안성도 향상된다.

  • 이미지 크기 최소화 - 실행에 필요한 파일만 포함
  • 보안 강화 - 소스 코드와 빌드 도구가 최종 이미지에서 제외
  • 배포 효율성 - 작은 이미지 크기로 인한 배포 시간 단축

Nginx 설정

./nginx/nginx.conf

server {
    listen 80;
    server_name ${DOMAIN_NAME};

    location / {
        proxy_pass http://backend:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

기본 서버 설정

listen 80: 80포트로 들어오는 HTTP 요청을 받는다.
server_name ${DOMAIN_NAME}: 이 도메인으로 들어오는 요청을 처리한다.

location 설정

location /: 모든 경로의 요청을 처리한다.
proxy_pass http://backend:8080: 받은 요청을 backend 컨테이너의 8080 포트로 전달한다.

프록시 헤더 설정

proxy_set_header Host $host: 클라이언트가 요청한 원래 호스트 정보를 백엔드로 전달. 백엔드 애플리케이션이 어떤 도메인으로 요청이 들어왔는지 알 수 있음.

proxy_set_header X-Real-IP $remote_addr: 실제 클라이언트의 IP 주소를 백엔드로 전달. 로깅이나 차단 목록 관리 등에 사용.

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for: 요청이 거쳐온 모든 프록시 서버의 IP 주소 목록을 전달. 클라이언트의 실제 IP를 포함한 전체 요청 경로 추적 가능.

proxy_set_header X-Forwarded-Proto $scheme: 클라이언트가 사용한 프로토콜(http/https)을 백엔드로 전달. 백엔드에서 보안 관련 처리시 필요.

이러한 헤더 설정들은 프록시 서버를 통해 요청이 전달될 때 원래 요청의 정보를 잃지 않고 백엔드 서버로 전달하기 위해 필요하다.
예를 들어, 이런 헤더가 없다면 백엔드 서버는 모든 요청이 Nginx(프록시 서버)에서 온 것으로만 인식하게 된다. 실제 클라이언트의 IP나 프로토콜 등의 정보를 알 수 없게 된다.

프록시 헤더 없는 경우:

클라이언트(1.1.1.1) -> Nginx(2.2.2.2) -> 백엔드
                                         ↳ "요청이 2.2.2.2에서 왔네?"

프록시 헤더 설정한 경우:

클라이언트(1.1.1.1) -> Nginx(2.2.2.2) -> 백엔드
                    ↳ X-Real-IP: 1.1.1.1
                    ↳ X-Forwarded-For: 1.1.1.1
                                         ↳ "아, 실제 요청은 1.1.1.1에서 왔구나!"

Nginx에는 Dockerfile을 사용하지 않았다

docker-compose.yml을 보면 nginx 서비스는 Dockerfile 없이 공식 이미지를 직접 사용했다:

nginx:
    image: nginx:alpine   # nginx 공식 이미지 사용
    volumes:
      - ./docker-files/nginx/nginx.conf:/etc/nginx/conf.d/default.conf # 설정 파일만 주입

이렇게 한 이유는:

  1. 커스터마이징이 거의 필요없음

    • nginx는 단순히 프록시 서버 역할만 수행
    • 복잡한 빌드 과정이 필요없음
    • 설정 파일만 변경하면 됨
  2. 공식 이미지의 신뢰성

    • nginx:alpine은 잘 관리되는 공식 이미지
    • 보안 업데이트가 지속적으로 이루어짐
    • 최적화가 잘 되어있음
  3. 간단한 설정 주입

    • volumes를 통해 설정 파일만 교체
    • 별도의 이미지 빌드 없이 설정 변경 가능
    • 설정 변경 시 컨테이너만 재시작하면 됨!! (nginx.conf를 바꿨다고 컨테이너를 다시 빌드할 필요가 없다. Dockerfile로 COPY를 했다면 재빌드가 필요한데, 이 경우 그냥 docker-compose restart만 하면 된다.)

반면 cr-backend는 커스텀 Spring Boot 애플리케이션이라 소스 코드를 빌드하고 실행하는 과정이 필요했기 때문에 Dockerfile이 필요했다.

이처럼 상황에 따라 Dockerfile을 직접 작성할지, 공식 이미지를 사용할지 선택할 수 있다. 커스터마이징이 많이 필요한 경우 Dockerfile을, 그렇지 않은 경우 잘 관리되는 공식 이미지를 사용하는 것이 효율적이다.

Mariadb 설정

MariaDB도 nginx와 마찬가지로 Dockerfile 없이 공식 이미지를 직접 사용했다

mariadb:
    image: mariadb:latest   # mariadb 공식 이미지 사용
    environment:
      MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MARIADB_DATABASE: ${DB_NAME}
      MARIADB_USER: ${DB_USER}
      MARIADB_PASSWORD: ${DB_PASSWORD}
    volumes:
      - mariadb-data:/var/lib/mysql  # 데이터 영속성을 위한 볼륨

마무리

잘 된다!

이 글에서는 도커를 사용해 개발 서버를 구축하는 방법을 살펴보았다. 각 서비스별로 서로 다른 컨테이너화 전략을 적용했다:

  1. Backend (Spring Boot)
    Dockerfile을 직접 작성하여 멀티 스테이지 빌드 구현
    소스 코드 빌드부터 실행까지 완전한 제어가 필요한 커스텀 애플리케이션이기 때문
  1. Nginx
    공식 이미지를 그대로 사용하고 설정 파일만 볼륨으로 마운트
    프록시 서버로서 단순한 역할만 수행하므로 커스터마이징이 거의 필요없음
    설정 변경 시 컨테이너 재시작만으로 반영 가능
  1. MariaDB
    공식 이미지 사용 및 환경 변수로 설정
    데이터베이스 설정은 환경 변수만으로 충분
    볼륨을 통해 데이터 영속성 보장

이제 다음은 라즈베리파이에다가 도커 실행하기..!!

라즈베리파이야 얼른 와

0개의 댓글

관련 채용 정보