컨테이너 속의 FastAPI - Docker

컨테이너 속의 FastAPI - Docker (FastAPI in Containers - Docker)

FastAPI 앱을 배포할 때 흔한 방법은 Linux 컨테이너 이미지를 빌드하는 거예요. 보통 Docker로 하죠. 그러면 그 컨테이너 이미지를 몇 가지 방법 중 하나로 배포할 수 있어요.

Linux 컨테이너를 쓰면 보안, 재현성(replicability), 단순함 등 여러 장점이 있어요.

💡 팁: 빠르게 진행하고 이미 다 아는 내용이라면, 아래의 Dockerfile 👇 로 건너뛰세요.

Dockerfile 미리보기 👀

FROM python:3.14

WORKDIR /code

COPY ./requirements.txt /code/requirements.txt

RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

COPY ./app /code/app

CMD ["fastapi", "run", "app/main.py", "--port", "80"]

# If running behind a proxy like Nginx or Traefik add --proxy-headers
# CMD ["fastapi", "run", "app/main.py", "--port", "80", "--proxy-headers"]

출처: 공식문서

컨테이너란 무엇인가

컨테이너(주로 Linux 컨테이너)는 앱과 그 모든 의존성, 필요한 파일들을 아주 가볍게 패키징하면서도, 같은 시스템 안의 다른 컨테이너(다른 앱이나 컴포넌트)와 격리(isolated)해 두는 방법이에요.

Linux 컨테이너는 호스트(머신, 가상 머신, 클라우드 서버 등)의 같은 Linux 커널을 사용해서 실행돼요. 이 말은 곧 (전체 운영체제를 에뮬레이션하는 완전한 가상 머신에 비해) 아주 가볍다는 뜻이에요.

이런 방식으로 컨테이너는 리소스를 조금만 소비해요. 프로세스들을 직접 실행하는 것과 비슷한 양이죠(가상 머신은 훨씬 많이 소비해요).

또한 컨테이너는 자체적인 격리된 실행 프로세스(보통 단지 하나의 프로세스), 파일 시스템, 네트워크를 가져서, 배포·보안·개발 등을 단순화해 줘요.

컨테이너 이미지란 무엇인가

컨테이너컨테이너 이미지에서 실행돼요.

컨테이너 이미지는 컨테이너 안에 있어야 하는 모든 파일, 환경 변수, 그리고 기본 명령/프로그램의 정적(static) 버전이에요. 여기서 정적이라는 것은 컨테이너 이미지가 실행 중이 아니고, 실행되지도 않으며, 단지 패키징된 파일과 메타데이터라는 뜻이에요.

저장된 정적 내용인 " 컨테이너 이미지"와 달리, " 컨테이너"는 보통 실행 중인 인스턴스, 즉 실행되고 있는 대상을 가리켜요.

컨테이너가 (컨테이너 이미지에서) 시작되어 실행되면 파일이나 환경 변수 등을 만들거나 바꿀 수 있어요. 그런 변경은 그 컨테이너 안에만 존재하고, 바탕이 되는 컨테이너 이미지에는 유지되지 않아요(디스크에 저장되지 않아요).

컨테이너 이미지는 프로그램 파일과 내용(예: python과 어떤 파일 main.py)에 비유할 수 있어요.

그리고 컨테이너 자체는 (컨테이너 이미지와 대조적으로) 이미지의 실제 실행 중인 인스턴스로, 프로세스에 비유할 수 있어요. 실제로 컨테이너는 프로세스가 실행 중일 때만 실행되고(보통은 단일 프로세스), 그 안에 실행 중인 프로세스가 없으면 멈춰요.

컨테이너 이미지

Docker는 컨테이너 이미지컨테이너를 만들고 관리하는 주요 도구 중 하나예요.

그리고 많은 도구, 환경, 데이터베이스, 애플리케이션을 위한 공식 컨테이너 이미지가 준비된 공개 Docker Hub 가 있어요.

