Docker로 WEB-WAS연동

목차

Docker로 WEB(Nginx)–WAS(Tomcat)를 연동하려면 두 컨테이너를 같은 사용자 정의 네트워크에 묶고, Nginx의 리버스 프록시가 컨테이너 이름으로 Tomcat을 가리키게 하면 된다. 이 글에서는 Nginx와 Tomcat 컨테이너를 각각 띄우고, docker network로 컨테이너 간 통신을 연결한 뒤, proxy_pass로 요청을 WAS에 넘기고, 마지막에 docker-compose 한 파일로 묶는 전 과정을 정리한다. 중간에 흔히 막히는 두 가지(네트워크 누락·Tomcat 기본 페이지 404)도 원인과 함께 짚는다.

WEB-WAS 연동 구성 개요

WEB 서버(Nginx)는 정적 콘텐츠를 처리하고 동적 요청은 뒤단의 WAS(Tomcat)로 넘기는 리버스 프록시 역할을 한다. VM이나 클라우드(EC2 등)에서 둘을 연동할 때와 원리는 같다. Nginx 설정 파일에 proxy_pass를 추가해 Tomcat으로 요청을 전달하면 된다. 다른 점은 두 미들웨어가 각각 격리된 컨테이너로 뜬다는 것이고, 그래서 컨테이너끼리 통신할 경로를 따로 만들어 줘야 한다는 점이다.

핵심은 사용자 정의 네트워크다. 같은 사용자 정의 브리지 네트워크에 속한 컨테이너들은 Docker 내장 DNS 덕분에 IP 대신 컨테이너 이름(서비스명)으로 서로를 찾을 수 있다. 즉 Nginx 설정에서 Tomcat의 IP를 하드코딩할 필요 없이 tomcat:8080처럼 이름으로 가리키면 된다.

실습 환경 및 버전

  • 로컬 OS: Windows 11 (WSL2 / Ubuntu)
  • WEB: Nginx 컨테이너 최신 버전 (1.25.3 기준)
  • WAS: Tomcat 컨테이너 최신 버전 (10.1.17 기준)

Windows에서 Docker를 쓰려면 그 아래에 리눅스 환경이 있어야 한다. 여기서는 WSL2 위에 Ubuntu를 올려 Docker를 돌리므로, 컨테이너 내부 패키지 설치도 apt 계열 명령을 쓴다.

사용자 정의 네트워크 먼저 생성

컨테이너를 띄우기 전에 통신용 네트워크부터 만든다. 순서를 바꿔서 네트워크 없이 컨테이너만 띄우면, 뒤에서 다룰 “연동이 안 되는” 실수로 이어진다. 사용자 정의 브리지 네트워크를 하나 생성한다.

docker network create network01

이렇게 만든 사용자 정의 네트워크에 속한 컨테이너끼리는 컨테이너 이름으로 서로를 찾을 수 있다. 참고로 Docker가 기본 제공하는 bridge 네트워크에서는 이 이름 기반 DNS가 동작하지 않으므로, 연동에는 반드시 직접 만든 네트워크를 써야 한다.

WAS 컨테이너(Tomcat) 실행

먼저 WAS인 Tomcat 컨테이너를 띄운다. --network 옵션으로 앞서 만든 네트워크에 바로 붙이고, 컨테이너 이름을 tomcat000ex01로 지정한다. Tomcat은 8080 포트로 동작한다.

docker run -d -p 8080:8080 --network network01 --name tomcat000ex01 tomcat

여기서 -p 8080:8080은 디버깅용으로 호스트에서 Tomcat을 직접 확인하기 위한 것이다. 실제 서비스에서는 외부에 Nginx만 노출하고 Tomcat 포트는 굳이 호스트로 열지 않아도 된다(같은 네트워크 안에서 Nginx가 접근하면 충분하다).

Tomcat 기본 페이지 404 — webapps가 비어 있다

여기서 한 가지 함정이 있다. 공식 Tomcat 이미지는 기본 웹앱(ROOT 등)을 webapps가 아니라 webapps.dist에 넣어 둔다. Tomcat의 문서 루트(appBase)는 server.xml에서 webapps로 정의돼 있는데, 정작 그 디렉터리가 비어 있어 기본 페이지에 접속하면 404가 뜬다.

설정값과 실제 디렉터리를 확인해 보자. 컨테이너 안에서 server.xmlappBasewebapps로 잡혀 있고, 그 디렉터리에는 아무것도 없는 것을 볼 수 있다.

docker exec -it tomcat000ex01 /bin/bash

