[TIL] Docker로 실행한 FastAPI를 Oracle VM과 Cloudflare로 공개하기

RE_BROTHER·2026년 7월 29일

FastAPI-migration

목록 보기
6/7

FastAPI 애플리케이션을 로컬에서 실행하는 것과 외부 사용자가 접속할 수 있는 서비스로 공개하는 것은 다른 문제였다.

이번 글에서는 Oracle Always Free VM에서 실행 중인 Docker 기반 FastAPI를 Nginx reverse proxy와 Cloudflare DNS/Proxy를 통해 도메인으로 연결한 과정을 정리한다.

애플리케이션 실행 환경은 Cloudflare Worker가 아니다. FastAPI는 표준 Docker 컨테이너로 실행하고, Cloudflare는 DNS, Proxy, D1, R2 역할을 담당한다.

1. 최종 아키텍처

사용자 브라우저
-> example.com
    -> Cloudflare DNS / Proxy
    -> Oracle VM 공인 IP
    -> Nginx :443
    -> Nginx reverse proxy
    -> 127.0.0.1:8000
    -> Docker Compose
    -> Uvicorn
    -> FastAPI
    -> Cloudflare D1 / R2

각 계층의 책임은 다음처럼 나눴다.

  • FastAPI: 애플리케이션과 API 제공
  • Docker: 실행 환경 패키징
  • Uvicorn: ASGI 애플리케이션 서버
  • Nginx: HTTPS 종료와 reverse proxy
  • Oracle VM: 애플리케이션 실행 서버
  • Cloudflare: DNS, Proxy, D1, R2
  • Gabia: 도메인 등록 기관

FastAPI는 특정 클라우드의 실행 런타임에 종속되지 않는다. Oracle VM 대신 다른 Linux VM이나 컨테이너 호스팅 서비스를 사용하더라도 Docker 실행 계층은 재사용할 수 있다.

2. Oracle VM에 Docker 애플리케이션 실행

Ubuntu VM에 Docker, Compose plugin, Git을 설치했다.

sudo apt update
sudo apt install -y docker.io docker-compose-v2 git
sudo systemctl enable --now docker
sudo usermod -aG docker "$USER"

그룹 권한을 적용하기 위해 SSH 세션을 종료한 뒤 다시 접속했다.

exit

소스는 배포 브랜치에서 내려받는다.

mkdir -p ~/apps
cd ~/apps
git clone --branch feature/FastAPI-migration <repository-url>
cd <repository-directory>

운영 환경변수는 저장소에 커밋하지 않고 VM에서 직접 작성한다.

cp .env.example .env
chmod 600 .env
nano .env

주요 설정은 다음과 같다.

SECRET_KEY=<long-random-secret>
DATABASE=/app/instance/app.db
UPLOAD_FOLDER=/app/uploads
MAX_CONTENT_LENGTH=20971520
DISPLAY_TIMEZONE=Asia/Seoul

REPOSITORY_BACKEND=d1
STORAGE_BACKEND=r2

CLOUDFLARE_ACCOUNT_ID=<cloudflare-account-id>
D1_DATABASE_ID=<d1-database-id>
CLOUDFLARE_API_TOKEN=<d1-runtime-api-token>

R2_BUCKET_NAME=<r2-bucket-name>
R2_ACCOUNT_ID=<cloudflare-account-id>
R2_ACCESS_KEY_ID=<r2-access-key-id>
R2_SECRET_ACCESS_KEY=<r2-secret-access-key>
R2_PUBLIC_BASE_URL=<r2-public-base-url>

D1 API Token과 R2 S3 Access Key는 서로 다른 인증 수단이다. CLOUDFLARE_API_TOKEN은 D1 REST API 호출에 사용하고, R2는 R2_ACCESS_KEY_ID와 R2_SECRET_ACCESS_KEY를 사용한다.

3. Docker Compose로 FastAPI 실행

Oracle VM용 Compose 설정은 FastAPI 포트를 외부에 직접 노출하지 않도록 구성했다.

services:
  fastapi:
    build:
      context: ../..
      dockerfile: Dockerfile
    container_name: personal-service-fastapi
    restart: unless-stopped
    env_file:
      - ../../.env
    ports:
      - "127.0.0.1:8000:8000"
    volumes:
      - fastapi-instance:/app/instance

volumes:
  fastapi-instance:

컨테이너는 다음처럼 실행한다.

cp deployment/oracle/docker-compose.yml.example deployment/oracle/docker-compose.yml
docker compose -f deployment/oracle/docker-compose.yml up -d --build

