psycopg2-binary / psycopgpoetry remove psycopg2-binary celery django-redis redisnginx.conf를 보면 HTTP에 해당하는 80번 포트만 열려있음nginx.conf 수정# Nginx의 서버 블록을 정의합니다.
server {
# 80번 포트(HTTP)로 들어오는 요청을 대기합니다.
listen 80;
# 처리할 도메인 주소나 IP를 지정합니다. (일단 모든 요청을 받도록 언더스코어(_) 사용)
server_name _;
# 클라이언트가 /static/ 경로로 요청을 보낼 때 처리하는 블록입니다.
location /static/ {
# Django를 거치지 않고, Nginx가 공유받은 볼륨 경로에서 직접 파일을 찾아 반환합니다. (응답 속도 최적화)
alias /app/staticfiles/;
}
# 정적 파일 외의 모든 일반적인 웹 요청(/)을 처리하는 블록입니다.
location / {
# 요청을 백엔드(Django Gunicorn) 컨테이너의 8000번 포트로 그대로 넘겨줍니다. (프록시)
proxy_pass http://backend:8000;
# 원래 클라이언트가 요청한 도메인(Host) 정보를 백엔드에 전달합니다.
proxy_set_header Host $host;
# 클라이언트의 실제 IP 주소를 백엔드에 전달하여, 백엔드가 방문자 IP를 알 수 있게 합니다.
proxy_set_header X-Real-IP $remote_addr;
# 여러 프록시를 거쳤을 경우의 IP 체인 정보를 백엔드에 전달합니다.
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
——————————————————————————————————————[비교]—————————————————————————————————————————
# -------------------------------------------------------------------
# [1] HTTP 요청을 HTTPS로 리다이렉트하는 서버 블록입니다.
# -------------------------------------------------------------------
server {
# 80번 포트(HTTP)로 들어오는 요청을 대기합니다.
listen 80;
# 처리할 도메인 주소나 IP를 지정합니다. (실제 도메인으로 변경해야 합니다. 예: myblog.com)
server_name _;
# 들어온 모든 요청을 HTTPS 주소로 영구 이동(301 상태 코드) 시킵니다.
return 301 https://$host$request_uri;
}
# -------------------------------------------------------------------
# [2] HTTPS(443) 보안 요청을 처리하는 메인 서버 블록입니다.
# -------------------------------------------------------------------
server {
# 443번 포트(HTTPS)로 들어오는 요청을 대기하며, SSL을 사용하도록 설정합니다.
listen 443 ssl;
# 처리할 도메인 주소를 지정합니다. (위와 동일하게 실제 도메인으로 변경 필요)
server_name _;
# [중요] 발급받은 SSL 인증서(공용 키) 파일의 경로를 지정합니다. (Let's Encrypt 기준)
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
# [중요] 발급받은 SSL 인증서의 개인 키 파일 경로를 지정합니다.
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# 클라이언트가 /static/ 경로로 요청을 보낼 때 처리하는 블록입니다. (기본 프로필 이미지 서빙용)
location /static/ {
# Django를 거치지 않고, Nginx가 공유받은 볼륨 경로에서 직접 파일을 찾아 반환합니다.
alias /app/staticfiles/;
}
# 정적 파일 외의 모든 일반적인 웹 요청(/)을 처리하는 블록입니다.
location / {
# 요청을 백엔드(Django Gunicorn) 컨테이너의 8000번 포트로 그대로 넘겨줍니다.
proxy_pass http://backend:8000;
# 원래 클라이언트가 요청한 도메인(Host) 정보를 백엔드에 전달합니다.
proxy_set_header Host $host;
# 클라이언트의 실제 방문자 IP 주소를 백엔드에 전달합니다.
proxy_set_header X-Real-IP $remote_addr;
# 여러 프록시를 거쳤을 경우의 IP 체인 정보를 백엔드에 전달합니다.
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 백엔드(Django)가 현재 요청이 HTTP가 아닌 HTTPS로 안전하게 들어왔음을 알 수 있게 전달합니다.
proxy_set_header X-Forwarded-Proto $scheme;
}
}
docker-compose.prod.yml 파일 Nginx 부분 수정 # 호스트(EC2)의 80번(HTTP) 포트로 들어오는 요청을 Nginx 컨테이너의 80번 포트로 연결합니다.
ports:
- "80:80"
# 백엔드 컨테이너와 볼륨을 공유하기 위해 설정합니다.
volumes:
# 로컬에 작성할 Nginx 설정 파일을 컨테이너의 설정 파일로 덮어씌웁니다.
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
# 백엔드가 모아둔 정적 파일을 Nginx가 읽을 수 있도록 동일한 볼륨을 마운트합니다.
- static_volume:/app/staticfiles
——————————————————————————————————————[비교]—————————————————————————————————————————
ports:
# 호스트의 80포트(HTTP)를 컨테이너 80포트로 연결합니다. (리다이렉트 용도)
- "80:80"
# 호스트의 443포트(HTTPS)를 컨테이너 443포트로 연결합니다. (실제 서비스 용도)
- "443:443"
# 백엔드 컨테이너 및 호스트와 파일/폴더를 공유하기 위해 설정합니다.
volumes:
# 로컬에 작성한 Nginx 설정 파일을 컨테이너의 기본 설정 파일로 덮어씌웁니다.
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
# 백엔드가 모아둔 정적 파일(기본 프로필 이미지)을 읽을 수 있도록 볼륨을 마운트합니다.
- static_volume:/app/staticfiles
# [추가됨] 호스트(EC2)에 설치된 Let's Encrypt 인증서 폴더를 Nginx가 읽기 전용(ro)으로 참조하도록 마운트합니다.
- /etc/letsencrypt:/etc/letsencrypt:ro
t2.micro (프리티어)로 충분하긴함 Gunicorn 워커 3개와 DB, Nginx 를 동시에 돌리기엔t3.small (램 2GB) 이상을 권장PuTTY 등)을 이용해 다운로드한 키페어(.pem)로 EC2에 원격 접속Docker와 Docker Compose를 설치 필요Git Clone.env 생성.env 파일을 서버에 직접 만들어야 함vi .env 명령어로 파일을 열고 Django Secret Key , DB 비밀번호, S3 키 등을 꼼꼼히 채워 넣음docker-compose.prod.yml의 /etc/letsencrypt 경로에 인증서가 있다는 전제하에 동작Certbot 실행Certbot을 설치하고 Let's Encrypt 인증서를 발급받아야 함sudo docker compose -f docker-compose.prod.yml up --build -d




[인스턴스 시작] 버튼을 누름

OS (Amazon Machine Image)Ubuntu(우분투)를 클릭하고Ubuntu 24.04 LTS 또는 22.04 LTS를 선택 (가장 대중적이고 자료가 많음)t3.micro를 선택t3.small 등으로 올려도 됨
★매우 중요[새 키 페어 생성]을 누름[키 페어 생성]을 누름
.pem 파일은 컴퓨터에 다운로드되는데 서버에 접속할 수 있는 '마스터 키'임
[편집]을 눌러 상세 화면으로 들어간 경우[보안 그룹 규칙 추가] 버튼을 눌러서 직접 아래의 3가지 규칙을 만들어 줌
[인스턴스 시작] 클릭
[네트워크 및 보안] -> [탄력적 IP]를 클릭[탄력적 IP 주소 할당]을 누르고[할당]을 누름 (새로운 고정 IP가 생김)[작업] -> [탄력적 IP 주소 연결]을 누름
blog-server-prod)가 뜸, 그걸 선택
[연결]을 누르면 끝사이트 접속 및 가입
원하는 주소 검색

