개발이군고구마

[멱살잡고 DevOps] 3. Backend Dockerfile (docker compose/docker hub) 본문

Cloud Deploy

[멱살잡고 DevOps] 3. Backend Dockerfile (docker compose/docker hub)

김구황 2025. 7. 17. 08:49
728x90

아래와 같은 순서대로 배포 프로세스를 잡아갈 예정이다. 

[Backend Dockerfile] <- 현단계 
1. backend Dockerfile 작성 🎆 
[AWS Front 설정]
2. S3 버킷 & CloudFront 배포 설정
[AWS EC2 설정]
3. EC2 인스턴스 프로비저닝
4. EC2 환경 구성 🎆
[CI/CD] 
5. GitHub Actions CI/CD 구축 🎇
[테스트와 확장]
6. DNS 설정 및 최초 배포 테스트
7. 배포 환경 분리 (dev/prod)

 

우선 아래 사항들은 사전에 준비가 완료되어야 한다. 

  • AWS 계정, IAM 사용자/권한 확인 (프리티어로 사용예정) 
  • 도메인 준비(예: 가비아) 및 DNS 관리 권한 확보
  • GitHub 저장소(backend, frontend) 및 Docker Hub 계정 
  • MongoDB Atlas 설정

 

IAM 사용자 권한 

ㄴ GitHub Actions에서 AWS에 접근하기 위한 전용 IAM 사용자 생성

(예: AmazonS3FullAccess의 특정 버킷 버전, CloudFrontFullAccess 등)


1. nginx 설정 파일

events {
}
# 이벤트 설정 블록을 정의하며, 워커 프로세스 연결 관련 설정을 포함합니다.

http {
# HTTP 관련 설정 블록을 시작합니다.

    upstream api {
    # 'api'라는 이름의 업스트림 그룹을 정의합니다.
        server backend:5000;
        # backend 컨테이너의 5000 포트를 API 처리 서버로 지정합니다.
    }

    server {
    # 가상 서버 설정을 정의합니다.

        listen 80;
        # HTTP 기본 포트(80)에서 요청을 수신합니다.

        server_name 도메인이름;
        # 해당 도메인 이름에 대한 요청을 처리합니다.

        location /api/ {
        # '/api/' 경로로 들어오는 요청을 처리하는 위치 블록을 시작합니다.
            proxy_pass http://api;
            # upstream 'api'로 모든 /api/ 요청을 전달합니다.

            proxy_set_header Host $host;
            # 원본 요청의 Host 헤더를 백엔드로 전달합니다.
            proxy_set_header X-Real-IP $remote_addr;
            # 클라이언트의 실제 IP 주소를 백엔드로 전달합니다.
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            # 프록시 체인에 클라이언트 IP 정보를 추가하여 전달합니다.
            proxy_set_header X-Forwarded-Proto $scheme;
            # 요청이 사용한 프로토콜(http/https)을 백엔드로 전달합니다.
        }
    }
}

 

💠 nginx는 별도의 Dockerfile을 생성하지 않음

 

  • 공식 이미지의 장점 -> 보안 유지보수 편리 
  • 현 사이트는 리버스 프록시와 기본 헤더·캐싱 설정 정도만 사용, 즉 커스텀 이미지 (모듈추가, 런타임 변경 불가 패키지 포함 등) 만들필요 없음 
  • HTTPS 종단(Offload)을 CloudFront에서 처리함 
  • 추가 모듈·패키지 요구사항이 없으므로, 매번 빌드할 이유 없이 “풀-매니지드” 형태로 운영이 편리

 

 

2. 환경변수 파일 

MONGO_URI="mongodb+srv://[사용자]:[비번]@[클러스터이름]/mern?retryWrites=true&w=majority&appName=[app이름]"
PORT=5000

 

 

3. Dockerfile

👁‍🗨 멀티 스테이징으로 build/run 하여 이미지 경량화 수정 필요 
# 1. 베이스 이미지
FROM node:18-alpine

# 2. 작업 디렉토리
WORKDIR /usr/src/app

# 3. 패키지 설치 (캐시 활용)
COPY package*.json ./
RUN npm install --production # 

# 4. 소스 복사
COPY . .

# 5. (선택) 빌드 타임 기본값 설정
#    .env가 없을 때만 이 기본값을 사용합니다.
# 포트는 공개하지 않는 것이 더 좋다고 함 

# 6. 포트 개방
EXPOSE ${PORT}

# 7. 시작 명령
CMD ["node", "app.js"]

 

 

4. docker-compose.yml

version: '3.8'