예를 들어 공식 Python 이미지가 있어요.

그리고 데이터베이스 같은 다른 것들을 위한 이미지도 많아요, 예를 들면:

미리 만들어진 컨테이너 이미지를 사용하면 여러 도구를 조합해서 쓰기가 아주 쉬워져요. 예를 들어 새 데이터베이스를 시험해 보는 경우에요. 대부분의 경우 공식 이미지를 쓰고 환경 변수로 구성하기만 하면 돼요.

그렇게 해서, 많은 경우 컨테이너와 Docker에 대해 배우고 그 지식을 여러 다른 도구와 컴포넌트에 재사용할 수 있어요.

그러니까 데이터베이스, Python 앱, React 프론트엔드 앱을 서빙하는 웹 서버처럼 서로 다른 것들을 담은 여러 컨테이너를 실행하고, 내부 네트워크로 서로 연결하면 돼요.

(Docker나 Kubernetes 같은) 모든 컨테이너 관리 시스템에는 이런 네트워킹 기능이 통합돼 있어요.

컨테이너와 프로세스

컨테이너 이미지는 보통 메타데이터에 컨테이너가 시작될 때 실행해야 할 기본 프로그램이나 명령, 그리고 그 프로그램에 전달할 파라미터를 포함해요. 명령줄에 있는 것과 아주 비슷하죠.

컨테이너가 시작되면 그 명령/프로그램을 실행해요(다른 명령/프로그램을 실행하도록 오버라이드할 수도 있어요).

컨테이너는 메인 프로세스(명령이나 프로그램)가 실행 중인 동안 실행돼요.

컨테이너는 보통 단일 프로세스를 갖지만, 메인 프로세스에서 하위 프로세스(subprocesses)를 시작하는 것도 가능해요. 그렇게 하면 같은 컨테이너 안에 여러 프로세스가 있게 되죠.

하지만 하나 이상의 실행 중인 프로세스 없이 컨테이너가 실행되는 것은 불가능해요. 메인 프로세스가 멈추면 컨테이너도 멈춰요.

FastAPI용 Docker 이미지 빌드하기

좋아요, 이제 뭔가를 직접 만들어 볼게요! 🚀

공식 Python 이미지에 기반해 FastAPIDocker 이미지처음부터 빌드하는 법을 보여드릴게요.

이게 대부분의 경우 하고 싶은 작업이에요, 예를 들면:

  • Kubernetes나 비슷한 도구를 사용할 때
  • Raspberry Pi에서 실행할 때
  • 컨테이너 이미지를 대신 실행해 주는 클라우드 서비스를 쓸 때 등등

패키지 요구사항

uv로 프로젝트를 관리한다면, 직접 의존성은 pyproject.toml에 선언되고 정확히 해석된 버전은 uv.lock에 저장돼요.

앱이 필요로 하는 패키지를 이렇게 추가할 수 있어요:

uv add "fastapi[standard]"

📝 참고: 아래의 Dockerfile은 컨테이너 안에서 pip을 사용해요. uv 프로젝트의 잠긴 의존성을 그가 기대하는 requirements.txt 형식으로 내보낼 수 있어요:

uv export --format requirements-txt --no-dev --no-emit-project --output-file requirements.txt

생성된 requirements.txt는 컨테이너 빌드용 내보내기입니다. 의존성 관리는 계속 uv add로 하고, uv.lock이 바뀌면 다시 생성하세요.

FastAPI 코드 만들기

  • app 디렉터리를 만들고 들어가세요.
  • 빈 파일 __init__.py를 만드세요.
  • 다음 내용의 main.py 파일을 만드세요:
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"Hello": "World"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
    return {"item_id": item_id, "q": q}

Dockerfile

이제 같은 프로젝트 디렉터리에 Dockerfile 파일을 만드세요:

FROM python:3.14

WORKDIR /code

COPY ./requirements.txt /code/requirements.txt

RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

COPY ./app /code/app

CMD ["fastapi", "run", "app/main.py", "--port", "80"]

💡 팁: 코드의 각 번호 버블을 클릭하면 각 줄이 무엇을 하는지 볼 수 있어요. 👆

⚠️ 경고: 아래에서 설명하는 것처럼, 반드시 항상 CMD 명령의 exec 형식(exec form) 을 사용하세요.

CMD 사용하기 - Exec 형식

CMD Docker 명령은 두 가지 형식으로 쓸 수 있어요:

Exec 형식:

# ✅ 이렇게 하세요
CMD ["fastapi", "run", "app/main.py", "--port", "80"]

⛔️ Shell 형식:

# ⛔️ 이렇게 하지 마세요
CMD fastapi run app/main.py --port 80

FastAPI가 정상적으로 종료될 수 있고 lifespan 이벤트 가 동작하도록, 반드시 항상 exec 형식을 사용하세요.

Docker 문서의 shell and exec form 에서 더 자세히 읽을 수 있어요.

이 차이는 docker compose를 쓸 때 꽤 눈에 띄게 드러나요. 더 기술적인 내용은 Docker Compose FAQ 섹션을 확인하세요: Why do my services take 10 seconds to recreate or stop?

디렉터리 구조

이제 다음과 같은 디렉터리 구조를 갖게 됐을 거예요:

.
├── app
│   ├── __init__.py
│   └── main.py
├── Dockerfile
└── requirements.txt

TLS 종료 프록시 뒤에서

컨테이너가 Nginx나 Traefik 같은 TLS 종료 프록시(로드 밸런서) 뒤에서 실행된다면 --proxy-headers 옵션을 추가하세요. 이는 Uvicorn에게(FastAPI CLI를 통해) 앱이 HTTPS 뒤에서 실행되고 있다는 등, 그 프록시가 보내는 헤더를 신뢰하라고 알려줘요.

CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80"]

Docker 캐시

Dockerfile에는 중요한 트릭이 있어요. 우리는 나머지 코드가 아니라 의존성이 들어 있는 파일을 먼저 복사해요. 그 이유를 알려드릴게요.

COPY ./requirements.txt /code/requirements.txt

Docker와 다른 도구들은 이런 컨테이너 이미지를 점진적으로 빌드해요. Dockerfile 맨 위에서부터 시작해서 각 명령이 만든 파일들을 한 레이어씩 그 위에 쌓아가는 방식이죠.

Docker와 비슷한 도구들은 이미지를 빌드할 때 내부 캐시도 사용해요. 어떤 파일이 마지막으로 빌드한 이후 바뀌지 않았다면, 파일을 다시 복사하고 레이어를 처음부터 새로 만들지 않고 지난번에 만든 레이어를 재사용해요.

파일 복사를 피하는 것만으로는 그렇게까지 큰 이득이 없을 수 있어요. 하지만 그 단계에서 캐시를 사용했기 때문에 다음 단계에서도 캐시를 쓸 수 있어요. 예를 들어 의존성을 설치하는 명령에서 캐시를 쓸 수 있죠:

RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

패키지 요구사항 파일은 자주 바뀌지 않아요. 그래서 그 파일만 복사해 두면 Docker는 그 단계에서 캐시를 사용할 수 있어요.

그리고 그 다음 단계인, 그 의존성들을 다운로드하고 설치하는 단계에서도 Docker는 캐시를 사용할 수 있어요. 바로 여기서 시간을 아주 많이 아낄 수 있어요. ✨

패키지 의존성을 다운로드하고 설치하는 데는 몇 분이 걸릴 수 있지만, 캐시를 쓰면 기껏해야 몇 초예요.

개발 중에는 코드 변경이 잘 동작하는지 확인하려고 컨테이너 이미지를 계속 다시 빌드할 테니, 이렇게 쌓이는 시간 절약이 꽤 커요.

