[AWS] Docker와 GitHub Actions를 활용한 CI/CD 파이프라인 구축과 환경 변수 문제 해결
FastAPI로 개발한 백엔드 애플리케이션을 Docker 컨테이너로 배포하는 과정에서 환경 변수 관련 문제가 발생했다. 리액트와 자바는 환경변수 키 파일을 포함한 채로 빌드 배포해도 문제가 없었기에 도커에서도 당연히 될 거라 생각했는데, 도커를 이용한 배포에서는 환경변수가 자동으로 적용되지 않는다는 것을 몰랐다. 실행할 때 키를 하나하나 입력해서 실행하는 방법도 가능하지만 번거롭고 키 개수가 늘어날 수도 있어서, 기존에는 로컬에서 Docker 이미지를 빌드하고 EC2 서버에 수동으로 배포하는 방식을 사용했다. 하지만 이 과정을 자동화하기 위해 GitHub Actions를 도입했다. 이 글에서는 이러한 문제를 해결한 과정을 정리해보고자 한다.
1. 문제 상황
애플리케이션이 EC2 서버에서 다음과 같은 오류를 발생시켰다.
httpcore.LocalProtocolError: Illegal header value b'Bearer '
이는 OpenAI API 호출 시 인증 토큰이 비어있거나 잘못 설정되었음을 의미한다. 로컬 개발 환경에서는 정상적으로 작동했지만, Docker 컨테이너로 배포한 후에는 오류가 발생했다.
2. 원인 파악
문제의 근본 원인은 Docker 컨테이너에 환경 변수가 제대로 전달되지 않았기 때문이다. 기존 배포 방식에서는 .env 파일을 프로젝트에 포함시켜 배포했으나, Docker 컨테이너는 이 파일을 자동으로 환경 변수로 로드하지 않는다. 나는 env파일을 넣은채로 빌드 했으니까 당연히 환경변수가 적용될거라고 생각했다.
# 애플리케이션 코드에서의 환경 변수 사용
import os
from dotenv import load_dotenv
# 환경변수 로드
load_dotenv()
# OpenAI API 키 가져오기
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
이 코드는 .env 파일이 존재하고 적절한 권한으로 접근 가능할 때만 정상 작동한다. Docker 컨테이너 내부에서는 .env 파일이 없거나 파일은 있어도 환경 변수로 등록되지 않아 os.getenv("OPENAI_API_KEY")가 None을 반환하게 된다.
3. 해결 과정 - GitHub Actions CI/CD 구축
3.1 GitHub Actions 워크플로우 설정
문제 해결을 위해 GitHub Actions를 활용한 CI/CD 파이프라인을 구축하기로 했다. 다음과 같은 워크플로우 파일(.github/workflows/deploy.yml)을 생성했다
name: Build and Deploy
on:
push:
branches: [ main ] # 또는 master, develop 등 배포할 브랜치
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: DOCKER_USERNAME/backend-app:latest
- name: Deploy to EC2
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
# 기존 컨테이너 정리
docker stop backend-app-container || true
docker rm backend-app-container || true
# 기존 이미지 삭제 (필요한 경우)
docker rmi DOCKER_USERNAME/backend-app:latest || true
# 사용하지 않는 이미지, 컨테이너, 볼륨 등 정리 (선택 사항)
docker system prune -f
# 최신 이미지 가져오기
docker pull DOCKER_USERNAME/backend-app:latest
# 환경 변수와 함께 컨테이너 실행 (메모리 제한 포함)
docker run -d -p 8000:8000 --name backend-app-container \
--memory="256m" --memory-swap="512m" \
--restart=on-failure \
-e MONGO_URI="${{ secrets.MONGO_URI }}" \
-e OPENAI_API_KEY="${{ secrets.OPENAI_API_KEY }}" \
-e OPENAI_API_BASE="https://api.openai.com/v1" \
-e OPENAI_MODEL_NAME="gpt-4.1-mini" \
-e HOST="0.0.0.0" \
-e PORT="8000" \
-e DEBUG="False" \
-e LOG_LEVEL="INFO" \
-e LOG_FILE="app.log" \
-e DB_HOST="localhost" \
-e DB_PORT="27017" \
-e DB_NAME="database_name" \
-e DB_USER="${{ secrets.DB_USER }}" \
-e DB_PASSWORD="${{ secrets.DB_PASSWORD }}" \
-e SESSION_SECRET="${{ secrets.SESSION_SECRET }}" \
-e SESSION_EXPIRE_DAYS="7" \
-e MYSQL_HOST="YOUR_DB_HOST" \
-e MYSQL_PORT="3306" \
-e MYSQL_USER="YOUR_DB_USER" \
-e MYSQL_PASSWORD="${{ secrets.MYSQL_PASSWORD }}" \
-e MYSQL_DB="YOUR_DB_NAME" \
DOCKER_USERNAME/backend-app:latest
# 컨테이너 실행 확인
docker ps | grep backend-app-container
# 로그 확인 (최근 10줄)
sleep 5
docker logs --tail 10 backend-app-container
# 사용하지 않는 이미지 정리 (dangling 이미지 제거)
docker image prune -f
3.2 GitHub Secrets 설정
환경 변수를 안전하게 관리하기 위해 GitHub 저장소의 Settings > Secrets and variables > Actions에서 다음 시크릿들을 설정했다.
- DOCKER_USERNAME: Docker Hub 사용자 이름
- DOCKER_PASSWORD: Docker Hub 비밀번호
- EC2_HOST: EC2 서버 IP 주소
- EC2_USERNAME: EC2 SSH 사용자 이름
- EC2_SSH_KEY: EC2 SSH 개인 키
- MONGO_URI: MongoDB 연결 문자열
- OPENAI_API_KEY: OpenAI API 키
- DB_USER: 데이터베이스 사용자 이름
- DB_PASSWORD: 데이터베이스 비밀번호
- SESSION_SECRET: 세션 시크릿 키
- MYSQL_PASSWORD: MySQL 비밀번호
4. 배포 및 검증
GitHub Actions 워크플로우를 설정한 후, GitHub의 main 브랜치에 코드를 푸시하여 자동 배포 프로세스를 시작했다. 워크플로우는 다음 단계를 수행한다.
- 코드 체크아웃
- Docker Hub 로그인
- Docker 이미지 빌드 및 푸시
- EC2 서버에 SSH 접속
- 기존 컨테이너 및 이미지 정리
- 새 이미지 다운로드
- 환경 변수와 함께 컨테이너 실행
빌드 및 배포가 성공적으로 완료되면 초록색 체크 아이콘이 생긴다.
빌드 및 배포가 되는 자세한 진행 로그도 확인할수있다.
마무리
이 문제를 통해 Docker 컨테이너에서 환경 변수를 관리하는 방법과 GitHub Actions를 활용한 CI/CD 파이프라인 구축의 중요성을 배웠다. 또한 민감한 정보를 GitHub Secrets에 저장하여 안전하게 관리하는 방법도 익힐 수 있었다.
- Docker 컨테이너는 환경 변수를 명시적으로 전달해야 한다. .env 파일은 자동으로 로드되지 않는다.
- GitHub Actions를 활용하면 빌드, 배포 과정을 자동화하여 개발 효율성을 높일 수 있다.
- 서버 로그와 디버깅 정보를 충분히 남기는 것이 문제 해결에 큰 도움이 된다.
이제 코드를 main 브랜치에 푸시하기만 하면 자동으로 EC2 서버에 최신 버전이 배포된다. 이로써 수동 배포 과정에서 발생할 수 있는 실수를 줄이고, 개발-배포 주기를 크게 단축할 수 있어서 편리해졌다.