Triển khai từ xa riêng tư
Mẫu này chạy Libre WebUI, Ollama và Cloudflare Tunnel trên một host Docker mà không publish port app hoặc Ollama. Cloudflare Access là boundary identity bên ngoài; xác thực Libre WebUI vẫn là boundary bên trong. Work và Watchtower là lựa chọn riêng với quyền tương đương root.
Đây là topology solo một replica: SQLite, blob local mã hóa, vector embedded,
điều phối local và worker bền vững tích hợp dùng chung volume data app. Không
biến nó thành deployment team bằng cách đổi selector backend trong .env.
Deployment team phải dùng docker-compose.team.yml (và
docker-compose.team.work.yml khi Work bật), cung cấp PostgreSQL/PGVector,
storage S3 có version, Redis, worker ngoài và gateway như một topology phối hợp.
Dùng deploy/private/docker-compose.yml làm điểm bắt đầu. Image mặc định là main:
LIBRE_WEBUI_IMAGE=ghcr.io/libre-webui/libre-webui:main
Tag dev chỉ phù hợp cho instance development được chọn rõ, không cho deployment client thường.
Mô hình bảo mật
- Cloudflare Access bảo vệ toàn bộ hostname, gồm
/api/*và WebSocket upgrade. Không thêm public bypass path. - API app cần account Libre WebUI active. Operation lifecycle model và Work cần database role administrator.
- App, Ollama, SearXNG và cloudflared chỉ dùng network Compose private. Host không publish port app.
- SearXNG đi kèm cung cấp tìm kiếm web tùy chọn. Service chỉ nội bộ và inactive đến khi administrator bật tìm kiếm trong Settings > Search; đặt
SEARXNG_SECRETtrong.envtrước khi start stack. - App chạy non-root, filesystem root read-only, không Linux capability, có no-new-privileges và limit CPU, memory, PID.
- Work tắt đến khi thêm một file override. Container Work nhận root filesystem read-only, capability đã drop, resource limit, workspace volume và policy network default-deny.
Stack cơ sở không mount Docker socket. Khi Work bật bằng
docker-compose.work-proxy.yml, socket proxy trong network nội bộ giữ socket và
chỉ chuyển các phần API Work dùng (containers, images, volumes, networks, exec,
info). Swarm, secrets, build và system bị block; app không cần mount socket hay
thuộc group. Proxy thu hẹp API surface Docker, không thu hẹp tác động của
operation được chuyển: process tạo container vẫn có thể mount host path. Xem
đây là lớp hardening, không phải isolation multi-tenant.
Lựa chọn raw socket vẫn là trust boundary rộng nhất:
docker-compose.work.yml và override Watchtower cho container process có thể gọi
Docker API tùy ý và control host. Mount socket read-only không làm access Docker
API read-only. Backup helper tích hợp từ chối inherit raw socket; chuyển Work
sang proxy được lọc trước khi dựa vào backup theo lịch.
Thiết lập ban đầu
- Tạo operator non-root có sudo và verify login SSH bằng key trước khi tắt SSH root.
- Copy
deploy/private/.env.exampleđến/opt/libre-webui/.env, đặt mode0600, tạo secret unique và định cỡBLOB_QUOTA_BYTES_PER_USERcho host.BLOB_QUOTA_RESERVATION_TTL_MSgiới hạn reservation upload bị bỏ; mặc định một giờ. - Nếu bật Work, đặt
DOCKER_GIDthành ID số của group sở hữu/var/run/docker.sock. - Lưu token Cloudflare Tunnel tại
/opt/libre-webui/secrets/tunnel-tokenvới mode0640hoặc chặt hơn. - Tạo app self-hosted Cloudflare Access cho toàn hostname, session 24 giờ và chỉ cho identity mong muốn. Bật Protect with Access trên route Tunnel. Nếu monitoring cần health check public, tạo app/policy riêng chỉ cho
/health/live. Không thêm policy Bypass rộng: Bypass khớp sẽ thắng policy Allow. - Giữ
ENABLE_SIGNUP=false. Sau khi allowlist Access bảo vệ hostname, tạo administrator local đầu; database rỗng cho phép account bootstrap duy nhất đó. Chỉ bật signup trong cửa sổ có chủ ý. - Cấu hình hạn chế hostname Turnstile và đặt
TURNSTILE_EXPECTED_HOSTNAMEthành tên public chính xác.
Start và verify:
cd /opt/libre-webui
docker compose config --quiet
docker compose up -d
docker compose ps
Với Work, thêm override socket proxy:
docker compose -f docker-compose.yml -f docker-compose.work-proxy.yml up -d
Biến thể raw socket (docker-compose.work.yml) vẫn có với hệ quả trust ở trên.
Sau khi Access active, test command line cần service token Cloudflare Access trừ khi path có bypass hẹp. Giữ credential ngoài shell history và gửi cả hai header:
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
Request chưa xác thực đến API được bảo vệ phải trả 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
Hardening host
Directory có config sshd bổ sung và jail fail2ban. Trước áp dụng, verify session
operator non-root có sudo trong terminal khác. Test sshd -t trước reload SSH.
Dùng UFW hoặc firewall tương đương để mặc định từ chối ingress và chỉ cho SSH rate-limited. Docker không publish port service trong template:
ufw default deny incoming
ufw default allow outgoing
ufw limit OpenSSH
ufw enable
Giữ update bảo mật tự động active. Tắt forwarding X11, agent và TCP trừ khi có nhu cầu documented.
Backup và recovery
Trước backup, chạy inventory recovery read-only trong container active. Việc này dùng version app, environment và volume thực sự deployed. Command từ checkout host có thể kiểm tra nhầm database hoặc source.
docker exec libre-webui \
libre-webui recovery-check --json --data-dir /app/backend/data
Code 0 nghĩa không blocker, 1 nghĩa JSON report có blocker và 2 nghĩa
command lỗi. Report chỉ có fingerprint encryption key và dấu hiệu secret tồn
tại; giá trị secret không in. Lưu inventory cùng backup để so version, schema
fingerprint, resource Work và exception trước recovery.
Tạo encryption key và signing key riêng bằng image chính xác. Giữ directory ngoài volume app và copy private key đến vị trí recovery riêng:
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
Key generation từ chối file có sẵn. Không ghi đè backup set: mất encryption key hoặc signing identity làm recovery proof vô dụng.
Cài script backup/recovery và unit systemd rồi bật timer:
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
Unit có thể đọc maintenance override từ /etc/libre-webui/backup.env; .env
của app không load. Chỉ tạo bằng root khi cần:
install -d -m 0750 /etc/libre-webui
install -m 0600 /dev/null /etc/libre-webui/backup.env
Tại đó có thể đặt LIBRE_WEBUI_STACK_DIR,
LIBRE_WEBUI_BACKUP_RETENTION_DAYS, LIBRE_WEBUI_CONTAINER_NAME và
LIBRE_WEBUI_BACKUP_KEY_DIR. File phải thuộc root, mode 0600. Directory key
riêng phải để root đọc được trong sandbox systemd.
Đổi LIBRE_WEBUI_BACKUP_DIR cũng đổi boundary write systemd. Directory phải tồn
tại và unit cần drop-in khớp. Sau khi đặt
LIBRE_WEBUI_BACKUP_DIR=/srv/backups/libre-webui trong backup.env:
install -d -m 0700 /srv/backups/libre-webui
systemctl edit libre-webui-backup.service
Thêm path chính xác rồi reload unit:
[Service]
ReadWritePaths=/srv/backups/libre-webui
systemctl daemon-reload
systemctl start libre-webui-backup.service
Không có ReadWritePaths=, ProtectSystem=strict ngăn write vào vị trí custom.
Service cho tối đa sáu giờ với archive lớn. Helper lấy host lock, chỉ stop app nếu
đang chạy và tạo archive từ volume yên bằng image chính xác. Archive có
manifest ký và payload do operator mã hóa, gồm data, runtime và config secret.
Archive được verify đầy đủ trước khi metadata report publish atomic. Container
maintenance read-only nhận tmpfs /tmp private để check; plaintext tạm không
persist. Copy file và key ra ngoài host.
Khi Work dùng docker-compose.work-proxy.yml, recovery cũng phải chứng minh từng
volume Work. Helper đọc DOCKER_HOST, tìm socket-proxy trong project Compose
active và xác định một network nội bộ chung từ attachment Docker thật. Không
đoán tên network vì Compose thêm prefix project. Chỉ container tạo archive tham
gia và tới proxy; không nhận raw socket. Verification độc lập dùng
--network none. Thiếu proxy, endpoint bất ngờ, network ngoài/mơ hồ hoặc raw
socket sẽ fail trước khi app dừng và archive publish.
Test recovery vào volume mới:
LIBRE_WEBUI_RESTORE_IMAGE="$image_ref" \
libre-webui-restore \
/var/backups/libre-webui/libre-webui-integrated-YYYYMMDDTHHMMSSZ.lwb \
libre-webui-restore-drill
Helper từ chối volume hoặc target config có sẵn, verify archive và inventory
trong storage disposable, rồi copy data vào volume mới và ghi runtime.json,
secrets.json với permission private. Nó không đổi hay start stack active.
Kiểm tra config và test bằng stack biệt lập.
Model Ollama có thể download lại. Volume Docker Work, PVC Kubernetes Work và folder Work host nằm ngoài directory app nên cần snapshot và retention riêng.
Cập nhật
Libre WebUI giữ state dù tag image mutable. Compose cơ sở luôn loại app khỏi Watchtower. Chỉ update như hành động operator phối hợp:
- Ghi ID image đang chạy và digest thay thế immutable đã review.
- Chạy
libre-webui recovery-check, start backup và yêu cầu archive/report mới. - Đặt
LIBRE_WEBUI_IMAGEthành digest, pull và recreate chỉlibre-webui; không xóa data volume. - Yêu cầu
/health/ready, login, session/history, document và test Work. Nếu lỗi, quay lại digest và giữ state lỗi cùng backup.
Trình tự host cố ý manual. Kiểm tra cặp .lwb/.json mới nhất trước pull:
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}}'
Override Watchtower có socket chỉ dành cho sidecar được label:
docker compose \
-f docker-compose.yml \
-f docker-compose.watchtower.yml \
up -d
Watchtower kiểm tra Ollama và SearXNG mỗi 30 phút. Data model Ollama giữ trong
named volume và config SearXNG trong host mount. Watchtower không update Libre
WebUI, cloudflared, socket proxy Work hoặc sandbox Work. Deployment client theo
main; instance thử nghiệm có thể chọn :dev nhưng app vẫn cần update manual
bắt buộc qua backup verified. Không nối stack solo private này vào service
persistence team; triển khai topology team đầy đủ.