그리고 Dockerfile의 거의 끝에서 모든 코드를 복사해요. 이게 가장 자주 바뀌는 것이기 때문에 끝쪽에 두는 거예요. 이 단계 이후의 것은 거의 항상 캐시를 쓸 수 없으니까요.

COPY ./app /code/app

Docker 이미지 빌드하기

이제 모든 파일이 제자리에 있으니, 컨테이너 이미지를 빌드해 볼게요.

  • 프로젝트 디렉터리(Dockerfile이 있고 app 디렉터리를 포함하는 곳)로 이동하세요.
  • FastAPI 이미지를 빌드하세요:
docker build -t myimage .

💡 팁: 끝에 있는 .에 주목하세요. 이것은 ./와 같고, Docker에게 컨테이너 이미지를 빌드할 디렉터리를 알려줘요.

이 경우에는 같은 현재 디렉터리(.)예요.

Docker 컨테이너 시작하기

  • 이미지에 기반한 컨테이너를 실행하세요:
docker run -d --name mycontainer -p 80:80 myimage

확인하기

Docker 컨테이너의 URL에서 확인할 수 있을 거예요. 예를 들어 http://192.168.99.100/items/5?q=somequery 또는 http://127.0.0.1/items/5?q=somequery (또는 Docker 호스트에 맞는 동등한 URL)로요.

다음과 같은 걸 볼 수 있을 거예요:

{"item_id": 5, "q": "somequery"}

대화형 API 문서

이제 http://192.168.99.100/docs 또는 http://127.0.0.1/docs (또는 Docker 호스트에 맞는 동등한 URL)로 갈 수 있어요.

(Swagger UI가 제공하는) 자동 대화형 API 문서를 볼 수 있을 거예요.

대안 API 문서

또한 http://192.168.99.100/redoc 또는 http://127.0.0.1/redoc (또는 Docker 호스트에 맞는 동등한 URL)로 갈 수도 있어요.

(ReDoc이 제공하는) 대안 자동 문서를 볼 수 있을 거예요.

단일 파일 FastAPI로 Docker 이미지 빌드하기

FastAPI가 단일 파일, 예를 들어 ./app 디렉터리 없이 main.py 하나뿐이라면, 파일 구조가 이렇게 보일 거예요:

.
├── Dockerfile
├── main.py
└── requirements.txt

그러면 Dockerfile 안에서 파일을 복사하는 해당 경로만 바꾸면 돼요:

FROM python:3.14

WORKDIR /code

COPY ./requirements.txt /code/requirements.txt

RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

COPY ./main.py /code/

CMD ["fastapi", "run", "main.py", "--port", "80"]

파일을 fastapi run에 넘기면 그것이 단일 파일이고 패키지의 일부가 아님을 자동으로 감지해서, import하는 방법을 알고 FastAPI 앱을 서빙해 줘요. 😎

배포 개념

배포 개념 몇 가지를 컨테이너 관점에서 다시 이야기해 볼게요.

컨테이너는 주로 앱을 빌드하고 배포하는 과정을 단순화하는 도구이지, 이런 배포 개념을 처리하는 특정 방식을 강제하지 않아요. 그래서 가능한 전략이 여러 개 있어요.

좋은 소식은 각 전략마다 모든 배포 개념을 커버할 방법이 있다는 거예요. 🎉

배포 개념들을 컨테이너 관점에서 다시 살펴볼게요:

  • HTTPS
  • 시작 시 실행(Running on startup)
  • 재시작(Restarts)
  • 복제(Replication - 실행 중인 프로세스 수)
  • 메모리(Memory)
  • 시작 전 사전 단계(Previous steps before starting)

HTTPS

FastAPI 앱의 컨테이너 이미지(그리고 나중의 실행 중인 컨테이너)에만 집중한다면, HTTPS는 보통 외부의 다른 도구가 처리해요.

Traefik 같은 다른 컨테이너가 HTTPS와 인증서의 자동 획득을 처리하는 방식일 수 있어요.

