[TroubleShooting] Oracle Cloud + GHCR + Vercel 배포기: 무수한 '실패' 끝에 얻은 교훈

지난 포스팅에서 Docker 이미지 크기를 줄이며 배포 준비를 마쳤다면, 이번에는 실제 오라클 클라우드(OCI) VMVercel에 서비스를 올리며 겪은 시행착오를 공유하려 합니다.

단순한 배포를 넘어 성능 제한과 네트워크 설정, 환경 변수 주입까지 꽤나 험난한 과정이었습니다.

1. 배포 전략의 변화: "VM은 생각보다 연약했다"

초기 계획: VM 내부 빌드

처음에는 VM 환경에 영향을 주지 않으려고 VM 안에서 직접 이미지를 빌드하거나 코드를 가져와 실행하려 했습니다. GitHub Secrets의 환경 변수를 .env로 만들어 주입하고, docker-compose와 함께 압축(tar)해서 VM으로 던지는 방식을 구상했죠.


      - name: Save Docker image as tar
        run: docker save ${{ env.IMAGE_NAME }}:latest | gzip > image.tar.gz

      - name: Create .env file
        run: |
          cat > .env << EOF
          NODE_ENV=production
          PORT=${{ secrets.PORT }}
          DATABASE_URL=${{ secrets.DATABASE_URL }}
          JWT_ACCESS_SECRET=${{ secrets.JWT_ACCESS_SECRET }}
          JWT_REFRESH_SECRET=${{ secrets.JWT_REFRESH_SECRET }}
          MINIO_ENDPOINT=minio
          MINIO_PORT=9000
          MINIO_ACCESS_KEY=${{ secrets.MINIO_ACCESS_KEY }}
          MINIO_SECRET_KEY=${{ secrets.MINIO_SECRET_KEY }}
          EOF

      - name: Copy image to Oracle VM
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.ORACLE_HOST }}
          username: ${{ secrets.ORACLE_USER }}
          key: ${{ secrets.ORACLE_SSH_KEY }}
          port: ${{ secrets.ORACLE_SSH_PORT }}
          source: "image.tar.gz,docker-compose.yml,.env"
          target: "~/app/blog-server"

      - name: Deploy on Oracle VM
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.ORACLE_HOST }}
          username: ${{ secrets.ORACLE_USER }}
          key: ${{ secrets.ORACLE_SSH_KEY }}
          port: ${{ secrets.ORACLE_SSH_PORT }}
          script: |
            export PATH=$PATH:/usr/local/bin:/usr/bin:/bin

문제 발생: CI/CD 파이프라인의 연쇄 실패

하지만 OCI 프리티어 VM의 성능은 생각보다 제한적이었습니다. 빌드 과정에서 리소스를 과하게 점유하며 파이프라인이 멈춰버리는 '실패의 맛'을 보게 되었습니다.

스크린샷 2026-04-05 오전 12.28.13

순살처럼 연약한 내 인스턴스..

결론적으로 Deploy on Oracle VM 단계에서 시간이 오래걸렸고, tar로 압축하고, tar로 압축을 푸는 과정이 굉장히 리소스를 많이 잡아먹는다는 것을 알 수 있었습니다.

      - name: Deploy on Oracle VM
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.ORACLE_HOST }}
          username: ${{ secrets.ORACLE_USER }}
          key: ${{ secrets.ORACLE_SSH_KEY }}
          port: ${{ secrets.ORACLE_SSH_PORT }}
          script: |
            export PATH=$PATH:/usr/local/bin:/usr/bin:/bin

최종 전략: GHCR과 Remote Fetch 활용

과거 ACR(Azure Container Registry)을 사용했던 경험에서 영감을 얻어, GHCR(GitHub Container Registry)을 활용하기로 했습니다.

  1. GitHub Actions: 도커 이미지를 빌드하여 GHCR에 푸시.

  2. SSH 접근: VM에 접속하여 .env 파일을 생성.

  3. 파일 다운로드: raw.githubusercontent.com을 활용해 레포지토리의 docker-compose.yml 파일을 직접 VM으로 다운로드. (이 방식은 별도의 파일 전송 없이 최신 설정 파일을 가져올 수 있어 매우 편리했습니다.)

