비공개 원격 배포
이 패턴은 애플리케이션 또는 Ollama 포트를 공개하지 않고 하나의 Docker 호스트에서 Libre WebUI, Ollama, Cloudflare Tunnel을 실행합니다. Cloudflare Access는 외부 ID 경계이고 Libre WebUI 인증은 내부 경계로 유지됩니다. Work와 Watchtower는 각각 별도로 선택해야 하며 root와 동등한 권한을 갖습니다.
이 템플릿은 단일 복제본 solo 토폴로지입니다. SQLite, 로컬 암호화 Blob, 임베디드 벡터, 로컬 조정과 임베디드 작업 워커가 애플리케이션 데이터 볼륨을 공유합니다. .env에서 백엔드 선택기를 변경해 팀 배포로 전환하지 마세요. 팀 배포에서는 저장소의 docker-compose.team.yml을 사용해야 하며, Work를 활성화할 때는 docker-compose.team.work.yml도 함께 사용해야 합니다. 이 구성은 PostgreSQL/PGVector, 버전 관리 S3 저장소, Redis, 외부 워커와 게이트웨이를 하나의 조정된 토폴로지로 프로비저닝합니다.
deploy/private/docker-compose.yml을 출발점으로 사용하세요. 기본값은 main 이미지입니다.
LIBRE_WEBUI_IMAGE=ghcr.io/libre-webui/libre-webui:main
dev 태그는 명시적으로 선택한 개발 인스턴스에 적합하며 클라이언트 기본값으로는 적합하지 않습니다.
보안 모델
- Cloudflare Access는
/api/*와 WebSocket 업그레이드를 포함한 전체 호스트 이름을 보호합니다. 공개 우회 경로를 추가하지 마세요. - Libre WebUI 애플리케이션 API에는 현재 계정이 필요합니다. 모델 수명 주기와 Work 작업에는 현재 데이터베이스 역할이 관리자여야 합니다.
- 애플리케이션, Ollama, SearXNG와 cloudflared는 비공개 Compose 네트워크만 사용합니다. 호스트는 애플리케이션 포트를 공개하지 않습니다.
- 번들 SearXNG 서비스는 선택적 웹 검색을 지원합니다. 내부에서만 접근할 수 있으며 관리자가 설정 > 검색에서 검색을 활성화하기 전까지 동작하지 않습니다. 스택을 시작하기 전에
.env에서SEARXNG_SECRET을 설정하세요. - 앱은 읽기 전용 루트 파일 시스템, Linux 기능 없음, no-new-privileges와 CPU, 메모리, PID 한도가 적용된 비-root 사용자로 실행됩니다.
- Work는 해당 재정의 중 하나를 포함하지 않으면 비활성화됩니다. 활성화하면 컨테이너에도 별도의 읽기 전용 루트 파일 시스템, 기능 제거, 리소스 한도, 워크스페이스 볼륨과 기본 거부 네트워크 정책이 적용됩니다.
기본 스택은 Docker 소켓을 마운트하지 않습니다. docker-compose.work-proxy.yml로 Work를 활성화해도 이 상태가 유지됩니다. 내부 네트워크의 소켓 프록시가 소켓을 보유하고 Work에서 사용하는 API 영역(컨테이너, 이미지, 볼륨, 네트워크, exec, info)만 전달합니다. swarm, secrets, build, system 엔드포인트는 프록시에서 거부되며 애플리케이션에는 소켓 마운트나 소켓 그룹 구성원 자격이 필요하지 않습니다. 프록시는 Docker API 표면을 줄이지만 전달하는 작업의 피해 범위까지 줄이지는 않습니다. 컨테이너를 만들 수 있는 주체는 여전히 호스트 경로를 바인드 마운트할 수 있으므로, 이를 실제 강화 계층으로 취급하되 다중 테넌트 격리로 간주하지 마세요.
원시 소켓 방식은 여전히 가장 큰 신뢰 경계입니다. docker-compose.work.yml과 Watchtower 재정의는 컨테이너 안의 프로세스가 임의의 Docker API 호출을 실행해 호스트를 제어할 수 있게 합니다. 소켓을 읽기 전용으로 마운트해도 Docker API 접근이 읽기 전용이 되지는 않습니다. 통합 백업 도우미는 원시 Docker 소켓 상속을 거부합니다. 예약 통합 백업에 의존하기 전에 Work를 필터링된 프록시로 마이그레이션하세요.
초기 설정
- root가 아닌 sudo 운영자를 만들고 키 기반 SSH 로그인을 확인한 뒤 root SSH를 비활성화합니다.
deploy/private/.env.example을/opt/libre-webui/.env로 복사하고 모드를0600으로 설정한 뒤 고유한 비밀 값을 생성하고 호스트에 맞게BLOB_QUOTA_BYTES_PER_USER크기를 정합니다.BLOB_QUOTA_RESERVATION_TTL_MS는 방치된 업로드 예약을 만료하며 기본값은 1시간입니다.- Work를 활성화할 예정이라면
DOCKER_GID를/var/run/docker.sock소유 그룹의 숫자 ID로 설정합니다. - Cloudflare 터널 토큰을
/opt/libre-webui/secrets/tunnel-token에 저장하고 모드를0640이하로 설정합니다. - 전체 호스트 이름을 대상으로 Cloudflare Access 자체 호스팅 애플리케이션을 만들고, 24시간 세션을 사용하며, 의도한 ID만 허용합니다. 터널 경로에서 Protect with Access를 활성화하세요. 모니터링에 공개 상태 확인이 필요하면
/health/live에만 적용되는 별도의 경로 범위 애플리케이션 또는 정책을 만드세요. 주 애플리케이션에 포괄적인 Bypass 정책을 추가해서는 안 됩니다. 일치하는 Bypass 정책은 Allow 정책을 무력화합니다. ENABLE_SIGNUP=false를 유지합니다. Access 허용 목록이 호스트 이름을 보호하면 첫 로컬 관리자를 만드세요. 빈 데이터베이스에서는 이 초기 계정 하나가 자동으로 허용됩니다. 이후 명확히 계획한 등록 기간에만 가입을 활성화하세요.- Turnstile 호스트 이름 제한을 구성하고
TURNSTILE_EXPECTED_HOSTNAME을 정확한 공개 호스트 이름으로 설정합니다.
시작하고 검증합니다.
cd /opt/libre-webui
docker compose config --quiet
docker compose up -d
docker compose ps
Work를 활성화하려면 소켓 프록시 재정의를 의도적으로 포함합니다.
docker compose -f docker-compose.yml -f docker-compose.work-proxy.yml up -d
원시 소켓 변형(docker-compose.work.yml)은 위에서 설명한 신뢰 관련 영향을 감수해야 하는 배포를 위해 계속 제공됩니다.
Access가 활성화되면 해당 경로에 좁은 범위의 우회가 없는 한 명령줄 스모크 테스트에 Cloudflare Access 서비스 토큰이 필요합니다. 자격 증명은 셸 기록 외부에 저장하고 두 헤더를 모두 보냅니다.
curl --fail --silent --show-error \
-H "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" \
-H "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET" \
https://your-hostname.example/api/auth/system-info
보호된 애플리케이션 API에 인증되지 않은 요청을 보내면 반드시 401이 반환되어야 합니다.
curl --output /dev/null --write-out '%{http_code}\n' \
-H "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" \
-H "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET" \
https://your-hostname.example/api/work/tasks
호스트 강화
이 디렉터리에는 sshd drop-in과 fail2ban jail이 포함되어 있습니다. sshd drop-in을 적용하기 전에 다른 터미널에서 별도의 비-root sudo 세션이 작동하는지 확인하세요. SSH를 다시 로드하기 전에 sshd -t로 구성을 테스트합니다.
UFW 또는 동등한 방화벽을 사용해 인바운드 트래픽을 기본적으로 거부하고 속도 제한된 SSH만 허용하세요. 이 템플릿에서는 Docker가 서비스 포트를 공개하지 않습니다.
ufw default deny incoming
ufw default allow outgoing
ufw limit OpenSSH
ufw enable
무인 보안 업그레이드를 계속 활성화하세요. 배포에 문서화된 필요성이 없다면 X11, 에이전트, TCP 포워딩을 비활성화합니다.
백업과 복구
백업하기 전에 실행 중인 배포 컨테이너에서 읽기 전용 복구 인벤토리를 실행합니다. 그러면 실제 배포된 애플리케이션 버전, 환경과 마운트된 데이터 볼륨을 정확히 사용합니다. 호스트 체크아웃의 명령은 잘못된 데이터베이스를 검사하거나 배포 이미지와 다른 소스를 실행할 수 있습니다.
docker exec libre-webui \
libre-webui recovery-check --json --data-dir /app/backend/data
종료 상태 0은 복구 준비를 막는 요소가 없다는 뜻이고, 1은 JSON 보고서에 차단 요소가 있다는 뜻이며, 2는 명령을 실행할 수 없다는 뜻입니다. 보고서에는 암호화 키 지문과 비밀 값 존재 플래그만 포함되며 키나 다른 비밀 값을 절대 출력하지 않습니다. 운영자가 복원 전에 애플리케이션 버전, 스키마 지문, 필요한 Work 리소스와 제외 항목을 비교할 수 있도록 인벤토리를 해당 백업과 함께 보관하세요.
정확한 배포 이미지로 전용 백업 암호화 키와 서명 키를 만듭니다. 이 디렉터리는 애플리케이션 볼륨 외부에 두고 암호화 키와 서명 개인 키를 별도로 보호되는 복구 위치에 복사합니다.
install -d -m 0700 /etc/libre-webui/backup-keys
image_ref=$(docker inspect libre-webui --format '{{.Image}}')
docker run --rm --user 0:0 --read-only --network none --cap-drop ALL \
--security-opt no-new-privileges \
--mount type=bind,src=/etc/libre-webui/backup-keys,dst=/backup-keys \
--entrypoint /usr/local/bin/libre-webui "$image_ref" \
backup keygen \
--directory /backup-keys
키 생성은 기존 출력 파일이 있으면 거부됩니다. 기존 백업 세트 위에 새 키를 생성하지 마세요. 아카이브 암호화 키나 서명 ID 중 하나라도 잃으면 해당 복구 증명을 사용할 수 없게 됩니다.
제공된 백업/복원 스크립트와 systemd 유닛을 설치한 다음 타이머를 활성화합니다.
install -d -m 0700 /var/backups/libre-webui
install -m 0750 deploy/private/libre-webui-backup \
/usr/local/sbin/libre-webui-backup
install -m 0750 deploy/private/libre-webui-restore \
/usr/local/sbin/libre-webui-restore
install -m 0644 deploy/private/libre-webui-backup.{service,timer} \
/etc/systemd/system/
systemctl daemon-reload
systemctl enable --now libre-webui-backup.timer
유닛은 선택적으로 /etc/libre-webui/backup.env에서 유지 관리 전용 재정의를 읽으며 애플리케이션 .env는 로드하지 않습니다. 재정의가 필요할 때만 root로 파일을 만듭니다.
install -d -m 0750 /etc/libre-webui
install -m 0600 /dev/null /etc/libre-webui/backup.env
LIBRE_WEBUI_STACK_DIR, LIBRE_WEBUI_BACKUP_RETENTION_DAYS, LIBRE_WEBUI_CONTAINER_NAME, LIBRE_WEBUI_BACKUP_KEY_DIR를 여기에서 직접 설정할 수 있습니다. 파일 소유자는 root, 모드는 0600으로 유지하세요. 사용자 지정 키 디렉터리는 systemd 샌드박스 안에서 root가 읽을 수 있어야 합니다.
LIBRE_WEBUI_BACKUP_DIR를 변경하면 systemd 쓰기 경계도 바뀝니다. 서비스가 시작되기 전에 디렉터리가 존재해야 하고 유닛에는 일치하는 drop-in이 필요합니다. 예를 들어 backup.env에 LIBRE_WEBUI_BACKUP_DIR=/srv/backups/libre-webui를 설정한 뒤 다음을 실행합니다.
install -d -m 0700 /srv/backups/libre-webui
systemctl edit libre-webui-backup.service
편집기에 다음의 정확한 경로를 추가한 뒤 유닛을 다시 로드합니다.
[Service]
ReadWritePaths=/srv/backups/libre-webui
systemctl daemon-reload
systemctl start libre-webui-backup.service
일치하는 ReadWritePaths= 항목이 없으면 ProtectSystem=strict가 타이머에서 사용자 지정 위치에 쓰는 작업을 올바르게 차단합니다.
백업 서비스는 대형 아카이브를 위해 최대 6시간을 허용합니다. 도우미는 호스트 잠금을 획득하고, 애플리케이션이 이미 실행 중이었을 때만 중지하며, 정확한 배포 이미지로 정지된 볼륨을 대상으로 아카이브를 만듭니다. 아카이브에는 서명된 매니페스트와 운영자가 암호화한 페이로드가 있으며, 해당 상태를 여는 데 필요한 데이터 디렉터리, 런타임 및 비밀 구성이 포함됩니다. 이후 도우미는 전체 아카이브를 독립적으로 검증한 뒤 메타데이터 보고서를 원자적으로 게시합니다. 읽기 전용 유지 관리 컨테이너에는 SQLite 검사와 인증된 아카이브 검증을 위한 비공개 쓰기 가능 /tmp tmpfs가 제공되며, 임시 평문은 컨테이너 계층에 저장되지 않습니다. 두 파일과 별도로 보호한 복구 키를 모두 호스트 외부에 복사하세요.
Work에서 docker-compose.work-proxy.yml을 사용한다면 복구 과정에서 데이터베이스가 참조하는 모든 Work 볼륨이 여전히 존재하는지도 증명해야 합니다. 도우미는 배포된 애플리케이션의 DOCKER_HOST를 읽고, 동일한 실행 중 Compose 프로젝트에서 소켓 프록시 서비스를 찾은 뒤, Docker의 실제 네트워크 연결에서 두 서비스가 공유하는 유일한 내부 네트워크를 검색합니다. Compose는 해당 네트워크 앞에 프로젝트 이름을 붙이므로 추측한 네트워크 이름을 구성하거나 하드코딩하지 마세요. 아카이브 생성 컨테이너만 이 내부 네트워크에 연결되어 필터링된 프록시에 접근할 수 있으며 원시 소켓은 제공되지 않습니다. 독립 아카이브 검증은 계속 --network none으로 실행됩니다. 프록시 누락, 예상하지 못한 엔드포인트, 외부 또는 모호한 공유 네트워크, 원시 소켓 마운트가 있으면 애플리케이션을 중지하고 아카이브를 게시하기 전에 실패합니다.
실시간 볼륨을 교체하지 않고 새 볼륨으로 복구를 테스트합니다.
LIBRE_WEBUI_RESTORE_IMAGE="$image_ref" \
libre-webui-restore \
/var/backups/libre-webui/libre-webui-integrated-YYYYMMDDTHHMMSSZ.lwb \
libre-webui-restore-drill
복원 도우미는 기존 볼륨이나 구성 대상을 거부하고, 일회용 저장소에서 아카이브와 내부 복구 인벤토리를 검증한 다음, 새 볼륨에 데이터를 복사하고 복구한 runtime.json과 secrets.json을 비공개 권한으로 기록합니다. 실시간 스택을 다시 연결하거나 시작하지 않습니다. 복구된 구성을 검사하고 배포별 값을 의도적으로 업데이트한 뒤 격리된 스택에서 복원 볼륨을 테스트하세요.
Ollama 모델은 다시 가져올 수 있습니다. Docker Work 볼륨, Kubernetes Work PVC와 호스트에 바인딩된 Work 폴더는 애플리케이션 데이터 디렉터리 외부에 있으며 별도로 조정된 스냅샷 및 보존 정책이 필요합니다.
업데이트
이미지 태그를 변경할 수 있더라도 Libre WebUI는 상태를 유지합니다. 기본 Compose 파일은 애플리케이션이 Watchtower에서 제외되도록 영구적으로 레이블을 지정합니다. 조정된 운영자 작업으로만 업그레이드하세요.
- 실행 중인 이미지 ID를 기록하고 검토한 대체 이미지를 변경 불가능한 다이제스트로 확인합니다.
libre-webui recovery-check를 실행하고 백업 서비스를 시작한 뒤 계속 진행하기 전에 새로 생성된 아카이브와 검증 보고서를 요구합니다.LIBRE_WEBUI_IMAGE를 검토한 다이제스트로 설정하고 가져온 뒤 Docker Compose로libre-webui만 다시 만듭니다. 데이터 볼륨을 제거하거나 다시 만들지 마세요./health/ready, 로그인, 세션/기록, 문서 검색과 Work 스모크 테스트가 통과해야 합니다. 통과하지 못하면 기록해 둔 이미지 다이제스트로 롤백하고, 진단을 위해 실패 상태와 검증된 백업을 모두 보존합니다.
호스트 측 순서는 의도적으로 수동입니다. 검토한 후에만 다이제스트를 교체하고, 가져오기 전에 가장 최근의 .lwb 및 .json 쌍을 확인합니다.
docker inspect libre-webui --format '{{.Config.Image}} {{.Image}}'
docker exec libre-webui \
libre-webui recovery-check --json --data-dir /app/backend/data
systemctl start libre-webui-backup.service
systemctl --no-pager --full status libre-webui-backup.service
ls -lt /var/backups/libre-webui/libre-webui-integrated-* | head
# Set LIBRE_WEBUI_IMAGE=ghcr.io/libre-webui/libre-webui@sha256:REVIEWED_DIGEST
# in the root-owned .env, then recreate only the application.
docker compose pull libre-webui
docker compose up -d --no-deps libre-webui
docker inspect libre-webui --format '{{.State.Health.Status}} {{.Image}}'
선택적인 소켓 보유 Watchtower 재정의는 기본 파일에서 명시적으로 레이블이 지정된 사이드카에만 계속 사용할 수 있습니다.
docker compose \
-f docker-compose.yml \
-f docker-compose.watchtower.yml \
up -d
Watchtower는 30분마다 Ollama와 SearXNG를 확인합니다. Ollama 모델 데이터는 명명 볼륨에, SearXNG 구성은 바인드 마운트에 그대로 유지됩니다. Libre WebUI, cloudflared, Work 소켓 프록시 또는 Work 샌드박스는 업데이트하지 않습니다. 클라이언트 배포는 main을 따르며 실험 인스턴스는 :dev를 선택할 수 있지만, 애플리케이션에는 여전히 동일하게 백업을 게이트로 삼는 수동 업그레이드가 필요합니다. 이 비공개 solo 스택을 팀 영구 저장 서비스에 연결하지 마세요. 대신 완전한 팀 토폴로지를 배포하세요.