💡 팁: Traefik은 Docker, Kubernetes 등과 통합이 있어서, 컨테이너용 HTTPS를 설정하고 구성하기가 아주 쉬워요.

또는 HTTPS를 (앱은 여전히 컨테이너에서 실행하면서) 클라우드 제공자가 그 서비스 중 하나로 처리할 수도 있어요.

시작 시 실행과 재시작

보통 컨테이너를 시작하고 실행하는 일은 또 다른 도구가 담당해요.

Docker 자체, Docker Compose, Kubernetes, 클라우드 서비스 등일 수 있어요.

대부분(또는 모든) 경우에 시작 시 컨테이너를 실행하고 실패 시 재시작하는 간단한 옵션이 있어요. 예를 들어 Docker에서는 --restart 명령줄 옵션이에요.

컨테이너를 쓰지 않으면 앱을 시작 시 실행하고 재시작하게 만드는 건 번거롭고 어려울 수 있어요. 하지만 컨테이너와 함께 작업하면 대부분의 경우 그 기능이 기본으로 포함돼 있어요. ✨

복제 - 프로세스 수

Kubernetes, Docker Swarm Mode, Nomad 같은 클러스터를 여러 머신에서 갖고 있다면, 각 컨테이너 안의 프로세스 매니저(Uvicorn with workers 같은)를 쓰는 대신 클러스터 레벨에서 복제를 처리하고 싶을 거예요.

Kubernetes 같은 분산 컨테이너 관리 시스템 중 하나에는 보통 들어오는 요청에 대한 로드 밸런싱을 지원하면서 컨테이너의 복제를 처리하는 통합된 방법이 있어요. 전부 클러스터 레벨에서요.

그런 경우에는 위에서 설명한 대로 Docker 이미지를 처음부터 빌드하고, 의존성을 설치한 뒤, 여러 Uvicorn 워커 대신 단일 Uvicorn 프로세스를 실행하고 싶을 거예요.

로드 밸런서

컨테이너를 쓸 때는 보통 메인 포트에서 수신을 듣는 컴포넌트가 있어요. 아마 그것도 HTTPS를 처리하는 TLS 종료 프록시인 다른 컨테이너이거나 비슷한 도구일 거예요.

이 컴포넌트가 요청의 부하(load) 를 받아 워커들 사이에 (바라건대) 균형있게 분배하기 때문에, 흔히 로드 밸런서(Load Balancer) 라고도 불러요.

💡 팁: HTTPS용으로 쓰는 그 TLS 종료 프록시 컴포넌트가 아마 로드 밸런서이기도 할 거예요.

그리고 컨테이너로 작업할 때, 그들을 시작하고 관리하는 데 쓰는 같은 시스템에는 이미 그 로드 밸런서(역시 TLS 종료 프록시일 수 있는)에서 앱이 담긴 컨테이너(들)로 네트워크 통신(예: HTTP 요청)을 전달하는 내부 도구가 있을 거예요.

로드 밸런서 하나 - 워커 컨테이너 여러 개

Kubernetes 같은 분산 컨테이너 관리 시스템으로 작업할 때, 그 내부 네트워킹 메커니즘을 쓰면 메인 포트에서 수신을 듣는 단일 로드 밸런서가 실행 중인 앱이 담긴 여러 컨테이너로 통신(요청)을 전달할 수 있어요.

앱을 실행하는 이 컨테이너 각각은 보통 프로세스가 하나(예: FastAPI 앱을 실행하는 Uvicorn 프로세스)일 거예요. 이들은 모두 동일한 컨테이너로, 같은 것을 실행하지만 각자 자신의 프로세스, 메모리 등을 갖고 있어요. 그렇게 해서 CPU의 다른 코어, 심지어 다른 머신에서 병렬화를 활용하게 돼요.