services:
  backend:
    build:
      context: .
      dockerfile: Dockerfile
    # 로컬에서 .env 파일 쓸 경우 이렇게 참조 가능
    env_file:
      - .env
    ports:
      - "${PORT}:${PORT}"
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    depends_on:
      - backend
    ports:
      - "80:80"
      - "443:443"
    # nginx.conf 파일을 프로젝트 루트/nginx/nginx.conf 로 두고 마운트
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro

 

 

5. dockerignore

# Node.js dependencies - 로컬에 설치된 패키지 제외
node_modules

# 환경 변수 파일 (비밀 정보)
.env

# IDE, OS 메타데이터
.vscode
.DS_Store

# 로그
npm-debug.log*
yarn-debug.log*

 

 

6. docker 이미지 빌드해보기 

# 이미지 빌드
docker build --tag allweneedfarm-backend:local --file Dockerfile .

# 빌드 로그 & 이미지 목록 확인  
docker images allweneedfarm-backend:local

# 컨테이너 실행 
docker run -d --name test-backend -e PORT=5000 -p 5000:5000 allweneedfarm-backend:local

# 도커 컴포즈 빌드 backend와 nginx가 함께 올라가는지 점검
docker-compose up --build

 

 

7. Docker 이미지 빌드 & 레지스트리 업로드

❎  Nginx 는 따로 docker hub에 올릴 필요 없나? 

 

공식 Nginx 이미지를 쓰고 있다면 이미 Docker Hub에 있으니까

  • 직접 빌드하거나 관리하지 않아도
  • docker-compose up 하면 자동으로 최신 버전을 pull

커스텀 이미지가 아닐 때는 푸시가 불필요

  • 직접 Dockerfile을 작성해서 Nginx를 커스터마이징하지 않았다면
  • 별도의 이미지 빌드·태깅·푸시 과정 없이 바로 공식 이미지를 참조만 하면 충분

 

# 이미지 빌드
docker build --tag {도커hub아이디}/{이미지이름}:{태그} --file Dockerfile .

# v1.0.0 은 릴리즈 버전 규칙(Semantic Versioning) 배포 자동화 
docker tag {도커hub아이디}/{이미지이름}:{태그} {도커hub아이디}/{이미지이름}:v1.0.0

# 이미지 푸시 
docker push {도커hub아이디}/{이미지이름}:{태그}
docker push {도커hub아이디}/{이미지이름}:v1.0.0

 

 

현재까지 진행상황 -


 

[빌드 과정에서 발생한 오류 모음]

💥 ✘ Container backend-nginx-1 발생 

ㄴ Docker Desktop 설정 변경 (WSL based integration으로)

 

💥 컨테이너 실행 오류 
docker: Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint test-backend: Bind for 0.0.0.0:5000 failed: port is already allocated

docker: Error response from daemon: Conflict. The container name "/test-backend" is already in use by container "3c138c1b807615f4730721835c76ccefe5d306f53b9d8d93df543154aad414f3". You have to remove (or rename) that container to be able to reuse that name.

ㄴ 기존에 실행되었던 포트/컨테이너가 제거가 되지 않아서 발생하는 오류 

# 1) 현재 실행 중인 컨테이너 확인 docker ps
# → 만약 test-backend 외에 5000번을 쓰고 있는 컨테이너가 있다면,
docker stop <CONTAINER_ID>
docker rm <CONTAINER_ID>

# (1) 중지된 포함 모든 컨테이너 목록에서 확인
docker ps -a --filter "name=test-backend"
# (2) 필요하다면 컨테이너 중지
docker stop test-backend
# (3) 컨테이너 제거
docker rm test-backend

 

 

🌐 이미지 빌딩 완료 

 

백엔드와 nginx가 이미지가 작동하는 컨테이너 

 

 

💥 컨테이너 health 체크 오류 

ㄴ app.js에 404 처리용 “catch-all” 미들웨어가 잘못 작성

// 변경 전
// router를 미들웨어로
app.use("/api/farms", farmRoutes); // 받아온 라우팅
app.use("/api/users", userRoutes);


// 지원되지 않는 라우트 처리
app.use((req, res, next) => {
  const error = new HttpError("Could not find this route", 404);
   // ← next(error)나 res.send() 없이 이 함수가 그냥 종료.
});


// 변경 후
// catch-all 미들웨어보다 위에 위치
app.get("/api/health", (req, res) => {
  res.status(200).send("OK");
});

app.use((req, res, next) => {
  const error = new HttpError("Could not find this route", 404);
  next(error);   // ← 반드시 호출!
});