# appBase 확인: <Host appBase="webapps" ... >
grep appBase conf/server.xml

# 비어 있는 webapps
ls -a /usr/local/tomcat/webapps

# 기본 앱은 webapps.dist 쪽에 있다
ls /usr/local/tomcat/webapps.dist
find / -name index.jsp 2>/dev/null

해결은 간단하다. webapps.dist의 기본 앱(ROOT 포함)을 webapps로 옮기면 된다.

# 방법 1) ROOT만 복사
mkdir -p /usr/local/tomcat/webapps/ROOT
cp -r /usr/local/tomcat/webapps.dist/ROOT/* /usr/local/tomcat/webapps/ROOT/

# 방법 2) 기본 앱 전체를 쓰려면 디렉터리째 교체
# rm -rf /usr/local/tomcat/webapps && mv /usr/local/tomcat/webapps.dist /usr/local/tomcat/webapps

복사 후 컨테이너를 재시작하면 Tomcat 기본 페이지가 정상적으로 뜬다.

docker restart tomcat000ex01

WEB 컨테이너(Nginx)와 리버스 프록시 설정

이제 WEB 서버인 Nginx 컨테이너를 같은 네트워크에 띄운다. 외부에서 들어오는 80 포트를 받고, 이 요청을 Tomcat으로 넘기는 것이 목표다.

docker run -d -p 80:80 --network network01 --name nginx000ex01 nginx

다음으로 Nginx 설정에 리버스 프록시를 추가한다. 컨테이너 안에서 직접 수정하려면 에디터가 필요한데, 공식 이미지에는 vi가 없으므로 먼저 설치한다(WSL Ubuntu 기반이라 apt를 쓴다).

docker exec -it nginx000ex01 /bin/bash
apt-get update
apt-get install -y vim
vi /etc/nginx/conf.d/default.conf

설정의 핵심은 upstream으로 Tomcat 컨테이너를 정의하고, server 블록의 location /에서 proxy_pass로 그쪽에 요청을 넘기는 것이다. 여기서 tomcat000ex01Tomcat 컨테이너 이름이며, 같은 사용자 정의 네트워크 덕분에 이름만으로 해석된다.

upstream tomcat_was {
    server tomcat000ex01:8080;
}

server {
    listen 80;
    server_name localhost;

    location / {
        proxy_pass http://tomcat_was;
        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;
    }
}

설정을 저장했으면 문법을 검사하고 Nginx를 리로드한다.

nginx -t
nginx -s reload

자주 하는 실수 — 네트워크를 빼먹으면 502

처음 연동을 시도할 때 가장 흔한 실수가 두 컨테이너를 같은 네트워크에 넣지 않은 것이다. 이 상태에서는 Nginx가 tomcat000ex01이라는 이름을 해석하지 못해 host not found in upstream 같은 오류가 나거나, 브라우저에 502 Bad Gateway가 뜬다.

이미 네트워크 없이 컨테이너를 띄웠다면, 다시 만들 필요 없이 기존 컨테이너를 네트워크에 연결하면 된다.

docker network connect network01 nginx000ex01
docker network connect network01 tomcat000ex01

# 같은 네트워크에 잘 붙었는지 확인
docker network inspect network01

docker-compose로 한 번에 묶기

위 과정을 명령어로 하나씩 실행했는데, 매번 컨테이너를 띄우고 네트워크에 붙이고 설정을 바꾸는 것은 번거롭다. docker-compose를 쓰면 네트워크 생성, 컨테이너 실행, 설정 파일 마운트를 한 파일에 선언해 두고 한 번에 올릴 수 있다.

먼저 호스트에 Nginx 설정 파일을 따로 만들어 둔다. compose에서 이 파일을 컨테이너로 마운트할 것이다. 서비스명이 곧 DNS 이름이 되므로, proxy_pass의 대상은 compose에서 정의할 서비스명 tomcat으로 적는다.

# ./nginx/default.conf
upstream tomcat_was {
    server tomcat:8080;
}

server {
    listen 80;
    server_name localhost;

    location / {
        proxy_pass http://tomcat_was;
        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;
    }
}

이제 docker-compose.yml을 작성한다. 두 서비스를 같은 네트워크에 두고, Nginx에는 위 설정 파일을 마운트한다. depends_on으로 Tomcat이 먼저 뜨도록 순서도 잡아 준다.

services:
  tomcat:
    image: tomcat:10.1.17
    container_name: tomcat000ex01
    networks:
      - network01

  nginx:
    image: nginx:1.25.3
    container_name: nginx000ex01
    ports:
      - "80:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - tomcat
    networks:
      - network01

networks:
  network01:
    driver: bridge

compose에서는 네트워크를 명시하면 자동으로 사용자 정의 네트워크가 만들어지고, 서비스명(tomcat, nginx)이 그대로 DNS 이름이 된다. 따로 docker network create를 하지 않아도 이름 기반 통신이 된다. 다음 명령으로 전체 스택을 한 번에 올린다.

docker compose up -d
docker compose ps

참고로 Tomcat 기본 페이지(404) 문제는 compose에서도 동일하게 발생할 수 있다. 실서비스라면 배포할 WAR/앱을 webapps에 마운트하거나, 커스텀 Dockerfile에서 webapps.distwebapps로 바꿔 이미지를 빌드해 두면 깔끔하다.

# Dockerfile (Tomcat 기본 앱 활성화 예시)
FROM tomcat:10.1.17
RUN rm -rf /usr/local/tomcat/webapps 
    && mv /usr/local/tomcat/webapps.dist /usr/local/tomcat/webapps

동작 확인

모든 컨테이너가 떴으면 브라우저에서 http://localhost로 접속한다. Nginx(80)가 요청을 받아 Tomcat(8080)으로 넘기고, Tomcat 기본 페이지가 나오면 WEB-WAS 연동이 정상 동작하는 것이다. 터미널에서도 빠르게 확인할 수 있다.

# Nginx를 거쳐 응답이 오는지 확인 (200이면 정상)
curl -I http://localhost

# 컨테이너 간 통신 직접 확인: Nginx에서 Tomcat 이름으로 접근
docker exec -it nginx000ex01 curl -I http://tomcat000ex01:8080
Docker로 Nginx와 Tomcat을 연동해 띄운 최종 화면

접속이 안 되면 다음 순서로 점검한다. ① 두 컨테이너가 같은 네트워크에 있는가(docker network inspect) ② Nginx proxy_pass의 대상이 Tomcat 컨테이너 이름과 정확히 같은가 ③ Tomcat webapps에 ROOT 앱이 있는가(없으면 404) ④ nginx -t 문법 오류는 없는가. 대부분 이 네 가지 안에서 해결된다.

자주 묻는 질문(FAQ)

왜 기본 bridge 네트워크로는 안 되나?

Docker가 기본 제공하는 bridge 네트워크에서는 컨테이너 이름으로 서로를 찾는 내장 DNS가 동작하지 않는다. 이름 기반 통신은 docker network create로 만든 사용자 정의 네트워크에서만 제공된다. 그래서 연동에는 직접 만든 네트워크나 compose가 자동 생성하는 네트워크를 쓴다.

Tomcat 포트(8080)를 호스트로 꼭 열어야 하나?

아니다. 외부에 노출할 것은 Nginx(80)뿐이고, Nginx는 같은 네트워크 안에서 tomcat:8080으로 접근하므로 Tomcat의 -p 8080:8080은 필수가 아니다. 디버깅 목적으로만 잠깐 열고, 운영에서는 닫아 두는 편이 보안상 낫다.

컨테이너 안에서 vi로 설정을 고치는 방식이 맞나?

학습·확인 단계에서는 괜찮지만, 컨테이너 내부 수정은 재시작·재배포 시 사라진다. 실무에서는 위 compose 예시처럼 설정 파일을 호스트에 두고 볼륨 마운트하거나 Dockerfile에 넣어 이미지로 굽는다. 그래야 같은 구성을 반복해서 재현할 수 있다.

정리

Docker로 WEB-WAS를 연동하는 흐름은 사용자 정의 네트워크 생성 → Tomcat·Nginx 컨테이너 실행 → Nginx 리버스 프록시(proxy_pass)로 컨테이너 이름 지정 → 동작 확인이며, 이걸 docker-compose 한 파일로 묶으면 네트워크·서비스·설정 마운트가 한 번에 선언된다. 막히는 지점은 거의 두 가지로 정해져 있다. 같은 네트워크에 안 넣어 이름이 해석되지 않는 경우(502), 그리고 Tomcat 기본 앱이 webapps.dist에 있어 비어 있는 webapps 때문에 404가 뜨는 경우다. 다음 글에서는 여기에 MariaDB까지 붙여 WEB-WAS-DB 3계층을 완성한다.

💬 댓글 0

💬 댓글을 남기려면?
첫 댓글을 남겨보세요 ✨

이 콘텐츠가 도움이 됐나요?

누스쿨 커뮤니티에서 더 많은 커리어 전략을 나눠요