로드 밸런서를 가진 분산 컨테이너 시스템은 앱이 담긴 각 컨테이너들로 요청을 돌아가며 분배해요. 그래서 각 요청은 여러 복제된 컨테이너 중 하나가 처리할 수 있어요.

그리고 보통 이 로드 밸런서는 클러스터 안의 다른 앱들(예: 다른 도메인이나 다른 URL 경로 접두사 아래)로 가는 요청도 처리하고, 그 통신을 클러스터에서 실행 중인 그 다른 앱을 위한 올바른 컨테이너로 전달할 수 있어요.

컨테이너당 프로세스 하나

이런 유형의 시나리오에서는 아마 컨테이너당 (Uvicorn) 프로세스를 하나만 갖고 싶을 거예요. 이미 클러스터 레벨에서 복제를 처리하고 있으니까요.

그러니 이 경우에는 컨테이너 안에 여러 워커를 두고 싶지 않을 거예요. 예를 들어 --workers 명령줄 옵션 같은 걸로요. 컨테이너당 단일 Uvicorn 프로세스만 (하지만 컨테이너는 여러 개) 갖고 싶을 거예요.

컨테이너 안에 또 다른 프로세스 매니저를 두는 것은(여러 워커의 경우처럼) 이미 클러스터 시스템으로 처리하고 있을 불필요한 복잡성을 더하는 것뿐이에요.

여러 프로세스와 특수 사례가 있는 컨테이너

물론, 안에 여러 Uvicorn 워커 프로세스가 있는 컨테이너를 원하는 특수 사례도 있어요.

그런 경우에는 --workers 명령줄 옵션으로 실행하고 싶은 워커 수를 설정할 수 있어요:

FROM python:3.14

WORKDIR /code

COPY ./requirements.txt /code/requirements.txt

RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

COPY ./app /code/app

CMD ["fastapi", "run", "app/main.py", "--port", "80", "--workers", "4"]

그게 말이 되는 몇 가지 예시를 들어볼게요:

단순한 앱

앱이 충분히 단순해서 클러스터가 아닌 단일 서버에서 실행할 수 있다면, 컨테이너 안에 프로세스 매니저를 원할 수 있어요.

Docker Compose

Docker Compose단일 서버(클러스터 아님)에 배포하고 있을 수 있어요. 그러면 공유 네트워크와 로드 밸런싱을 유지하면서 (Docker Compose로) 컨테이너의 복제를 관리하기가 쉽지 않을 거예요.

그러면 안에서 여러 워커 프로세스를 시작하는 프로세스 매니저가 있는 단일 컨테이너를 원할 수 있어요.


핵심은, 이것들 중 어느 것도 맹목적으로 따라야 할 돌에 새긴 규칙이 아니라는 거예요. 이런 아이디어를 써서 자신의 사용 사례를 평가하고 시스템에 가장 좋은 접근 방식을 결정하면 돼요. 다음 개념들을 어떻게 관리할지 살펴보면서요:

  • 보안 - HTTPS
  • 시작 시 실행
  • 재시작
  • 복제(실행 중인 프로세스 수)
  • 메모리
  • 시작 전 사전 단계

메모리

컨테이너당 단일 프로세스를 실행하면 각 컨테이너가 소비하는 메모리는 대체로 잘 정의되고 안정적이며 제한적이에요(복제되면 여러 개가 되겠지만요).

그리고 컨테이너 관리 시스템(Kubernetes 등)의 구성에서 그 메모리 한도와 요구사항을 설정할 수 있어요. 그러면 시스템이 사용 가능한 머신에 컨테이너가 필요로 하는 메모리 양과 클러스터 머신에서 사용 가능한 양을 고려해서 컨테이너를 복제할 수 있게 돼요.

앱이 단순하다면 이건 아마 문제가 되지 않을 거고, 엄격한 메모리 한도를 지정할 필요도 없을 거예요. 하지만 메모리를 많이 사용한다면(예: 머신러닝 모델), 메모리를 얼마나 소비하는지 확인하고 각 머신에서 실행되는 컨테이너 수를 조정해야 해요(그리고 클러스터에 머신을 더 추가할 수도 있어요).