도메인 확장자 선택

.com이나 .co.kr이지만 (보통 1년에 1.5만 원 ~ 2만 원 선).xyz, .site, .shop 처럼 장바구니 및 결제


[My가비아]를 클릭
[관리] 버튼을 누름
[DNS 관리] 또는 [DNS 설정] 메뉴로 들어감
[DNS 설정] 버튼을 누른 뒤, [레코드 추가]를 클릭하여 아래처럼 입력[확인] 및 [저장]을 누르면 끝
.pem파일)가 있는 폴더로 이동 (다운로드 폴더에 있다면 아래 명령어 입력 후 엔터)cd ~/Downloadsblog-key.pem)은 본인 파일명에 맞게 변경chmod 400 blog-key.pemssh -i "blog-key.pem" ubuntu@탄력적IP주소Are you sure you want to continue connecting (yes/no)? 라고 물어보는 문구가 뜸yes 라고 타이핑한 후 엔터ubuntu@ip-172-31-... 같은 글자가 떴다면 완벽하게 접속에 성공
t3.micro 서버는 깨끗한 '새 컴퓨터' 상태도커(Docker)를 설치하고스왑 메모리(가상 램) 최적화 작업을 진행# 1. 우분투의 패키지(앱) 목록을 최신 상태로 업데이트
sudo apt-get update
# 2. 도커를 설치할 때 통신에 필요한 필수 시스템 패키지들을 설치 (-y는 묻지 않고 설치하겠다는 뜻)
sudo apt-get install -y ca-certificates curl gnupg lsb-release
# 3. 안전한 다운로드를 위해 도커의 공식 GPG 키(인증서)를 서버의 안전한 폴더에 저장
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
# 4. 도커 공식 다운로드 주소(저장소)를 우분투 서버가 알 수 있도록 리스트에 추가
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 5. 새로운 저장소가 추가되었으니 패키지 목록을 다시 한번 업데이트
sudo apt-get update
# 6. 대망의 도커 엔진과 도커 컴포즈 최신 버전을 서버에 설치 (시간이 조금 걸림)
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# 현재 접속 중인 'ubuntu' 유저를 'docker' 그룹에 포함시킵니다.
sudo usermod -aG docker $USER
# [중요] 방금 부여한 권한을 적용하려면 서버에서 한 번 나갔다가 다시 접속해야 합니다!
# exit을 치면 터미널 창이 내 컴퓨터로 빠져나옵니다.
exit
ssh -i "blog-key.pem" ubuntu@탄력적IP 이용하여 재접속 t3.micro는 램이 1GB밖에 없어서# 1. 하드디스크에서 2기가바이트(2G) 만큼의 공간을 떼어내어 '/swapfile'이라는 빈 파일을 만듬
sudo fallocate -l 2G /swapfile
# 2. 보안을 위해 이 가상 램 파일은 관리자(root)만 읽고 쓸 수 있도록 권한을 제한
sudo chmod 600 /swapfile
# 3. 방금 만든 빈 파일을 시스템이 인식할 수 있는 '스왑 메모리 공간'으로 포맷(초기화)
sudo mkswap /swapfile
# 4. 포맷된 스왑 메모리를 시스템에 즉시 활성화하여 적용
sudo swapon /swapfile
# 5. 서버를 껐다 켜도 스왑 메모리가 유지되도록 시스템 설정 파일(fstab)의 맨 아랫줄에 내용을 추가
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
docker compose version 이라고 쳐보자Docker Compose version v2... 라고 뜨면 성공
# 1. 깃허브에서 내 프로젝트 코드를 서버로 다운로드(복제) 합니다.
# (주의: 아래 주소를 본인의 실제 깃허브 레포지토리 주소로 꼭 바꿔서 입력해 주세요!)
git clone https://github.com/본인아이디/프로젝트이름.git
# 2. 다운로드가 완료되면, 방금 생성된 프로젝트 폴더 안으로 이동합니다.
# (본인의 프로젝트 폴더 이름으로 바꿔주세요!)
cd 프로젝트이름
# 1. 현재 폴더에 '.env'라는 이름의 텍스트 파일을 만들고, 편집기를 엽니다.
nano .env
.env 파일의 내용(Django Secret Key, DB 비밀번호, S3 정보 등)을 모두 복사해서# 1. Certbot을 설치하기 위해 우분투의 앱스토어격인 snapd를 최신 상태로 업데이트합니다.
sudo snap install core; sudo snap refresh core
# 2. SSL 인증서를 발급해 줄 Certbot 프로그램을 설치합니다.
sudo snap install --classic certbot
# 3. Certbot 명령어를 서버 어디서든 쉽게 쓸 수 있도록 바로가기(심볼릭 링크)를 만듭니다.
sudo ln -s /snap/bin/certbot /usr/bin/certbot
# 4. [★매우 중요★] 아직 Nginx 웹 서버가 켜지기 전이므로, 내장된 임시 웹서버(standalone)를 이용해
# 아까 구매하신 도메인(coding-garden.site)에 대한 인증서를 발급받습니다.
sudo certbot certonly --standalone -d coding-garden.site


