[Nginx] Nuxt 3000포트 번호 안겹치게 디플로이하는 방법 (with Docker)

JeongYun Lee·2025년 1월 14일
0

Developing

목록 보기
5/7

Nuxt는 기본적으로 3000포트로 열린다. Flask는 5000포트, FastAPI는 8000포트 등 backend도 다 각자의 기본 포트들이 있다. 따라서 도커 환경에서 여러개의 프로젝트를 열어줄 때 포트 번호가 겹치지 않도록 해야 하므로 Nuxt의 포트를 3001로 바꿔서 도커라이징 해주려고 하는데, 여러 시도를 해도 잘 되지 않았다. 따라서 내부에서는 3000으로 열지만, 외부 접근을 할 때에는 3001로 접근해서 열리도록 했다.

또한 현재 하나의 웹 서버에서 여러개의 Nuxt 프로젝트를 docker로 nginx(로컬 자체의 것이 아닌 도커라이징할 때 설정해주는 nginx)의 한 포트에서 front와 back을 모두 연결해서 열어주려고 한다. 즉, Project1은 nginx의 기본포트인 80, 443으로, 프로젝트2는 81, 442로 연다는 의미이다.

정리하면 이 세가지를 적용해준다는 의미이다.

Project1 (default)
1️⃣ nginx: 80, 443
2️⃣ Nuxt 포트: 3000
3️⃣ FastAPI 포트: 8000 + /api라는 prefix를 붙여줌

Project2
1️⃣ Nginx 포트: 81, 442
2️⃣ Nuxt 포트: 3001
3️⃣ Flask 포트: 5000 + /projects/api/라는 prefix를 붙여줌

Project1은 default 포트로 열어주면 되므로, 이 글에서는 Project2의 포트를 바꿔서 dockerizing하는 방법을 알아보겠다. 주요 파일 구조는 다음과 같다. frontend, backend라는 폴더 안에 각각 Dockerfile과 .env 파일이 있다.

├── docker-compose.yml
├── nginx.conf
├── frontend
	└── Dockerfile
	└── nuxt.config.ts
    └── .env
├── backend
	└── Dockerfile
	└── main.py 
    └── .env

참고로, 도커라이징할 때 사용하는 프로젝트 폴더의 nginx.conf와 실제로 디플로이하는 서버 로컬의 nginx.conf는 다른 파일이니 헷갈리지 말기!

이 글을 쓰면서 다시 생각한건데... localhost:3001나 localhost:5000 이런식으로 굳이 프론트와 백을 하나씩 열어서 볼 필요가 없다면 사실 3001로 외부접근을 설정할 필요는 없는 것 같다. 3000으로 열어도 내부에서 여는거라서 다른 컨테이너와 겹치지 않을거 같다는 생각...

1. root 폴더

docker-compose.yml

version: "3.8"

services:
  nginx:
    image: nginx:alpine
    ports:
      - "81:81"  
      - "442:442" 
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - frontend
      - backend
    networks:
      - app-network
    restart: unless-stopped

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    expose:
      - "3000"  # 실제 내부 포트
    ports:
      - "3001:3000"  # 호스트 3001 포트를 컨테이너의 3000 포트로 매핑
    environment:
      - NUXT_PORT=3000  # Nuxt 앱이 3000 포트에서 실행되도록 설정
      - NUXT_HOST=0.0.0.0  # 모든 IP에서 접근 가능하도록 설정
    env_file:
      - ./frontend/.env
    depends_on:
      - backend
    networks:
      - app-network

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    expose:
      - "5000"
    ports:
     - "5000:5000"
    env_file:
      - ./backend/.env
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

이 파일이 핵심이다. nginx는 81, 442로 바로 열어주고, pront는 내부 포트는 3000으로 열지만, 실제 외부 접속은 3001로 해주었다(외부 접속이 필요 없다면 ports 부분은 삭제해도 됨). backend는 똑같이 5000으로 열어준다.

nginx.conf

server {
    listen 81;
    server_name localhost;

    # 정적 파일 처리
    location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot|json)$ {
        proxy_pass http://frontend:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        expires 1y;
        add_header Cache-Control "public, no-transform";
        access_log off;
    }

    # 정확한 경로 매칭을 위한 리다이렉트
    location = /projects/chatbot {
        return 301 $scheme://$host$uri/;
    }

    # 프론트엔드 설정
    location /projects/chatbot/ {
        proxy_pass http://frontend:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        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;

        # 타임아웃 설정
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;

        # 연결 재시도 설정
        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;

        # CORS 헤더
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Credentials true;
        add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
        add_header Access-Control-Allow-Headers 'Origin, Content-Type, Accept, Authorization';
    }

    # API 요청 설정 (나머지 설정 동일)
    location /projects/chatbot/api/ {
        proxy_pass http://backend:5000/api/;
        proxy_http_version 1.1;
        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;

        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain; charset=utf-8';
            add_header 'Content-Length' 0;
            return 204;
        }
    }

    # 에러 페이지 설정
    error_page 502 /502.html;
    location = /502.html {
        return 502 'Sorry, the application is temporarily unavailable. Please try again later.';
    }
}

frontend 부분만 신경쓰면 되는데, 내부에서는 3000으로 연결해주기 때문에 이 부분에서도 3000으로 연결해준다. front와 back을 각각 서브 디렉토리 경로가 있으므로 prefix를 붙여준다. 호출 엔드포인트를 구분하고, api가 여러개인 경우 한번에 처리(라우팅할 때 등) 처리할 수 있으므로 /api/도 추가해줬다..env 파일이 있는 부분의 경로도 다시 확인해주면 끝이다.