컨테이너당 여러 프로세스를 실행한다면, 시작되는 프로세스 수가 사용 가능한 메모리보다 더 많은 메모리를 소비하지 않도록 확인해야 해요.

시작 전 사전 단계와 컨테이너

컨테이너(Docker, Kubernetes 등)를 사용한다면, 쓸 수 있는 두 가지 주요 접근 방식이 있어요.

여러 컨테이너

여러 컨테이너를 갖고 있다면(아마 Kubernetes 클러스터에서처럼 각각 단일 프로세스를 실행하는), 복제된 워커 컨테이너들을 실행하기 전에, 사전 단계의 작업을 하는 별도의 컨테이너 하나를 단일 프로세스로 실행하고 싶을 거예요.

📝 참고: Kubernetes를 쓴다면, 이것은 아마 Init Container 가 될 거예요.

사용 사례에서 그런 사전 단계를 여러 번 병렬로 실행해도 문제가 없다면(예: 데이터베이스 마이그레이션을 실행하는 게 아니라, 데이터베이스가 준비됐는지만 확인하는 경우), 그냥 각 컨테이너에서 메인 프로세스를 시작하기 직전에 넣어도 돼요.

단일 컨테이너

단일 컨테이너로 여러 워커 프로세스(또는 하나의 프로세스만)를 시작하는 단순한 설정이라면, 같은 컨테이너 안에서 앱이 있는 프로세스를 시작하기 직전에 그 사전 단계들을 실행할 수 있어요.

기본 Docker 이미지

공식 FastAPI Docker 이미지인 tiangolo/uvicorn-gunicorn-fastapi 가 예전에 있었어요. 하지만 지금은 deprecated 됐어요. ⛔️

이 기본 Docker 이미지는(또는 다른 비슷한 것은) 아마 사용하지 않는 게 좋아요.

Kubernetes(등)를 쓰고 이미 클러스터 레벨에서 여러 컨테이너복제를 설정하고 있다면, 위에서 설명한 대로 이미지를 처음부터 빌드하는 게 더 나아요.

그리고 여러 워커가 필요하다면, 간단히 --workers 명령줄 옵션을 쓰면 돼요.

기술 세부사항 (Technical Details)

이 Docker 이미지는 Uvicorn이 죽은 워커를 관리하고 재시작하는 것을 지원하지 않던 때 만들어졌어요. 그래서 Uvicorn과 함께 Gunicorn을 써야 했고, 그저 Gunicorn이 Uvicorn 워커 프로세스를 관리하고 재시작하게 하려고 꽤 복잡한 코드가 추가됐어요.

하지만 이제 Uvicorn(과 fastapi 명령)이 --workers를 지원하니까, 기본 Docker 이미지를 쓰는 대신 나만의 이미지를 만들 이유가 없어요(코드 양이 거의 같아요 😅).

uv 사용하기

uv로 프로젝트를 설치하고 관리한다면, 그들의 uv Docker 가이드를 따라갈 수 있어요.

요약 (Recap)

컨테이너 시스템(예: DockerKubernetes)을 쓰면 모든 배포 개념을 처리하는 것이 꽤 간단해져요:

  • HTTPS
  • 시작 시 실행
  • 재시작
  • 복제(실행 중인 프로세스 수)
  • 메모리
  • 시작 전 사전 단계

대부분의 경우 어떤 기본 이미지도 쓰지 않고, 공식 Python Docker 이미지에 기반해 컨테이너 이미지를 처음부터 빌드하고 싶을 거예요.

Dockerfile에서 명령의 순서Docker 캐시를 신경 쓰면 빌드 시간을 최소화해서 생산성을 극대화할 수 있어요(그리고 지루함도 피하고요). 😎

더 알아보기 (Learn more)