FastAPI는 127.0.0.1:8000에서만 수신한다. 외부 요청은 Nginx만 받도록 하여 애플리케이션 포트를 인터넷에 직접 공개하지 않는다.

4. Nginx reverse proxy 구성

Docker 컨테이너가 실행된 다음 VM에 Nginx를 설치한다.

sudo apt update
sudo apt install -y nginx
sudo systemctl enable --now nginx

/etc/nginx/sites-available/example.com에 다음 설정을 작성한다.

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name example.com www.example.com;

    ssl_certificate /etc/nginx/ssl/example.com.pem;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

사이트를 활성화한다.

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.com
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

Nginx는 외부의 80, 443 요청을 받아 내부 FastAPI의 8000 포트로 전달한다. FastAPI 포트를 127.0.0.1에만 바인딩했기 때문에 외부 사용자는 Nginx를 거치지 않고 컨테이너에 직접 접근할 수 없다.

5. Oracle 네트워크 포트 설정

Oracle Cloud Console의 VCN 보안 목록에 다음 Ingress Rule을 추가했다.

Source CIDR: 0.0.0.0/0
Protocol: TCP
Destination Port: 80

Source CIDR: 0.0.0.0/0
Protocol: TCP
Destination Port: 443

SSH 접속을 위한 22번 포트도 필요하다. 운영 환경에서는 0.0.0.0/0 대신 본인의 고정 IP 대역으로 제한하는 것이 안전하다.

Oracle 보안 목록을 통과해도 Ubuntu 내부 방화벽 규칙에서 차단될 수 있다. 서버에서는 iptables의 INPUT 규칙에 80, 443을 허용하고 영구 저장했다.

sudo iptables -I INPUT 5 -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT
sudo iptables -I INPUT 5 -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT
sudo apt install -y iptables-persistent
sudo netfilter-persistent save

외부 연결은 다음 세 계층을 모두 통과해야 한다.

Oracle VCN Security List -> Ubuntu iptables -> Nginx

6. Gabia 도메인을 Cloudflare로 연결

구매한 도메인은 Gabia에 그대로 두고 DNS 운영만 Cloudflare로 이전했다.

Cloudflare에서 도메인을 추가하면 사용할 네임서버를 할당한다. 이번에 할당된 네임서버는 다음과 같았다.

<cloudflare-nameserver-1>
<cloudflare-nameserver-2>

Gabia 도메인 관리 화면의 네임서버 설정에서 기존 Gabia 네임서버를 삭제하고 Cloudflare 네임서버를 등록한다.

1차: <cloudflare-nameserver-1>
2차: <cloudflare-nameserver-2>

DNSSEC를 사용 중이었다면 네임서버를 바꾸기 전에 비활성화해야 한다. DNSSEC 상태가 남아 있으면 네임서버 변경 후 DNS 응답 검증이 실패할 수 있다.

네임서버 전파 여부는 다음 명령으로 확인할 수 있다.

nslookup -type=ns example.com 1.1.1.1

7. Cloudflare DNS 레코드 설정

Cloudflare DNS에 Oracle VM의 Reserved Public IP를 등록한다.

Type: A
Name: @
IPv4 address: <oracle-reserved-public-ip>
Proxy status: Proxied

www도 사용할 경우 CNAME을 추가한다.

Type: CNAME
Name: www
Target: example.com
Proxy status: Proxied

Oracle VM의 공인 IP는 Ephemeral IP가 아닌 Reserved IP를 사용하는 편이 안전하다. Reserved IP는 해제하기 전까지 유지할 수 있어 VM 교체 시에도 같은 IP를 다시 연결할 수 있다.

8. Cloudflare Origin Certificate

Cloudflare Proxy가 HTTPS로 원본 서버에 연결하려면 Oracle VM의 Nginx에도 인증서가 필요하다.

Cloudflare Dashboard에서 다음 메뉴로 이동한다.

SSL/TLS
  -> Origin Server
  -> Create Certificate

호스트 이름에는 다음을 포함한다.

example.com
*.example.com

인증서 발급 후 화면에 표시되는 두 값을 구분해야 한다.

Origin Certificate -> 공개 인증서 파일(.pem)
Private Key        -> 개인 키 파일(.key)

두 값을 각각 VM에 저장한다.

sudo mkdir -p /etc/nginx/ssl
sudo nano /etc/nginx/ssl/example.com.pem
sudo nano /etc/nginx/ssl/example.com.key
sudo chmod 600 /etc/nginx/ssl/example.com.key

