들어가며,

notion-blog-api를 만들고 EC2로 배포하기 위해서 설정하는 과정을 정리해보았다.

Dockerfile

# syntax=docker/dockerfile:1 FROM python:3.13-slim AS base ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 WORKDIR /app RUN apt-get update \ && apt-get install -y --no-install-recommends build-essential libpq-dev \ && rm -rf /var/lib/apt/lists/* COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
각 설정이 어떤 역할인지 알고 싶다면?
# syntax=docker/dockerfile:1
Dockerfile문법 버전을 지정.
 
FROM python:3.13-slim AS base
  • 베이스 이미지 설정.
  • python:3.13-slim → Python 3.13이 설치된 경량(debian 기반) 이미지.
  • AS base → 멀티 스테이지 빌드를 위해 이름 붙인 것 (여기서는 실제로 스테이지는 하나라 필요는 없음)
 
ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1
  • PYTHONDONTWRITEBYTECODE=1 → .pyc (바이트코드 캐시 파일) 생성하지 않음 → 컨테이너 크기 줄어듦.
  • PYTHONUNBUFFERED=1 → 출력(stdout/stderr)이 버퍼링되지 않음 → 로그가 바로바로 출력됨.
  • PIP_NO_CACHE_DIR=1 → pip install 시 캐시 저장 안 함 → 이미지 크기 줄어듦.
 
WORKDIR /app
  • 컨테이너 안에서 작업 디렉토리/app으로 설정.
  • 이후 COPY, RUN, CMD 같은 명령은 전부 /app을 기준으로 실행됨.
 
RUN apt-get update \ && apt-get install -y --no-install-recommends build-essential libpq-dev \ && rm -rf /var/lib/apt/lists/*
  • 필요한 시스템 패키지 설치
    • apt-get update → 패키지 목록 갱신.
    • build-essential → C 컴파일러 등 빌드 도구 모음 (일부 파이썬 패키지가 빌드할 때 필요).
    • libpq-dev → PostgreSQL 클라이언트 라이브러리 (예: psycopg2 설치 시 필요).
    • -no-install-recommends → 최소한의 패키지만 설치.
    • rm -rf /var/lib/apt/lists/* → apt 캐시 삭제 → 이미지 크기 최적화.
 
COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt
  • 파이썬 의존성 설치
    • requirements.txt를 먼저 복사 → pip install 실행.
    • 이렇게 하면 코드가 바뀌더라도, 의존성이 같으면 Docker 캐시를 재사용할 수 있어서 빌드 속도가 빨라짐.
 
COPY . .
  • 현재 디렉토리(.)의 모든 파일을 컨테이너의 /app에 복사.
  • 즉, 소스코드를 컨테이너 안으로 넣는 단계.
 
EXPOSE 8000
  • 컨테이너가 사용할 포트(8000) 를 선언.
  • 실제로 포트를 열어주는 건 아니고, 문서화/표시 역할.
  • docker run -p 8000:8000 해야 호스트에서 접근 가능.
 
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
  • 컨테이너 실행 시 기본 실행 명령어.
  • uvicorn app.main:app → app/main.py 안의 app 객체(FastAPI 인스턴스)를 실행.
  • -host 0.0.0.0 → 외부 접속 허용.
  • -port 8000 → 8000번 포트에서 서버 실행.
 
 

docker-compose.yml

version: "3.9" services: db: image: postgres:15 environment: POSTGRES_DB: appdb POSTGRES_USER: appuser POSTGRES_PASSWORD: apppass ports: - "5432:5432" volumes: - pgdata:/var/lib/postgresql/data web: build: . depends_on: - db environment: DATABASE_URL: postgresql+psycopg2://appuser:apppass@db:5432/appdb NOTION_API_KEY: ${NOTION_API_KEY} NOTION_POST_DATABASE_ID: ${NOTION_POST_DATABASE_ID} ports: - "8000:8000" volumes: - ./static:/app/static volumes: pgdata:

Docker 실행

Docker build

docker compose --env-file .env up --build -d
 
open http://localhost:8000/docs
 
docker exec -it 53f4fdaf48a3 psql -U notion_blog_user -d notion_blog
 

코드 수정 후 재배포

docker compose up -d --build web

EC2 배포하기

EC2 인스턴스 생성

EC2 Docker 설치

EC2 SSH로 연결하기

chmod 400 "aws-ec2-keypair.pem" ssh -i "aws-ec2-keypair.pem" ubuntu@ec2-3-35-11-18.ap-northeast-2.compute.amazonaws.com
 

EC2 배포 자동화

name: Deploy to EC2 (docker-compose) on: push: branches: ["main"] jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Add EC2 host to known_hosts run: | mkdir -p ~/.ssh ssh-keyscan -p "${{ secrets.EC2_PORT }}" -H "${{ secrets.EC2_HOST }}" >> ~/.ssh/known_hosts - name: Deploy over SSH uses: appleboy/ssh-action@v1.2.0 with: host: ${{ secrets.EC2_HOST }} username: ${{ secrets.EC2_USER }} key: ${{ secrets.EC2_SSH_KEY }} port: ${{ secrets.EC2_PORT }} script_stop: true script: | set -e cd ${{ secrets.EC2_APP_DIR }} if [ ! -d .git ]; then echo "ERROR: not a git repo at ${{ secrets.EC2_APP_DIR }}" && exit 1; fi git remote set-url origin ${{ secrets.GIT_REPO_URL }} git fetch --all --prune git checkout main git pull --ff-only if [ ! -f .env ]; then echo "ERROR: .env not found on server" && exit 1; fi docker compose pull || true docker compose up -d --build docker image prune -f || true
 

Question

💡
AWS EC2로 할 떄, 로컬 데이터베이스를 사용하는게 좋을까?
아니면 외부 DB를 RDS로 배포하는게 나을까?
 
💡
코드를 수정하고 매번 Docker compose up하나?
아니면 로컬에서 개발하다가 Docker 올려보고 테스트 해보나?
 
💡
Github Secret에 ec2-keypair를 올려도 되는걸까?
다른 방법이 있다면 어떤 방법이 안전할까?
 

Error

🚨
unable to get image 'postgres:15': permission denied while trying to connect to the Docker daemon socket at
🔽 EC2 기본 계정이 ubuntu라면 $USER 대신 ubuntu가 도커 그룹에 포함되어야 한다.
sudo systemctl enable --now docker sudo usermod -aG docker $USER newgrp docker # 또는 로그아웃/재로그인
🚨
EC2에서 /notion/sync api로 들어가보니 다음과 같은 에러가 나왔다.
{ "detail": "Method Not Allowed" }
/notion/sync는 POST 전용이다.
브라우저 주소창 등으로 GET 요청을 보내서 405(Method Not Allowed)가 난 것이다.
 

RDS 분리하기

EC2 서버에서 RDS DATABASE_URL로 연결이 되는지 먼저 확인해보려면 어떻게 할 수 있을까?
dig +short[RDS URL] nc -vz [RDS URL] 5432
  • nc/pg_isready “succeeded” → 네트워크/보안그룹 OK.
  • timeout → RDS 보안그룹 인바운드 5432에 EC2 인스턴스의 보안그룹이 소스로 추가됐는지 확인.
  • password authentication failed → 유저/비밀번호/DB명 확인.
  • SSL 오류 → sslmode=require 추가.