이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
로컬 LLM을 앱에 붙이려고 할 때 제일 번거로운 부분은 모델 파일보다 실행 서버와 API 주소를 맞추는 일이었다. 이번에는 Docker Model Runner로 작은 모델을 내려받고, 터미널에서 응답을 확인한 다음 curl로 OpenAI 호환 API까지 호출한다. 결과적으로 별도 Python 환경이나 클라우드 API 키 없이 localhost에 테스트용 AI API 하나를 열 수 있다.
Docker Model Runner 준비 조건
이 글의 명령은 macOS·Windows의 Docker Desktop 터미널 또는 Linux의 Docker Engine 터미널에서 실행한다. 공식 문서 기준 최소 버전은 macOS Docker Desktop 4.40 이상, Windows Docker Desktop 4.41 이상이다. macOS는 Apple Silicon이 지원 대상이며, Windows와 Linux의 세부 GPU 조건은 Docker Model Runner 요구 사항에서 확인하는 편이 안전하다. Linux는 Docker 공식 저장소로 설치한 Docker Engine과 docker-model-plugin 패키지를 사용한다.
| 항목 | 이번 실습 기준 |
|---|---|
| 실행 위치 | 호스트의 macOS/Linux 셸 또는 Windows PowerShell |
| 필수 도구 | 지원 버전의 Docker Desktop 또는 Docker Engine, curl |
| 모델 | ai/smollm2:360M-Q4_K_M |
| 네트워크 | 첫 모델 다운로드 때 인터넷 필요 |
| 비용·계정 | 로컬 API 호출에는 API 키와 토큰 과금이 없음. Docker Desktop 이용 조건과 모델별 라이선스는 별도 확인 |
| 저장 공간 | 모델 파일과 런타임 이미지가 로컬 디스크를 사용하므로 여유 공간 필요 |
Docker Desktop을 쓰면 아래 명령으로 Model Runner와 호스트 TCP 포트 12434를 함께 켤 수 있다. Linux Docker Engine은 공식 시작 문서에 나온 대로 docker-model-plugin을 설치하면 TCP가 기본적으로 12434에 열린다.
입력
docker desktop enable model-runner --tcp 12434
docker model version
출력 예시 (공식 저장소의 출력 형식에 따른 예시이며 버전 문자열은 설치 환경에 따라 다름)
Docker Model Runner version v1.2.6
출력의 의미: Model Runner 버전이 나오면 CLI 플러그인을 찾은 것이다. 활성화 명령 자체의 출력은 Docker Desktop 버전에 따라 달라질 수 있다. 이 명령은 Docker Desktop의 Model Runner 기능을 켜고 호스트의 127.0.0.1:12434를 API 접근 지점으로 사용하게 한다.
Docker Model Runner로 모델 내려받기
큰 모델부터 받으면 설치 문제와 메모리 부족을 구분하기 어렵다. 먼저 공식 예제에 쓰이는 360M 양자화 모델로 경로만 확인한다.
입력
docker model pull ai/smollm2:360M-Q4_K_M
출력 예시 (진행률과 다이제스트는 달라질 수 있음)
Model pulled successfully
출력의 의미: 성공 문구가 보이면 모델 아티팩트가 로컬 저장소에 캐시된 것이다. 정확한 진행 표시와 다운로드 크기는 CLI 버전과 모델 태그에 따라 달라진다. 태그를 생략한 Hugging Face GGUF 모델은 Q4_K_M을 우선 찾는다는 동작도 공식 docker model pull 설명에서 확인할 수 있다.
이제 한 번 질문해 본다.
입력
docker model run ai/smollm2:360M-Q4_K_M "한 문장으로 로컬 API의 장점을 설명해 줘."
출력 예시 (모델 생성 결과이므로 실행할 때마다 달라질 수 있음)
로컬 API는 데이터를 외부 서비스로 보내지 않고 내 컴퓨터에서 처리할 수 있습니다.
출력의 의미: 오류 없이 자연어가 나오면 모델 다운로드뿐 아니라 추론 엔진 로딩까지 성공한 것이다. 답변 문장은 고정값이 아니다. 공식 docker model run 문서에 따르면 모델은 요청 시 메모리에 올라가며, 일정 시간 사용하지 않으면 내려간다.