Private Key는 저장소, 메신저, 블로그, 화면 캡처에 포함하면 안 된다.

9. Cloudflare SSL 모드

원본 Nginx에도 Cloudflare Origin Certificate를 설치했으므로 Cloudflare의 SSL/TLS 암호화 모드는 Full (strict)를 사용한다.

Full
  Cloudflare에서 원본까지 HTTPS로 연결하지만 인증서 검증은 엄격하지 않음

Full (strict)
  Cloudflare에서 원본까지 HTTPS로 연결하고 인증서 유효성 및 호스트 이름을 검증

원본 인증서가 정상적으로 설치된 운영 환경에서는 Full (strict)가 적합하다. Cloudflare와 원본 사이에서도 인증서를 검증하므로 Full보다 안전하다.

10. 배포 구조 확인

각 구간을 분리하면 연결 문제를 빠르게 찾을 수 있다.

1. 컨테이너 -> 127.0.0.1:8000
2. Nginx -> 127.0.0.1:8000
3. VM 공인 IP -> Nginx :80/:443
4. 도메인 DNS -> Cloudflare
5. Cloudflare Proxy -> Oracle Origin

서버 내부에서 Nginx가 FastAPI로 전달하는지 확인할 때는 Host 헤더를 지정한다.

curl -H "Host: example.com" http://127.0.0.1/health

도메인과 HTTPS 연결 후에는 다음처럼 접근한다.

curl.exe -i https://example.com/health

Cloudflare Proxy를 사용하는 경우 외부 응답에는 Cloudflare 관련 헤더가 표시되고, 응답 본문은 FastAPI가 반환한 내용이어야 한다.

11. 배포 결과와 한계

최종적으로 애플리케이션 실행과 저장소를 다음처럼 분리했다.

실행 계층: Oracle Always Free VM + Docker + Nginx
데이터 계층: Cloudflare D1
파일 계층: Cloudflare R2
네트워크 계층: Cloudflare DNS / Proxy

FastAPI는 Worker 전용 코드 없이 일반 ASGI 애플리케이션으로 실행된다. 따라서 로컬에서는 Uvicorn으로 실행하고, 서버에서는 Docker Compose로 실행하며, 필요하면 다른 클라우드의 VM이나 컨테이너 플랫폼으로 옮길 수 있다.

Always Free VM은 리전의 호스트 용량과 계정 상태에 영향을 받고, E2.1.Micro처럼 자원이 작은 인스턴스는 이미지 빌드와 메모리 사용량에 주의해야 한다. 실행 서버를 교체할 수 있도록 데이터와 파일을 D1/R2에 분리한 이유도 여기에 있다.

마무리

이번 배포의 핵심은 FastAPI를 Cloudflare Worker로 변환하지 않고, 일반 Docker 서비스로 실행하면서 Cloudflare의 관리형 서비스를 연결한 것이다.

FastAPI는 어디서나 실행 가능하게
Cloudflare는 DNS, Proxy, D1, R2로 사용
Nginx는 외부 요청과 내부 애플리케이션 사이의 경계로 사용

이 구조를 기반으로 이후에는 자동 배포, 운영 로그, 백업, 접근 제어와 모니터링을 추가할 수 있다.

부록. PythonAnywhere와 응답 속도 비교

배포 후 기존 PythonAnywhere 주소와 새 도메인의 응답 속도를 간단히 비교했다.

두 서비스에서 공통으로 응답하는 루트 경로를 대상으로 PowerShell에서 각각 3회 요청했고, DNS 조회나 페이지 렌더링 시간은 제외하고 HTTP 요청의 전체 응답 시간을 측정했다.

측정 경로: /
측정 횟수: 각 3회

PythonAnywhere 평균: 약 0.893초
Oracle VM + Cloudflare 평균: 약 0.491초

측정 환경에서는 새 배포 구조가 기존 PythonAnywhere보다 약 0.4초 빠르게 응답했으며, 전체 응답 시간이 약 45% 감소했다.

다만 이 결과는 단일 환경에서 짧게 측정한 참고값이다. 실제 속도는 네트워크 상태, Cloudflare Proxy 경로, 서버 부하, 응답 데이터 크기와 캐시 여부에 따라 달라질 수 있다. 또한 /health 경로는 PythonAnywhere 서비스에 존재하지 않아 404가 반환되었으므로 속도 비교에서는 사용하지 않았다.

profile
will be better

0개의 댓글