Open WebUI 직접 설치 후기 — 로컬 LLM을 ChatGPT처럼 쓸 수 있나
Open WebUI는 결론부터 말하면 설치 가치가 충분합니다. 다만 검색하면 나오는 "단 한 줄 docker run"은 그대로 복사하면 안 됩니다. 이 글이 쓰는 모든 수치는 jw-ms7c94라는 Ubuntu 25.10 환경에서 직접 실행해 얻은 값이며, 다른 머신에서는 다를 수 있습니다.
먼저 결론부터 정리
- 설치는 10분 컷. 이미지 pull 포함 6분 내 걸렸습니다.
- UI는 확실히 터미널보다 낫습니다. 마크다운·코드 하이라이팅·대화 이어가기·다중 모델 비교가 특히 그렇습니다.
- 그런데 포스트가 알려주는 기본 docker run 명령은 그대로 쓰면 안 됩니다. 두 가지 설정이 빠져 있고, 그 상태로 두면 Ollama 모델이 0개로 뜹니다.
- 저사양 PC에서도 구동은 됩니다. 이 환경은 16코어·RAM 31Gi인데, 컨테이너 메모리는 660MiB만 먹었습니다. 이미지가 4.62GB인 게 유일한 체감 부담입니다.
1. 환경 조건
| 항목 | 값 |
|---|---|
| 호스트 | jw-ms7c94 (Ubuntu 25.10, 커널 6.17.0-41-generic) |
| CPU / RAM | 16코어 / 31Gi |
| GPU | NVIDIA RTX 3070 8GB |
| Docker | 브리지 네트워크 기본 (기존 컨테이너 5개 운영 중) |
| Ollama | 0.33.3, GPU 정상 인식, 로컬 모델 11개 |
| Open WebUI | 0.11.4 (이미지 빌드 2026-09-21) |
모델 목록에는 qwen3.8-4b-q6k, supergemma-e4b-q4km-32k, ghunghab/qwen3.8-9b-distill, 임베딩용으로 nomic-embed-text와 bge-m3 등이 들어 있었습니다.
2. 이 글이 틀린 부분: host.docker.internal이 안 된다
포스트의 방법 1은 이렇게 되어 있습니다.
docker run -d -p 3000:8080 \
--add-host=host.docker.internal:host-gateway \
-v open-webui:/app/backend/data \
--name open-webui \
--restart always \
ghcr.io/open-webui/open-webui:main
이 그대로 실행하면 컨테이너는 뜹니다. healthcheck도 eventually healthy가 됩니다. HTTP 200도 받습니다.
그런데 모델 목록이 0개입니다.
로그를 보면 이 한 줄이 전부입니다.
ERROR | open_webui.routers.ollama:send_get_request:93 - Connection error: Cannot connect to host host.docker.internal:11434 ssl:default [Name or service not known]
--add-host=host.docker.internal:host-gateway가 /etc/hosts에 이름을 심어야 하는데, 컨테이너 안에서는 여전히 "이름을 알 수 없다"고 합니다. --network host가 아닌 상태에서 host-gateway가 제대로 해석되지 않는 환경입니다. 컨테이너 안에서 직접 확인해봤습니다.
$ docker exec open-webui curl -s -m 5 http://host.docker.internal:11434/api/tags
Connection timed out after 5000 milliseconds
$ docker exec open-webui curl -s -m 5 http://172.17.0.1:11434/api/tags
Connection timed out after 5000 milliseconds
게다가 이 환경의 Ollama는 127.0.0.1:11434에만 바인딩돼 있었습니다. 컨테이너에서 보는 172.17.0.1(docker0 게이트웨이)로는 당연히 닿지 않습니다. 방화벽도 11434가 외부 인터페이스에 대해 닫혀 있었습니다(ufw 활성화 상태).
즉 이름 해석 실패 + 바인딩 주소 제한 + 방화벽, 세 겹이 겹친 것입니다. 포스트는 이 조합을 언급하지 않습니다.
해결책: --network host
이름 조회를 아예 없애는 게 가장 확실했습니다.
docker run -d --network host \
-e OLLAMA_BASE_URL=http://127.0.0.1:11434 \
-v open-webui:/app/backend/data \
--name open-webui \
--restart always \
ghcr.io/open-webui/open-webui:main
--network host를 쓰면 컨테이너가 호스트 네트워크를 공유하므로 127.0.0.1이 그대로 호스트를 가리킵니다. --add-host가 필요 없어집니다.
대신 포트가 host의 8080을 그대로 씁니다. -p 옵션이 무시되므로, 그 포트를 다른 것이 이미 쓰고 있지 않은지 먼저 확인해야 합니다.
이 환경은 이미 3000번을 다른 컨테이너가 갖고 있어서 처음엔 3001번으로 붙였는데, 결국 host 네트워크로 바꾸면서 8080이 비어 있는 것을 확인하고 그대로 썼습니다.
주의: DB에 값이 남는다
한 가지 함정입니다. OLLAMA_BASE_URL을 환경변수로 줬는데도 로그가 여전히 host.docker.internal을 보는 순간이 있습니다.
원인은 SQLite에 설정이 저장돼 있기 때문입니다. 컨테이너 안에서 직접 확인했습니다.
$ docker exec open-webui python -c "...select * from config where key like '%ollama%'"
('ollama.base_urls', '["http://host.docker.internal:11434"]', 1791166799)
('rag.ollama.base_url', '"http://host.docker.internal:11434"', 1791166799)
볼륨 open-webui이 살아 있는 한 이 값이 환경변수를 덮습니다. 처음부터 올바른 값으로 띄우면 문제는 없지만, 이미 잘못된 상태로 한 번 띄웠다면 볼륨을 지우거나 값을 직접 고쳐야 합니다.
저는 DB를 직접 고쳤습니다(설정 화면에서는 405 Method Not Allowed가 났습니다).
docker exec open-webui python -c "
import sqlite3
c=sqlite3.connect('/app/backend/data/webui.db')
c.execute('update config set value=? where key=?', ('[\"http://127.0.0.1:11434\"]', 'ollama.base_urls'))
c.execute('update config set value=? where key=?', ('\"http://127.0.0.1:11434\"', 'rag.ollama.base_url'))
c.commit()
"
정상화 확인은 이 한 줄로 됩니다.
curl -s http://127.0.0.1:8080/api/v1/models -H "Authorization: Bearer <token>"
수정 후 11개가 나왔고, 요청 이후 ERROR 로그 누적은 1건(정상화 이전 잔존)으로 멈췄습니다.
3. 가입과 첫 화면
첫 가입자는 관리자가 됩니다. 그런데 이메일 형식을 엄격히 검사합니다.
$ curl -X POST http://127.0.0.1:8080/api/v1/auths/signup -d '{"email":"jw@local",...}'
HTTP=400
{"detail":"The email format you entered is invalid. Please double-check and make sure you're using a valid email address (e.g., yourname@example.com)."}
로컬 전용이라 jw@local로 넣었는데 거부됐습니다. jw@example.com은 통과했습니다. 로컬 설치라도 도메인 형태를 지켜야 합니다.
주의할 점은 환경변수로 신뢰 헤더를 줬다가 signin이 막혔다는 겁니다. WEBUI_AUTH_TRUSTED_EMAIL_HEADER=null을 넣은 컨테이너에서 signin을 시도하면 이렇게 됩니다.
{"detail":"Your provider has not provided a trusted header. Please contact your administrator for assistance."}
역시 빼는 게 맞습니다. 헤더 신뢰 방식은 리버스 프록시 뒤에 둘 때의 이야기이고, 개인 PC에 필요 없습니다.
4. 실제 응답 실측
/api/chat/completions를 직접 때려봤습니다.
요청: {"model":"qwen3.8-4b-q6k:latest","messages":[{"role":"user","content":"2+2=? 숫자만 답해"}]}
응답 메시지 객체의 키는 셋이었습니다.
keys: ['role', 'content', 'reasoning_content']
content: '4'
reasoning: 'Thinking Process:\n1. The user asks "2+2=?" in Korean.\n...'
여기서 주목할 게 있습니다. reasoning_content가 별도 키로 분리되어 나오고, content에는 최종 답만 들어갑니다. 조용한 추론(quiet reasoning) 모델을 쓸 때 이 분리가 UI에 그대로 반영됩니다. Open WebUI가 reasoning을 본문과 다른 영역에 그려주는지 여부가 UI 완성도를 결정하는데, API 레벨에서 이미 분리 보장됩니다.
첫 테스트에서 이 점이 더 선명하게 드러났습니다. max_tokens: 30으로 "3단어로 대답하라"는 지시를 줬더니 content가 빈 문자열이었습니다.
reply: ''
usage: {'output_tokens': 3072, ..., 'completion_tokens_details': {'reasoning_tokens': 0}}
출력 3072토큰을 다 쓰면서 content는 비어 있었습니다. thinking 패턴이 강한 모델은 작은 max_tokens에서 최종 답 전에 reasoning으로 예산을 다먹을 수 있습니다. 짧은 응답을 강제하려면 max_tokens를 너무 작게 잡으면 안 됩니다. 실사용에서 이게 불편할 수 있는 지점입니다.
5. 리소스 실측
| 항목 | 측정값 |
|---|---|
| 이미지 크기 | 4.62GB |
| 최초 pull 소요 | 약 6분 |
| 컨테이너 메모리 (유휴) | 660MiB |
| 컨테이너 메모리 (응답 처리 중) | 1.037GiB |
/app/backend/data 용적 | 1.1G |
vector_db 초기 크기 | 188K |
| healthcheck becoming healthy | 약 40~50초 |
| 메인 페이지 응답 | 0.027s |
RAM 31Gi 환경에서 660MiB면 충분히 가볍습니다. 여기에 ollama가 GPU를 쓰므로 CPU 쪽 부담은 크지 않습니다.
다만 이미지 4.62GB는 전부 미리 받아둬야 하는 고정 비용입니다. 이 작업으로 Docker 이미지 총량이 9.708GB에서 14.33GB로 늘었고, reclaimable은 9.954GB가 됐습니다. 디스크가 100GB 미만이면 설치 전에 용량을 확인하세요.
RAG를 안 써도 vector_db 디렉터리와 SQLite 파일은 생깁니다. 빈 상태로 1.1G가 잡히는 건 백엔드 번들 파일 때문입니다.
6. 포스트에서 안 다룬 부분
Docker Compose 방식의 OLLAMA_BASE_URL=http://ollama:11434. 같은 네트워크 안의 서비스명으로 연결하므로 --network host 문제가 없습니다. 컨테이너로 ollama를 함께 올린다면 이쪽이 더 안전합니다. 이 환경은 ollama가 이미 systemd 서비스로 떠 있어서 쓰지 않았지만, 둘 다 컨테이너인 환경이라면 compose 쪽이 근본적으로 깔끔합니다.
임베딩 모델 미지정 시 RAG가 멈춥니다. nomic-embed-text(274MB)와 bge-m3(1.2GB)가 이미 로컬에 있어서 이 부분은 우연히 통과했습니다. 임베딩 모델이 하나도 없으면 문서 검색이 조용히 실패합니다.
GPU 설정 위치. 모델이 자동으로 잡힙니다. 별도 -e CUDA_VISIBLE_DEVICES 지정 없이도 ollama 쪽 GPU를 그대로 씁니다.
7. 총평
| 항목 | 평가 |
|---|---|
| 설치 난이도 | 쉬움. 단 포스트의 기본 명령은 수정이 필요함 |
| UI 완성도 | 높음. 마크다운·코드블록·대화 지속성 모두 정상 |
| Ollama 연동 | 기본 명령으로는 실패. --network host 또는 compose 필요 |
| 리소스 | RAM 가벼움(660MiB), 디스크 무겁움(4.62GB) |
| RAG | 임베딩 모델 선지정 필요 |
| 단독 실행 가치 | 있음. 여러 모델을 한 화면에서 오가며 쓰기 좋음 |
"ChatGPT 부럽지 않다"는 표현은 과장이 아닙니다. 특히 모델 목록을 한 화면에 두고 여러 모델을 오가는 부분은 터미널로는 불편한 부분이라 확실한 이득입니다.
다만 이 글이 처음에 준 명령을 그대로 복사하면 이득을 얻기 전에 헤맬 수 있습니다. --network host가 그 해법입니다.
다음 글에서 RAG와 웹 검색 연동을 실제 문서로 검증해 보겠습니다. 임베딩 모델 지정과 SearXNG 붙이는 과정에서 또 나올 함정을 기대하겠습니다.
AI Knowledge Hub