Physicist, Programmer. What you eat, how you think, and most importantly what you have done become who you are. Who are you? and who will you be?
[IT/Programming]
Linux (AWS Amazon Linux) 에 git 설치 및 nginx 설치 (초기 세팅들), Docker (Dockerfile and .dockerignore) and CI/CD GitHub Action (main.yml)
kipid2025. 2. 15. 13:46
반응형
# Linux (AWS Amazon Linux) 에 git 설치 및 nginx 설치 (초기 세팅들), Docker (Dockerfile and .dockerignore) and GitHub Action (main.yml)
EC2 로 인스턴스 생성 (AWS Amazon Linux) free tier 로.
Amazon Linux 에 접속 .ssh 이용. (public IP 는 재부팅시 항상 바뀌기 때문에 돈을 내고 Elastic IP 를 이용하는게 좋긴 함.)
아래와 같이 터미널/cmd 창에서 EC2 로 접속.
43.202.97.246
```[.linenums.lang-sh]
# FE
ssh -i "codeit-fs-key.pem" ec2-user@ec2-43-202-97-246.ap-northeast-2.compute.amazonaws.com
# BE
ssh -i "fitmate-be.pem" ec2-user@ec2-15-164-195-136.ap-northeast-2.compute.amazonaws.com
```/
## PH
2024-12-31 : First posting.
## TOC
## Git 및 Nginx 설치
```[.linenums.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 라서 따로 사용자 및 비번을 넣지 않아도 됨.)
```[.linenums.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 실행하기
```[.linenums.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
```/
```[.linenums.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 실행하기
```[.linenums.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
```/
```[.linenums.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)
```[.linenums.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 을 다음과 같이 작성.
```[.linenums.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 도 다음과 같이 작성.
```[.linenums.lang-sh]
# 운영체제 파일
.DS_Store
Thumbs.db
# Node.js 관련 파일 (예: 로컬 개발 환경에서 설치된 모듈)
node_modules/
.next/
.github/
.git/
# 빌드 아티팩트
dist/
build/
# 로그 파일
*.log
# 기타 불필요한 파일
*.md
*.txt
Dockerfile
```/
다음과 같은 명령어들로 Docker 실행. HTTPS 설정이랑 Nginx 설정은 따로 해줘야 함.
```[.linenums.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='83c6b0fde64b363bd52e26fd41f2fbd6'" \
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 도 아님.
```[.linenums.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 설치
```[.linenums.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 에게 문의.
```[.linenums.lang-sh]
redis-server --daemonize yes
redis-cli ping # Expected PONG
```/
## pm2 start
package.json 은 다음과 같이.. port 3001 로 start.
```[.linenums.lang-sh]
"scripts": {
"dev": "next dev -p 3001",
"build": "next build",
"start": "next start -p 3001",
"lint": "next lint"
},
```/
```[.linenums.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 설정
```[.linenums.lang-sh]
sudo yum install -y certbot
sudo certbot certonly --standalone -d fitmate.asia
sudo certbot certonly --standalone -d fitmate-be.asia
```/
성공적으로 인증서를 발급받으면 /etc/letsencrypt/live/fitmate.asia/ 에 인증서 파일이 저장됨.
```[.linenums.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
```/
```[.linenums.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;
}
}
```/
```[.linenums.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;
}
}
```/
전체 파일을 들여다 보면 다음과 같음.
```[.linenums.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 {
}
}
}
```/
```[.linenums.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
```[.linenums.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 무효화
```[.linenums.lang-sh]
// eslint-disable-next-line @typescript-eslint/no-explicit-any
```/
## RRA