최대한 VM에 라이브러리를 설치하지 않고 진행할 수 있는 상황을 고려했고, 인스턴스의 성능이 낮은 만큼 SSH로 접근해서 파일을 쓰고, 다운로드 받을 수 있도록 설계했습니다.

이렇게 하면 네트워크를 두번 쓰는 상황이 없어 리소스가 적게 들것이라고 생각을 했었고 적중하여 성공적으로 빌드, 배포를 마칠 수 있었습니다.

스크린샷 2026-04-05 오전 12.36.02

최초는 3분 정도 걸렸지만 이후에는 캐시 최적화를 통해 1분 정도 소요가 되었습니다.


2. OCI 트러블슈팅: 메모리 부족과 네트워크 개방

[Issue 1] 원인 모를 배포 실패와 원격 접속 끊김

GHCR을 선정하고 바로 성공했던 것은 아닙니다.

스크린샷 2026-04-05 오전 12.37.02

충격과 공포의 10분...

이미지 크기를 최소한으로 줄였음에도 배포가 계속 실패했습니다. 처음에는 네트워크 속도 문제인 줄 알았으나, 모니터링 결과 메모리 피크가 원인이었습니다. 메모리가 부족해지니 원격 접속(SSH)조차 끊기는 상황이었습니다.

  • 해결: 디스크 공간을 메모리처럼 사용하는 스왑(Swap) 메모리 설정을 추가했습니다. htop으로 모니터링하니 CPU 피크는 여전했지만, 메모리 여유가 생기면서 배포 파이프라인이 정상적으로 끝까지 완주되었습니다.

스크린샷 2026-04-05 오전 10.51.11

살린건 맞지..?

[Issue 2] "서버는 떴는데 왜 접속이 안 되지?"

Docker 컨테이너는 정상 실행 중이었지만, 외부 IP로 접근이 불가능했습니다.

  • 해결: 두 곳의 문을 열어야 했습니다.

    1. Docker: docker-compose 내부 포트 포워딩 확인.

    2. OCI 콘솔: 수신(Ingress) 규칙 설정에서 서버 포트(4000)와 MinIO 관리 포트(9001)를 허용했습니다. 이후 Swagger와 MinIO UI를 통해 정상 작동을 확인했습니다.

스크린샷 2026-04-05 오전 12.41.38스크린샷 2026-04-05 오전 12.42.53


3. Vercel & Next.js: API Route와 도메인 이슈

프론트엔드는 Vercel로 배포하며 나름의 규칙을 세웠습니다. "모든 외부 API 통신은 Next.js의 API Route를 거친다"는 원칙입니다.

  • 문제: Client Side에서는 /api/...로 호출하면 문제가 없지만, Server Side(Server Components)에서 호출할 때는 Next.js 서버 컴포넌트는 서버에서 실행되므로 상대 경로를 인식하지 못하기 때문에 절대 경로(도메인 포함)가 필요했습니다. 개발 환경에서는 localhost:3000으로 설정해 두었으나, Vercel 배포 환경에서는 localhost접근이 불가능하여 에러가 발생했습니다.

  • 해결: Vercel에서 제공하는 실제 배포 도메인을 환경 변수로 등록했습니다. 서버 사이드 요청 시 이 환경 변수를 도메인으로 사용하도록 수정하여 문제를 해결했습니다.

스크린샷 2026-04-05 오전 12.05.42

vercel에서 도메인을 확인 할 수 있다.


4. 마치며

이번 배포를 통해 인프라 환경에 따른 유연한 전략 수정이 얼마나 중요한지 깨달았습니다. 특히 OCI 프리티어처럼 제한된 자원에서는 스왑 메모리 설정이 선택이 아닌 필수라는 것을 배웠네요.

이제 환경이 갖춰졌으니, 이제 블로그를 통해 더 재미있는 기능들을 구현해 볼 차례입니다!