2. frontend 폴더

Dockerfile

# Stage 1: Build the application
FROM node:latest AS builder

WORKDIR /app
COPY package*.json ./
RUN yarn install --production

# 애플리케이션 파일 복사
COPY . .

# 애플리케이션 빌드
RUN npm run build 

# Stage 2: Create production image
FROM node:18

# 작업 디렉토리 설정
WORKDIR /app

# builder 스테이지에서 필요한 파일만 복사
COPY --from=builder /app/.output /app/.output

# 포트 설정
EXPOSE 3000

# Nuxt 애플리케이션 시작
CMD ["node", ".output/server/index.mjs"]

사실 처음에는 프론트 자체를 3001로 열어주고 싶었기 때문에 여기서도 EXPOSE 부분에 3001로 설정해주었지만, 적용되지 않았다. 찾아보니 nuxt.config.ts의 설정에 덮어씌워져서 그런 것 같았다.

nuxt.config.ts

export default defineNuxtConfig({
  compatibilityDate: "2024-04-03",
  devtools: { enabled: true },

  modules: [
    "@nuxtjs/tailwindcss", 
  ],

  css: ["prismjs/themes/prism.css"],

  app: {
    baseURL: '/projects/chatbot/', 
  },
  
  runtimeConfig: {
    public: {
      serverPort: 3001,  // 애플리케이션을 3001번 포트에서 실행하도록 설정
      serverHost: '0.0.0.0',  // 모든 IP에서 접근 가능하도록 설정
    },
  },
  server: {
    port: 3000,     // 내부 포트
    host: '0.0.0.0'  // 외부에서 접근 가능
  }
});

하지만, 여기서도 이렇게 3001로 설정해줘도 여전히 적용되지 않았다. 이것도 각종 이유가 있는거 같은데 도커 내부에서 다른 프로세스와 적용이 잘 되지 않았다던가 default 포트를 보다 강제로 바꿔줘야 하는 것 같았다. 그래서 결국 그냥 내부에서는 3000으로 열었던 건데, 처음에 언급했듯이 어쩌피 nginx의 81로 감싸서 실행할 것이기 때문에 외부 접근이 필요 없다면 상관 없는 부분이다.

즉, 그냥 아래와 같이 작성해도 된다는 것이다.

export default defineNuxtConfig({
  compatibilityDate: "2024-04-03",
  devtools: { enabled: true },

  modules: [
    "@nuxtjs/tailwindcss", 
  ],

  css: ["prismjs/themes/prism.css"],

  app: {
    baseURL: '/projects/chatbot/', 
  }
});

3. backend 폴더

Dockerfile

FROM python:3.12-slim

RUN apt-get update && apt-get install -y \
    build-essential \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY requirements.txt .

RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000

CMD ["gunicorn", "--bind", "0.0.0.0:5000", "main:app"]

여긴 별거 없다. 똑같이 5000으로 열어주면 됨.

main.py

import os
from flask import Flask, request, jsonify
from flask_cors import CORS
from flask_restx import Api, Resource

app = Flask(__name__)

# CORS 설정
origins = [
    "http://localhost:3000", 
    "http://xxxxxx.xxx.xxx", 
    "http://xxxxxx.xxx.xxx/projects/chatbot", 
    "xxx.xxx.xxx.xx", 
]
CORS(app, resources={r"/api/*": {"origins": origins}}, supports_credentials=True)

# 초기 사용자 ID 설정
current_user_id = None

@app.route('/api/set_user_id', methods=['POST'])
def set_user_id():
	# 함수 작성
 
    return jsonify({"status": "ID received successfully"}), 200

@app.route('/api/', methods=['POST', 'GET'])
def get_answer():
	# 함수 작성
    return 
    
if __name__ == '__main__':
    app.run('0.0.0.0', port=5000, debug=True)

Flask에서 api를 작성해주는 부분을 신경써줘야 한다. 우선 origins 부분은 CORS 에러를 막기 위해서 설정하는 부분이다. 연결되는 다양한 도메인들, 로컬에서 실행하는 경우 로컬의 포트도 설정해준다. 그리고 api.route 부분에도/api/라는 prefix를 설정해줘야 한다. 앞에서 docker 설정하는 부분에 모든 backend에서 이 경로로 설정해줬기 때문에!


이렇게까지 설정해주고, docker-compose build --no-cachedocker-compose up -d를 실행해준뒤, localhost:81/projects/chatbot 으로 접속하면 제대로 접속이 될 것이다.

결론은, 직접적으로 front와 back에 접근하고 싶은 경우(도커라이징을 한 뒤에는 거의 없을거 같은데, back의 swagger ui를 확인하고 싶은 경우 정도?) docker-compose.yml에서 ports를 통해서 이전에 사용하지 않은 포트를 외부 접속 포트(외부접속포트:내부접속포트)로 설정해주면 되지만, 이 글에서 했듯 nginx로 한번에 열어서 접속하는 경우에는 이전에 사용한 포트와 겹칠 걱정을 하지 않고 그대로 적용하면 된다.
.
.
.
참 또 이렇게 얼레벌레 웹서버 공부를 한다 🤣

profile
궁금한 건 많지만, 천천히 알아가는 중입니다

0개의 댓글