Reco Everything you wanna value. 자세히보기

[IT|Programming]

핏메이트 (FitMate) - Linux (AWS Amazon Linux) 에 git 설치 및 nginx 설치 (초기 세팅들), Docker (Dockerfile and .dockerignore) and GitHub Action (main.yml)

kipid 2025. 6. 30. 00:19
반응형
# 핏메이트 (FitMate) - Linux (AWS Amazon Linux) 에 git 설치 및 nginx 설치 (초기 세팅들), Docker (Dockerfile and .dockerignore) and GitHub Action (main.yml) 코드잇 풀스택 2기 - Part 4 - Project-3 FitMate 개인 개발 리포트 (이강수 (kipid)) ## PH
  • 2024-12-31 : First posting.
## TOC ## 팀원별 구현 기능 상세 및 담당 업무 - 로그인 페이지 - 회원가입 페이지 - 프로필 등록 페이지 - 마이페이지 - 프로필 수정 페이지 - 알림 페이지 - AI 챗봇 페이지 --- - UserProvider/NotificationProvider/ViewportProvider - ErrorBoundary - GNB (Global Navigation Bar) - Input/InputPassword 컴포넌트 - Textarea/Button 컴포넌트 - ReviewCard/RatingAvgCard/RatingStatCard 컴포넌트 - Chip 컴포넌트 - 기본 정보 카드 템플릿 (강사님) - 리뷰 리스트 카드 템플릿 (강사님) - 좋아요 기능 (카드 템플릿 안에 들어감) --- - 프론트 배포 (Docker and GitHub Action (CI/CD)) - 시연 영상 발표 ## 문제해결 문제상황: 서버와 데이터를 주고받는 일이 많다보니까 tanstack-query 를 사용할 일이 많았는데, 데이터가 필요한 페이지마다 useQuery 를 쓰다보니 queryKey 도 다들 제각각이고 그러다보니 invalidateQueries 쓰는것도 복잡해짐. 원인분석: 파편화 된 것이 문제니까 한곳에 모아놓고 export 해서 써보자는 생각을 함. 해결방법: useQuery 나 useMutation 쓰는것을 파일로 분리해서 export 해서 써보기로 함. ```[.no-escape-HTML.lang-ts] import { useQuery } from "@tanstack/react-query"; import { ProfileData } from "@/types/types"; import { getUserInfo } from "../userService"; // 유저 프로필 조회 export const useGetUser = (userId: string) => { return useQuery<ProfileData>(["user-info", userId], () => getUserInfo(userId), { enabled: !!userId, cacheTime: 5 * 60 * 1000, staleTime: 5 * 60 * 1000, retry: 3, }); }; ```/ 배운점 및 개선사항: 파일로 분리해서 쓰다보니 queryKey 관리도 쉬워지고 여러모로 좋은점이 많았음. useMutateUser 같은 useMutation 도 이런식으로 사용하면 더 좋을거 같음. ```[.scrollable.lang-ts] // src/lib/api/queries/lesson.ts import toast from "react-hot-toast"; import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Lesson, LessonParams, LessonResult } from "@/types/lesson"; import { cancelLessonRequest, createDirectQuote, getLessonInfo, getMyLessonRequest, getReceiveRequest, } from "../lessonService"; // 레슨 요청 목록 조회 export const useGetReceivedLesson = ( userId: string, { keyword, order, sort, status, lesson_type, gender, region, has_direct_quote }: LessonParams, ) => { return useInfiniteQuery( [ "received-request", userId, { keyword, order, sort, status, lesson_type, gender, region, has_direct_quote }, ], ({ pageParam = 1 }) => getReceiveRequest({ page: pageParam, limit: 5, keyword, order, sort, status, lesson_type, gender, region, has_direct_quote, }), { getNextPageParam: (lastPage, allPages) => { return lastPage.hasMore ? allPages.length + 1 : undefined; }, }, ); }; // 내 레슨 전체 목록 조회 export const useGetMyLessons = ({ status }: LessonParams) => { return useQuery(["my-lesson"], () => getMyLessonRequest({ status })); }; // 내 레슨 조회 (무한 스크롤) export const useGetMyLessonList = (userId: string, { limit, status }: LessonParams) => { return useInfiniteQuery( ["my-lesson", userId, { limit, status }], ({ pageParam = 1 }) => getMyLessonRequest({ page: pageParam, limit, status, }), { getNextPageParam: (lastPage, allPages) => { return lastPage.hasMore ? allPages.length + 1 : undefined; }, }, ); }; // 레슨 상세 조회 export const useGetLesson = (lessonRequestId: string) => { return useQuery(["lesson-info", lessonRequestId], () => getLessonInfo(lessonRequestId), { enabled: !!lessonRequestId, }); }; // 지정 견적 요청 export const useDirectQuote = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ lessonId, trainerId }: { lessonId: string; trainerId: string }) => createDirectQuote(lessonId, trainerId), onSuccess: () => { queryClient.invalidateQueries(["received-request", "my-lesson"]); toast.success("지정 견적을 요청하였습니다."); }, onError: (error: any) => { console.error("견적 요청에 실패하였습니다.", error.message); toast.error("견적 요청에 실패하였습니다."); }, }); }; // 레슨 요청 취소 export const useCancelLesson = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (lessonId: string) => cancelLessonRequest(lessonId), onSuccess: () => { queryClient.invalidateQueries(["my-lesson"]); toast.success("레슨 요청을 취소하였습니다."); }, onError: (error: any) => { console.error("레슨 취소 중 문제가 발생했습니다.", error.message); toast.error("레슨 취소 중 문제가 발생했습니다."); }, }); }; ```/ ```[.scrollable.lang-ts] // src/lib/api/queries/notification.ts import { NotificationContextType } from "@/contexts/NotificationProvider"; import { useInfiniteQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { NotiParams, NotiResult } from "@/types/notis"; import { getNotiList, readNoti } from "../notiService"; export const useGetNotiList = (userId: string, { page, limit, order, sort }: NotiParams) => { return useInfiniteQuery( ["notifications", userId], ({ pageParam = 1 }) => getNotiList({ page: pageParam, limit, order, sort, }), { enabled: !!userId, getNextPageParam: (lastPage, allPages) => { return lastPage.hasMore ? allPages.length + 1 : undefined; }, cacheTime: 5 * 60 * 1000, staleTime: 5 * 60 * 1000, }, ); }; let readNotiTimeout: NodeJS.Timeout; export const useReadNotiMutation = (notiContext: NotificationContextType) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (notiId: number) => readNoti(notiId), onSuccess: () => { clearTimeout(readNotiTimeout); readNotiTimeout = setTimeout(() => { notiContext.clearNotifications(); queryClient.invalidateQueries(["notifications"]); }, 2000); }, onError: (error) => { console.error(error); }, }); }; ```/ ```[.scrollable.lang-ts] // src/lib/api/queries/quote.ts import { useRouter } from "next/router"; import toast from "react-hot-toast"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Quote, QuoteData } from "@/types/quote"; import { rejectedLesson } from "../lessonService"; import { acceptQuote, getQuote, rejectQuote, sendQuote } from "../quoteService"; // 견적 상세 조회 export const useGetQuote = (quoteId: string) => { return useQuery(["quote-detail", quoteId], () => getQuote(quoteId), { enabled: !!quoteId, }); }; // 견적 보내기 (트레이너) export const useSendQuote = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (quoteData: QuoteData) => sendQuote(quoteData), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["my-lesson"] }); toast.success("견적을 전송하였습니다."); }, onError: (error: any) => { console.error("견적 전송에 실패하였습니다.", error.message); toast.error("견적 전송에 실패하였습니다."); }, }); }; // 견적 반려 (트레이너) export const useRejectedQuote = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ lessonId, directQuoteRequestId, rejectionReason, }: { lessonId: string; directQuoteRequestId: string; rejectionReason: string; }) => rejectedLesson(lessonId, directQuoteRequestId, rejectionReason), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["received-request"] }); toast.success("요청을 반려하였습니다."); }, onError: (error: any) => { console.error("요청 반려에 실패하였습니다.", error.message); toast.error("요청 반려에 실패하였습니다."); }, }); }; // 견적 확정 (유저) export const useQuoteAccept = () => { const queryClient = useQueryClient(); const router = useRouter(); return useMutation({ mutationFn: (quoteId: string) => acceptQuote(quoteId), onSuccess: () => { toast.success("견적이 확정되었습니다."); queryClient.invalidateQueries({ queryKey: ["my-lesson"] }); router.push("/user/my-lesson/active-lesson"); }, onError: (err) => { console.error("견적 확정 실패", err); toast.error("견적 확정에 실패하였습니다."); }, }); }; // 견적 반려 (유저) export const useQuoteRejection = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (quoteId: string) => rejectQuote(quoteId), onSuccess: () => { toast.success("견적이 반려되었습니다."); queryClient.invalidateQueries(["my-lesson"]); }, onError: (err) => { console.error("견적 반려 실패", err); toast.error("견적 반려에 실패하였습니다."); }, }); }; ```/ ## EC2 접속 EC2 로 인스턴스 생성 (AWS Amazon Linux) free tier 로. Amazon Linux 에 접속 .ssh 이용. (public IP 는 재부팅시 항상 바뀌기 때문에 돈을 내고 Elastic IP 를 이용하는게 좋긴 함.) 아래와 같이 터미널/cmd 창에서 EC2 로 접속. 43.202.97.246 ```[.no-escape-HTML.lang-sh] # FE ssh -i "C:/RecoeveNet/.ssh/codeit-fs-key.pem" ec2-user@ec2-43-202-97-246.ap-northeast-2.compute.amazonaws.com # BE ssh -i "C:/RecoeveNet/.ssh/fitmate-be.pem" ec2-user@ec2-15-164-195-136.ap-northeast-2.compute.amazonaws.com ```/ ## Git 및 Nginx 설치 ```[.no-escape-HTML.lang-sh] sudo yum update -y sudo yum install git sudo yum install nginx # Amazon Linux sudo apt install nginx -y # Ubuntu (WSL) ```/ Git clone 하기. (Open Source 라서 따로 사용자 및 비번을 넣지 않아도 됨.) ```[.no-escape-HTML.lang-sh] git clone https://github.com/FS-part4-1team-FitMate/FitMate-FE.git # 특정 branch 로 pull 하기. git branch git checkout Feat-kipid git pull origin Feat-kipid ```/ ## Windows 11 의 Local WSL (Windows Subsystem for Linux) 에서 Back-End 실행하기 ```[.no-escape-HTML.lang-sh] wsl # Ubuntu 접속 cd ~ # Home directory 로 이동 git clone https://github.com/FS-part4-1team-FitMate/FitMate-BE.git # Clone Back-End repo git pull npx prisma migrate reset npx prisma migrate dev npm run seed npm run build npm run start ```/ ```[.no-escape-HTML.lang-sh] ip addr show eth0 # ip 확인 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 00:15:5d:84:de:a6 brd ff:ff:ff:ff:ff:ff inet 172.21.104.225/20 brd 172.21.111.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::215:5dff:fe84:dea6/64 scope link valid_lft forever preferred_lft forever # 위와 같이 뜨면 localhost:3000 이 아니라 172.21.104.225:3000 으로 연결해야 함. # Front-End 의 .env 에서 NEXT_PUBLIC_API_URL="http://172.21.104.225:3000" 으로 설정해줘야 함. # wsl --shutdown 후 # wsl 로 접속해야 ip 들이 재설정 되는듯? # PostgreSQL 이 Windows 에서 돌아가고 있을거기 때문에. # BE 의 .env 의 DATABASE_URL="postgresql://..." 도 바꿔줘야 함. # cmd 창에서 ipconfig 쳤을때 나오는 Windows ip 로. DATABASE_URL="postgresql://postgres:[Password]@[Windows local ip address]:5432/fitmate_dev?schema=public" # 방화벽 확인 sudo ufw status Status: active To Action From -- ------ ---- 80/tcp ALLOW Anywhere 443/tcp ALLOW Anywhere 22/tcp ALLOW Anywhere 3000 ALLOW Anywhere 3001/tcp ALLOW Anywhere 80/tcp (v6) ALLOW Anywhere (v6) 443/tcp (v6) ALLOW Anywhere (v6) 22/tcp (v6) ALLOW Anywhere (v6) 3000 (v6) ALLOW Anywhere (v6) 3001/tcp (v6) ALLOW Anywhere (v6) # 와 같이 떠야 함. sudo ufw allow 3000/tcp # 와 같이 Back-End port 를 허용해줘야 할 수 있음. ```/ ## Windows 11 의 Local WSL (Windows Subsystem for Linux) 에서 Front-End 실행하기 ```[.no-escape-HTML.lang-sh] wsl # Ubuntu 접속 cd ~ # Home directory 로 이동 git clone https://github.com/FS-part4-1team-FitMate/FitMate-BE.git # Clone Back-End repo git pull git checkout Feat-kipid # 자신의 branch 로 들어가야 자기가 작업한 것들을 불러올 수 있음. git pull npm run build npm run start ```/ ```[.no-escape-HTML.lang-sh] ip addr show eth0 # ip 확인 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 00:15:5d:84:de:a6 brd ff:ff:ff:ff:ff:ff inet 172.21.104.225/20 brd 172.21.111.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::215:5dff:fe84:dea6/64 scope link valid_lft forever preferred_lft forever # 위와 같이 뜨면 localhost:3001 이 아니라 172.21.104.225:3001 으로 연결해야 함. (어쩔땐 그냥 localhost:3001 로도 접속이 되긴 했는데, 컴퓨터를 껐다 키니까 안됨 ㅡ,.ㅡ;;) ```/ ## Docker 설치 방법 (Amazon Linux 2) ```[.no-escape-HTML.lang-sh] sudo yum update -y # Amazon Linux 2 sudo yum install -y docker sudo systemctl enable docker sudo systemctl start docker sudo systemctl status docker docker ps docker images ```/ Docker 만 깔면 Node.js 및 pm2 설치는 넘어가도 됨. Dockerfile 을 다음과 같이 작성. ```[.no-escape-HTML.lang-sh] # Use the official Node.js LTS image for building FROM node:lts AS builder # Set working directory WORKDIR /app ARG NEXT_PUBLIC_KAKAO_API_KEY ENV NEXT_PUBLIC_API_URL="https://fitmate-be.asia" ENV NODE_ENV="production" ENV NEXT_PUBLIC_KAKAO_API_KEY=${NEXT_PUBLIC_KAKAO_API_KEY} # Install dependencies (only production deps for runtime) COPY package.json package-lock.json ./ RUN npm ci # Copy the rest of the application files COPY . . # Build the Next.js app RUN npm run build # Use a minimal runtime image FROM node:lts AS runtime # Create a non-root user # RUN addgroup -S appgroup && adduser -S appuser -G appgroup # Set working directory WORKDIR /app # pm2 글로벌 설치 RUN npm install -g pm2 # Copy necessary files from the builder stage COPY --from=builder /app/package.json /app/package-lock.json ./ COPY --from=builder /app/.next ./.next COPY --from=builder /app/public ./public COPY --from=builder /app/node_modules ./node_modules # Ensure correct permissions # RUN chown -R appuser:appgroup /app # Switch to non-root user # USER appuser # 환경 변수 설정 ENV NEXT_PUBLIC_API_URL="https://fitmate-be.asia" ENV NODE_ENV="production" ENV PORT=3001 # Expose application port EXPOSE 3001 # Define runtime command CMD ["pm2-runtime", "start", "npm", "--", "start"] ```/ .dockerignore 도 다음과 같이 작성. ```[.no-escape-HTML.lang-sh] # 운영체제 파일 .DS_Store Thumbs.db # Node.js 관련 파일 (예: 로컬 개발 환경에서 설치된 모듈) node_modules/ .next/ .github/ .git/ # 빌드 아티팩트 dist/ build/ # 로그 파일 *.log # 기타 불필요한 파일 *.md *.txt Dockerfile ```/ 다음과 같은 명령어들로 Docker 실행. HTTPS 설정이랑 Nginx 설정은 따로 해줘야 함. ```[.no-escape-HTML.lang-sh] # 1. Docker 이미지 빌드 (빌드는 Local 컴퓨터에서 하는게 좋아보임. 서버 성능이 좋지 않다면 특히나...) docker login # https://hub.docker.com/ 의 ID/Password 를 입력해야 함. # .env 를 production 에 맞춰서 바꿔준 뒤, build 해야 함. docker build -t kipid/fitmate-fe:v0.2 . docker push kipid/fitmate-fe:v0.2 # 2. 컨테이너 실행 (EC2 에 터미널로 연결해서) docker login # https://hub.docker.com/ 의 ID/Password 를 입력해야 함. docker images # 안쓰는 이미지 삭제 docker stop $(docker ps -aq) docker system prune -a docker rmi <이미지 ID 또는 이미지 이름> docker pull kipid/fitmate-fe:v0.2 docker stop fitmate-fe-container docker rm fitmate-fe-container docker run -d -p 3001:3001 --name fitmate-fe-container kipid/fitmate-fe:v0.2 docker run -d -p 3001:3001 --name fitmate-fe-container \ -e "NEXT_PUBLIC_API_URL='https://fitmate-be.asia'" \ -e "NODE_ENV='production'" \ -e "NEXT_PUBLIC_KAKAO_API_KEY='...'" \ kipid/fitmate-fe:v0.2 # Nginx 시작 sudo systemctl start nginx # Docker 중단 docker stop <컨테이너_ID 또는 컨테이너_이름> ```/ ## CI/CD (Continuous Integration, Continuous Delivery/Deployment) .github/workflows/main.yml 파일에 다음과 같이 입력하고 저장 and Git push Settings - Secrets and variables - Actions - Repository secrets 에 New repository secret 에다 비번같은걸 등록해야 함! Environment secrets 이 아님! 이걸로 한참 고생함. Organization secrets 도 아님. ```[.no-escape-HTML.lang-sh] name: CI/CD Pipeline # 워크플로우의 이름을 'CI/CD Pipeline'으로 설정 on: # 워크플로우 트리거 조건 설정 push: # Git push 이벤트 발생 시 실행 branches: ["Feat-kipid"] # Feat-kipid 브랜치에 push가 발생했을 때만 실행 jobs: # 실행할 작업들을 정의하는 섹션 build-and-deploy: # 작업 이름을 'build-and-deploy'로 설정 runs-on: ubuntu-latest # Ubuntu 최신 버전의 러너에서 작업 실행 steps: # 작업의 세부 단계들을 정의 - name: Checkout code # 코드 체크아웃 단계 uses: actions/checkout@v3 # GitHub 저장소 코드를 가져오는 공식 액션 사용 - name: Build Docker image # Docker 이미지 빌드 단계 env: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} run: | echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin # Docker Hub 로그인 (보안을 위해 secrets 사용) docker --version sleep 3 docker build \ --build-arg "NEXT_PUBLIC_API_URL=https://fitmate-be.asia" \ --build-arg "NODE_ENV=production" \ --build-arg "NEXT_PUBLIC_KAKAO_API_KEY=${{ secrets.NEXT_PUBLIC_KAKAO_API_KEY }}" \ -t kipid/fitmate-fe:v0.2 . || exit 1 # Dockerfile을 사용하여 이미지 빌드, 태그는 v0.2로 지정 sleep 3 docker push kipid/fitmate-fe:v0.2 # 빌드된 이미지를 Docker Hub에 업로드 sleep 3 docker logout continue-on-error: false - name: Deploy to server # 서버 배포 단계 uses: appleboy/ssh-action@master env: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} with: host: ${{ secrets.SERVER_IP }} username: ${{ secrets.SERVER_USERNAME }} key: ${{ secrets.SERVER_PRIVATE_KEY }} port: 22 script: | echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin docker pull kipid/fitmate-fe:v0.2 # 최신 이미지 다운로드 docker stop $(docker ps -a -q) || true docker rm -f $(docker ps -a -q) || true docker stop fitmate-fe-container || true # 기존 컨테이너 중지 (실패해도 계속 진행) docker rm fitmate-fe-container || true # 기존 컨테이너 제거 (실패해도 계속 진행) docker run -d -p 3001:3001 --name fitmate-fe-container \ -e "NEXT_PUBLIC_API_URL=https://fitmate-be.asia" \ -e "NODE_ENV=production" \ -e "NEXT_PUBLIC_KAKAO_API_KEY=${{ secrets.NEXT_PUBLIC_KAKAO_API_KEY }}" \ kipid/fitmate-fe:v0.2 sleep 5 # 컨테이너 실행 안정화 대기 docker system prune -f # 사용하지 않는 Docker 리소스 정리 sleep 3 ps -ef | grep docker # Check running processes docker logout ```/ Settings - Secrets and variables - Actions 들어가서 Environment secrets 저장해주면 끝. ## Node.js 설치 ```[.no-escape-HTML.lang-sh] curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash nvm install --lts ```/ ## Redis server 띄우기 Redis server 까는 법은 ChatGPT 에게 문의. ```[.no-escape-HTML.lang-sh] redis-server --daemonize yes redis-cli ping # Expected PONG ```/ ## pm2 start package.json 은 다음과 같이.. port 3001 로 start. ```[.no-escape-HTML.lang-sh] "scripts": { "dev": "next dev -p 3001", "build": "next build", "start": "next start -p 3001", "lint": "next lint" }, ```/ ```[.no-escape-HTML.lang-sh] npm i pm2 -g npm run build pm2 start "npm run start" pm2 start npm -- name 'my-app' -- start "pm2:start": "pm2 start npm -- name 'my-app' -- start" // npm run pm2:start pm2 list pm2 stop 0 pm2 restart 0 pm2 delete 0 and so on. ```/ ## HTTPS 연결로 만들기 및 Nginx 설정 ```[.no-escape-HTML.lang-sh] sudo yum install -y certbot sudo systemctl stop nginx sudo certbot certonly --standalone -d fitmate.asia sudo systemctl start nginx // 매 3개월? 마다 위 명령어를 실행해줘야 함. cron-job? 같은 자동화도 된다는거 같은데 나중에 공부해봐야겠음. // 업데이트 후 10분정도 기다려야 보안 인증서가 제대로 작동하는듯? sudo certbot certonly --standalone -d fitmate-be.asia ```/ 성공적으로 인증서를 발급받으면 /etc/letsencrypt/live/fitmate.asia/ 에 인증서 파일이 저장됨. ```[.no-escape-HTML.lang-sh] # Nginx 설정 파일 편집 sudo vi /etc/nginx/nginx.conf sudo vi /etc/nginx/conf.d/fitmate.asia.conf sudo vi /etc/nginx/conf.d/fitmate-be.asia.conf ```/ ```[.no-escape-HTML.lang-sh] server { listen 80; server_name fitmate.asia www.fitmate.asia; # Redirect HTTP to HTTPS return 301 https://$host$request_uri; } server { listen 443 ssl; server_name fitmate.asia www.fitmate.asia; ssl_certificate /etc/letsencrypt/live/fitmate.asia/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/fitmate.asia/privkey.pem; location / { proxy_pass http://localhost:3000; # 애플리케이션 포트 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } } ```/ ```[.no-escape-HTML.lang-sh] server { listen 80; server_name fitmate-be.asia www.fitmate-be.asia; # Redirect HTTP to HTTPS return 301 https://$host$request_uri; } server { listen 443 ssl; server_name fitmate-be.asia www.fitmate-be.asia; ssl_certificate /etc/letsencrypt/live/fitmate-be.asia/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/fitmate-be.asia/privkey.pem; location / { proxy_pass http://localhost:3001; # 애플리케이션 포트 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } } ```/ 전체 파일을 들여다 보면 다음과 같음. ```[.no-escape-HTML.lang-sh] # For more information on configuration, see: # * Official English Documentation: http://nginx.org/en/docs/ # * Official Russian Documentation: http://nginx.org/ru/docs/ user nginx; worker_processes auto; error_log /var/log/nginx/error.log notice; pid /run/nginx.pid; # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. include /usr/share/nginx/modules/*.conf; events { worker_connections 1024; } http { log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; keepalive_timeout 65; types_hash_max_size 4096; include /etc/nginx/mime.types; default_type application/octet-stream; # Load modular configuration files from the /etc/nginx/conf.d directory. # See http://nginx.org/en/docs/ngx_core_module.html#include # for more information. include /etc/nginx/conf.d/*.conf; server { listen 80; listen [::]:80; server_name fitmate.asia www.fitmate.asia; root /usr/share/nginx/html; # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; # Redirect HTTP to HTTPS return 301 https://$host$request_uri; error_page 404 /404.html; location = /404.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } } # Settings for a TLS enabled server. server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name fitmate.asia www.fitmate.asia; root /usr/share/nginx/html; ssl_certificate "/etc/letsencrypt/live/fitmate.asia/fullchain.pem"; ssl_certificate_key "/etc/letsencrypt/live/fitmate.asia/privkey.pem"; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_ciphers PROFILE=SYSTEM; ssl_prefer_server_ciphers on; # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; location / { proxy_pass http://localhost:3001; # 애플리케이션 포트 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } error_page 404 /404.html; location = /404.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } } } ```/ ```[.no-escape-HTML.lang-sh] sudo systemctl start nginx sudo vi /etc/nginx/nginx.conf ============================ server { listen 80 ...... location / { proxy_pass http://127.0.0.1:3000; 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; } } ============================ sudo nginx -t sudo systemctl restart nginx ```/ ## Typescript of APIspec ```[.no-escape-HTML.lang-sh] export type APISpec<BU = any, P = any, B = any, T = any> = { method: 'GET' | 'POST' | 'PATCH' | 'DELETE' baseURL: BU path: P body?: B response: T } export type APISpecs< T extends { [K in string]: APISpec } > = T export type ProductAPI = APISpecs<{getProducts: { method: 'GET', baseURL: 'http://localhost:3100', path: '/products' response: Product[] }}> ```/ ## ESLint 무효화 ```[.no-escape-HTML.lang-sh] // eslint-disable-next-line @typescript-eslint/no-explicit-any ```/ ## RRA
  1. https://www.docker.com/
반응형