# -f : 실행할 도커 설정 파일을 지정합니다.
# up : 컨테이너들을 켭니다.
# --build : 켜기 전에 도커 이미지를 최신 코드로 새로 굽습니다(빌드).
# -d : 백그라운드(데몬) 모드로 실행하여 터미널 창을 꺼도 서버가 계속 돌아가게 합니다.
sudo docker compose -f docker-compose.prod.yml up --build -d
# 켜진 컨테이너들의 실시간 로그를 확인하는 명령어입니다. (빠져나올 때는 Ctrl + C)
sudo docker compose -f docker-compose.prod.yml logs -f
psycopg.OperationalError: connection failed: connection to server at "127.0.0.1", port 5432 failed: Connection refused
cannot load certificate "/etc/letsencrypt/live/yourdomain.com/fullchain.pem": No such file or directory
# Nginx 설정 파일을 엽니다. (경로가 다를 경우 실제 경로로 이동하세요)
nano nginx/nginx.conf
도메인 주소 수정
# 실행 중인 컨테이너와 네트워크를 모두 중지하고 삭제합니다.
sudo docker compose -f docker-compose.prod.yml down
# 수정된 설정값을 가지고 다시 빌드하여 실행합니다.
sudo docker compose -f docker-compose.prod.yml up --build -d
[Settings] 를 누릅니다.[Secrets and variables] -> [Actions]를 클릭합니다.[New repository secret] 버튼을 눌러서 아래 3개를 각각 만들어주세요.cat 본인키이름.pem 사용ssh -i 키.pem ubuntu@IP주소 라고 쳤음.pem 파일 안에 적힌 암호화된 긴 텍스트 전체를 복사해서 붙여넣기name: Deploy to EC2
# 어떤 상황일 때 이 스크립트를 실행할지 정합니다.
on:
push:
branches: [ "main" ] # main 브랜치에 코드가 push(또는 merge) 될 때만 작동합니다.
jobs:
deploy:
runs-on: ubuntu-latest # 깃허브가 빌려주는 임시 컴퓨터(우분투)에서 실행됩니다.
steps:
- name: SSH로 EC2에 접속해서 배포 명령어 실행하기
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
# 아래 script 부분이 깃허브 로봇이 서버에 접속해서 대신 쳐줄 명령어들입니다.
script: |
cd ~/indi_Blog_Project
git pull origin main
sudo docker compose -f docker-compose.prod.yml down
sudo docker compose -f docker-compose.prod.yml up --build -d
sudo docker system prune -f
[네트워크 및 보안] -> [보안 그룹]을 클릭합니다.[인바운드 규칙] -> [인바운드 규칙 편집]을 누릅니다.SSH (포트 22)인 줄을 찾습니다.[위치 무관 (Anywhere-IPv4)] (또는 0.0.0.0/0)으로 변경[규칙 저장]을 누릅니다.http://127.0.0.1:8000/api/v1/user/login-page/?code=07dfc64f122e47b76db1
class GithubLoginAPIView(APIView):
permission_classes = [AllowAny]
def get(self, request):
client_id = settings.GITHUB_CLIENT_ID
redirect_uri = "http://127.0.0.1:8000/api/v1/user/login-page/"
...
——————————————————————————————————————[비교]—————————————————————————————————————————
from django.urls import reverse
class GithubLoginAPIView(APIView):
permission_classes = [AllowAny]
def get(self, request):
client_id = settings.GITHUB_CLIENT_ID
# 2. reverse로 'login_page'의 경로를 찾고, build_absolute_uri로 현재 도메인이 포함된 절대 경로를 동적으로 생성합니다.
# 이렇게 하면 로컬/배포 환경에 상관없이 현재 서버의 도메인이 올바르게 반영됩니다.
redirect_uri = request.build_absolute_uri(reverse('login_page'))
def get(self, request):
client_id = settings.DISCORD_CLIENT_ID
# 프론트엔드 로그인 페이지로 돌아가도록 설정
redirect_uri = "http://127.0.0.1:8000/api/v1/user/login-page/?provider=discord"
# URL에 들어갈 수 있도록 특수문자(:, /, ? 등)를 안전하게 인코딩합니다.
encoded_redirect_uri = urllib.parse.quote(redirect_uri)
——————————————————————————————————————[비교]—————————————————————————————————————————
class DiscordLoginAPIView(APIView):
# 누구나 접근 가능한 뷰입니다.
permission_classes = [AllowAny]
def get(self, request):
# 디스코드 클라이언트 ID를 가져옵니다.
client_id = settings.DISCORD_CLIENT_ID
# 1. 기본 로그인 페이지 URL을 현재 호스트에 맞춰 동적으로 생성합니다.
base_redirect_uri = request.build_absolute_uri(reverse('login_page'))
# 2. 디스코드 콜백임을 식별하기 위해 쿼리 파라미터를 덧붙입니다.
redirect_uri = f"{base_redirect_uri}?provider=discord"
# 3. URL에 들어갈 수 있도록 특수문자(:, /, ? 등)를 안전하게 인코딩합니다.
encoded_redirect_uri = urllib.parse.quote(redirect_uri)
def post(self, request):
# GET.get이 아닌 data.get으로 body에 담긴 JSON 데이터를 꺼냅니다.
code = request.data.get("code")
# 토큰을 요청할 때도 위(APIView)에서 적었던 주소와 완벽히 똑같아야 합니다!
redirect_uri = "http://127.0.0.1:8000/api/v1/user/login-page/?provider=discord"
——————————————————————————————————————[비교]—————————————————————————————————————————
def post(self, request):
# 인가 코드를 가져옵니다.
code = request.data.get("code")
# 1. 토큰 요청 시에도 권한 요청 시 보냈던 완벽히 동일한 redirect_uri를 전달해야 합니다.
# 따라서 현재 환경에 맞는 동적 URL을 다시 한번 만들어 줍니다.
base_redirect_uri = request.build_absolute_uri(reverse('login_page'))
redirect_uri = f"{base_redirect_uri}?provider=discord"
GitHub: Developer Settings -> OAuth Apps -> Authorization callback URL 수정
Discord: Developer Portal -> Applications -> OAuth2 -> Redirects 수정
request.build_absolute_uri()http://127.0.0.1:8000/api/v1/user/login-page/ 로 자동 생성https://my-blog.com으로 유저가 접속하면https://my-blog.com/api/v1/user/login-page/ 로 자동 생성