OpenAI 호환 API 호출하기
CLI 응답까지 확인했으면 같은 모델을 HTTP로 부른다. Docker의 DMR REST API 문서에서 호스트용 OpenAI 호환 기본 주소는 http://localhost:12434/engines/v1이다. 모델 이름에는 ai/ 네임스페이스와 태그를 그대로 넣는다.
입력
curl --fail-with-body http://localhost:12434/engines/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "ai/smollm2:360M-Q4_K_M",
"messages": [
{"role": "user", "content": "HTTP 200의 뜻을 한 문장으로 설명해 줘."}
],
"temperature": 0.2,
"max_tokens": 80
}'
출력 예시 (응답 ID, 시각, 토큰 수와 문장은 달라질 수 있음)
{
"id": "chatcmpl-example",
"object": "chat.completion",
"model": "ai/smollm2:360M-Q4_K_M",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "HTTP 200은 서버가 요청을 성공적으로 처리했다는 뜻입니다."
},
"finish_reason": "stop"
}
]
}
출력의 의미: choices[0].message.content가 실제 모델 답변이다. id, 토큰 사용량, 생성 문장은 실행마다 바뀐다. 이 요청은 클라우드 OpenAI API가 아니라 로컬의 Docker Model Runner로 전송되며, API 키는 필요 없다.
예전에 Docker 컨테이너에서 데이터베이스를 띄우는 흐름이 필요했다면 docker db 활용 메모도 같이 볼 만하다. 모델도 내려받은 아티팩트를 로컬에서 실행한다는 점은 비슷하지만, Model Runner API는 일반 컨테이너 포트와 접근 주소가 다르다. 다른 컨테이너에서 Docker Desktop의 모델을 부를 때는 localhost 대신 http://model-runner.docker.internal을 사용한다.
오류 해결
docker: 'model' is not a docker command
설치 버전과 플러그인 탐색부터 확인한다.
입력
docker version
docker model version
실패 출력 예시
docker: 'model' is not a docker command
출력의 의미: Docker CLI가 Model Runner 플러그인을 찾지 못했다. Docker Desktop을 지원 버전으로 업데이트하고 Model Runner를 다시 켠다. Linux라면 배포판 기본 docker.io 패키지가 아니라 Docker 공식 저장소를 사용했는지 확인한 뒤 docker-model-plugin을 설치한다. macOS에서 지원 버전인데도 같은 오류가 날 때의 플러그인 심볼릭 링크 방법은 공식 Known issues에 정리돼 있다.
curl: (7) Failed to connect
입력
docker model status
curl --fail-with-body http://localhost:12434/engines/v1/models
실패 출력 예시
curl: (7) Failed to connect to localhost port 12434
출력의 의미: 모델 문제가 아니라 TCP 접근 경로가 열리지 않은 상태다. Docker Desktop에서는 다시 docker desktop enable model-runner --tcp 12434를 실행한다. 그래도 안 되면 docker model logs로 런타임 로그를 확인하고, 다른 프로그램이 12434를 쓰는지도 점검한다.
주의할 점이 하나 있다. 이 API에는 자체 인증이 없다. 공식 문서도 API에 닿을 수 있는 클라이언트가 모델 pull과 추론 요청을 실행할 수 있다고 명시한다. 개발 PC 밖으로 포트를 노출하지 말고, 외부 접근이 필요하면 인증과 TLS를 둔 별도 프록시를 앞에 두는 편이 맞다.
마지막으로 모델 목록 API만 다시 호출한다.
입력
curl --fail-with-body http://localhost:12434/engines/v1/models
출력 예시 (ID 목록과 부가 필드는 로컬 상태에 따라 다름)
{
"object": "list",
"data": [
{"id": "ai/smollm2:360M-Q4_K_M", "object": "model"}
]
}
출력의 의미: 명령이 종료 코드 0으로 끝나고 data 안에 ai/smollm2:360M-Q4_K_M이 보이면 로컬 API와 모델 준비가 모두 끝난 것이다.