การติดตั้งส่วนตัวจากระยะไกล
รูปแบบนี้รัน Libre WebUI, Ollama และ Cloudflare Tunnel บนโฮสต์ Docker เครื่องเดียวโดยไม่เผยแพร่พอร์ตของแอปหรือ Ollama Cloudflare Access เป็นขอบเขตตัวตนชั้นนอก ส่วนการยืนยันตัวตนของ Libre WebUI ยังคงเป็นขอบเขตชั้นใน Work และ Watchtower เป็นตัวเลือกแยกที่มอบสิทธิ์เทียบเท่า root
นี่คือ topology solo แบบหนึ่ง replica: SQLite, blob ในเครื่องที่เข้ารหัส, เวกเตอร์ฝัง, การประสานงานในเครื่อง และ durable worker แบบฝัง ใช้ data volume ของแอปร่วมกัน อย่าเปลี่ยนเป็นการติดตั้ง team ด้วยการแก้ตัวเลือก backend ใน .env การติดตั้ง team ต้องใช้ docker-compose.team.yml (และ docker-compose.team.work.yml เมื่อเปิด Work) พร้อม PostgreSQL/PGVector, พื้นที่เก็บ S3 แบบมีเวอร์ชัน, Redis, worker ภายนอก และ gateway เป็น topology ที่ประสานกัน
ใช้ deploy/private/docker-compose.yml เป็นจุดเริ่มต้น image เริ่มต้นคือ main:
LIBRE_WEBUI_IMAGE=ghcr.io/libre-webui/libre-webui:main
แท็ก dev เหมาะเฉพาะ instance สำหรับพัฒนาที่เลือกอย่างชัดเจน ไม่เหมาะกับการติดตั้งทั่วไปของลูกค้า
รูปแบบความปลอดภัย
- Cloudflare Access ปกป้อง hostname ทั้งหมด รวม
/api/*และการอัปเกรด WebSocket อย่าเพิ่มเส้นทาง bypass สาธารณะ - API ของแอปต้องใช้บัญชี Libre WebUI ที่ active การทำงานวงจรชีวิตโมเดลและ Work ต้องใช้บทบาทผู้ดูแลระบบในฐานข้อมูล
- แอป, Ollama, SearXNG และ cloudflared ใช้เฉพาะเครือข่าย Compose ส่วนตัว โฮสต์ไม่เผยแพร่พอร์ตแอป
- SearXNG ที่มาพร้อมระบบให้บริการการค้นหาเว็บแบบทางเลือก บริการนี้อยู่ภายในเท่านั้นและยังไม่ active จนผู้ดูแลเปิดการค้นหาใน Settings > Search; ตั้ง
SEARXNG_SECRETใน.envก่อนเริ่ม stack - แอปรันแบบ non-root, root filesystem อ่านอย่างเดียว, ไม่มี Linux capability, ใช้ no-new-privileges และจำกัด CPU, หน่วยความจำ และ PID
- Work ปิดอยู่จนกว่าจะเพิ่มไฟล์ override คอนเทนเนอร์ Work ได้รับ root filesystem แบบอ่านอย่างเดียว, ตัด capability, จำกัดทรัพยากร, มี workspace volume และใช้นโยบายเครือข่ายแบบปฏิเสธโดยค่าเริ่มต้น
stack พื้นฐานไม่ mount Docker socket เมื่อเปิด Work ด้วย docker-compose.work-proxy.yml, socket proxy ในเครือข่ายภายในจะถือ socket และส่งต่อเฉพาะส่วน API ที่ Work ใช้ (containers, images, volumes, networks, exec, info) ส่วน Swarm, secrets, build และ system ถูกบล็อก แอปไม่ต้อง mount socket หรืออยู่ในกลุ่ม proxy ลดพื้นผิว Docker API แต่ไม่ได้ลดผลกระทบของ operation ที่ส่งผ่าน เพราะโปรเซสที่สร้างคอนเทนเนอร์ยัง mount เส้นทางโฮสต์ได้ ให้ถือเป็นชั้นเพิ่มความปลอดภัย ไม่ใช่การแยก multi-tenant
ตัวเลือก raw socket ยังคงเป็น trust boundary ที่กว้างที่สุด: docker-compose.work.yml และ override ของ Watchtower ทำให้โปรเซสคอนเทนเนอร์เรียก Docker API และควบคุมโฮสต์ได้ตามต้องการ การ mount socket แบบอ่านอย่างเดียวไม่ได้ทำให้การเข้าถึง Docker API เป็นแบบอ่านอย่างเดียว เครื่องมือสำรองแบบรวมจะปฏิเสธการรับ raw socket ต่อมา ให้เปลี่ยน Work ไปใช้ proxy ที่กรองแล้วก่อนพึ่งการสำรองตามกำหนดเวลา
การตั้งค่าเริ่มต้น
- สร้างผู้ดำเนินการ non-root ที่มี sudo และตรวจว่าลงชื่อเข้าใช้ SSH ด้วยคีย์ได้ก่อนปิด SSH ของ root
- คัดลอก
deploy/private/.env.exampleไปที่/opt/libre-webui/.env, ตั้งโหมด0600, สร้าง secret ที่ไม่ซ้ำ และกำหนดขนาดBLOB_QUOTA_BYTES_PER_USERให้เหมาะกับโฮสต์BLOB_QUOTA_RESERVATION_TTL_MSจำกัด reservation อัปโหลดที่ถูกทิ้ง; ค่าเริ่มต้นคือหนึ่งชั่วโมง - หากเปิด Work ให้ตั้ง
DOCKER_GIDเป็น ID ตัวเลขของกลุ่มที่เป็นเจ้าของ/var/run/docker.sock - เก็บ token ของ Cloudflare Tunnel ที่
/opt/libre-webui/secrets/tunnel-tokenด้วยโหมด0640หรือเข้มงวดกว่า - สร้างแอป Cloudflare Access แบบ self-hosted สำหรับ hostname ทั้งหมด ตั้งเซสชัน 24 ชั่วโมง และอนุญาตเฉพาะตัวตนที่ต้องการ เปิด Protect with Access บนเส้นทาง Tunnel หากระบบติดตามต้องใช้ health check สาธารณะ ให้สร้างแอป/นโยบายแยกเฉพาะ
/health/liveอย่าเพิ่มนโยบาย Bypass กว้าง เพราะ Bypass ที่ตรงจะชนะนโยบาย Allow - รักษา
ENABLE_SIGNUP=falseหลัง allowlist ของ Access ปกป้อง hostname แล้ว ให้สร้างผู้ดูแลระบบในเครื่องคนแรก ฐานข้อมูลว่างอนุญาตบัญชี bootstrap เดียวนั้น เปิด signup เฉพาะช่วงเวลาที่ตั้งใจ - กำหนด Turnstile ให้จำกัด hostname และตั้ง
TURNSTILE_EXPECTED_HOSTNAMEเป็นชื่อสาธารณะที่ตรงทุกตัว
เริ่มและตรวจสอบ:
cd /opt/libre-webui
docker compose config --quiet
docker compose up -d
docker compose ps
เมื่อใช้ Work ให้เพิ่ม override ของ socket proxy:
docker compose -f docker-compose.yml -f docker-compose.work-proxy.yml up -d
รูปแบบ raw socket (docker-compose.work.yml) ยังมีให้ใช้ พร้อมผลด้านความไว้วางใจตามที่อธิบายด้านบน
หลัง Access active การทดสอบด้วยบรรทัดคำสั่งต้องใช้ service token ของ Cloudflare Access เว้นแต่เส้นทางจะมี bypass ที่แคบ เก็บข้อมูลรับรองออกจากประวัติ shell และส่งส่วนหัวทั้งสอง:
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 เสริมและ jail ของ fail2ban ก่อนนำไปใช้ ให้ตรวจใน terminal อื่นว่าเซสชันผู้ดำเนินการ non-root มี sudo ทดสอบ sshd -t ก่อน reload SSH
ใช้ UFW หรือ firewall เทียบเท่าเพื่อปฏิเสธ ingress ตามค่าเริ่มต้นและอนุญาตเฉพาะ SSH แบบจำกัดอัตรา Docker จะไม่เผยแพร่พอร์ตบริการในเทมเพลต:
ufw default deny incoming
ufw default allow outgoing
ufw limit OpenSSH
ufw enable
เปิดการอัปเดตความปลอดภัยอัตโนมัติไว้ ปิด forwarding ของ X11, agent และ TCP เว้นแต่มีความจำเป็นที่บันทึกไว้
การสำรองและกู้คืน
ก่อนสำรอง ให้รันรายการกู้คืนแบบอ่านอย่างเดียวภายในคอนเทนเนอร์ที่ active เพื่อใช้เวอร์ชันแอป สภาพแวดล้อม และ volume ที่ติดตั้งจริง คำสั่งจาก checkout บนโฮสต์อาจตรวจฐานข้อมูลหรือ source ผิดชุด
docker exec libre-webui \
libre-webui recovery-check --json --data-dir /app/backend/data
รหัส 0 หมายถึงไม่มีสิ่งกีดขวาง, 1 หมายถึงรายงาน JSON มีสิ่งกีดขวาง และ 2 หมายถึงคำสั่งผิดพลาด รายงานมีเพียงลายนิ้วมือคีย์เข้ารหัสและตัวบ่งชี้ว่ามี secret โดยไม่พิมพ์ค่า secret เก็บรายการนี้พร้อม backup เพื่อเปรียบเทียบเวอร์ชัน, schema fingerprint, resource Work และข้อยกเว้นก่อนกู้คืน
สร้างคีย์เข้ารหัสและ signing key แยกด้วย image ที่ตรงทุกตัว เก็บไดเรกทอรีไว้นอก volume แอปและคัดลอก private key ไปยังที่เก็บกู้คืนแยก:
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
การสร้างคีย์จะปฏิเสธไฟล์ที่มีอยู่ อย่าเขียนทับชุด backup เพราะการสูญเสียคีย์เข้ารหัสหรือตัวตนลงลายเซ็นจะทำให้หลักฐานการกู้คืนใช้ไม่ได้
ติดตั้งสคริปต์สำรอง/กู้คืนและ unit ของ systemd แล้วเปิด 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 อ่าน maintenance override จาก /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 ไดเรกทอรีคีย์แยกต้องให้ root อ่านได้ภายใน sandbox ของ systemd
การเปลี่ยน LIBRE_WEBUI_BACKUP_DIR จะเปลี่ยนขอบเขตการเขียนของ systemd ด้วย ไดเรกทอรีต้องมีอยู่และ unit ต้องมี drop-in ที่ตรงกัน หลังตั้ง LIBRE_WEBUI_BACKUP_DIR=/srv/backups/libre-webui ใน backup.env:
install -d -m 0700 /srv/backups/libre-webui
systemctl edit libre-webui-backup.service
เพิ่มเส้นทางที่ตรง แล้ว reload unit:
[Service]
ReadWritePaths=/srv/backups/libre-webui
systemctl daemon-reload
systemctl start libre-webui-backup.service
หากไม่มี ReadWritePaths=, ProtectSystem=strict จะป้องกันการเขียนไปยังตำแหน่งแบบกำหนดเอง
บริการให้เวลาสูงสุดหกชั่วโมงสำหรับ archive ขนาดใหญ่ เครื่องมือช่วยจะถือ lock ของโฮสต์ หยุดแอปเฉพาะเมื่อกำลังทำงาน และสร้าง archive จาก volume ที่หยุดนิ่งด้วย image ที่ตรงทุกตัว archive มี manifest ลงลายเซ็นและ payload ที่ผู้ดำเนินการเข้ารหัส ซึ่งรวมข้อมูล runtime และ secret การกำหนดค่า ระบบจะตรวจ archive ทั้งหมดก่อนเผยแพร่รายงาน metadata แบบ atomic คอนเทนเนอร์ maintenance แบบอ่านอย่างเดียวได้รับ tmpfs /tmp ส่วนตัวเพื่อใช้ตรวจ โดยไม่เก็บ plaintext ชั่วคราวถาวร ให้คัดลอกไฟล์และคีย์ออกจากโฮสต์
เมื่อ Work ใช้ docker-compose.work-proxy.yml การกู้คืนต้องพิสูจน์ volume Work ทุกตัวด้วย เครื่องมือช่วยอ่าน DOCKER_HOST, ค้นหา socket-proxy ในโครงการ Compose ที่ active และหาเครือข่ายภายในร่วมจาก attachment Docker จริง โดยไม่เดาชื่อเครือข่ายเพราะ Compose เติม prefix โครงการ เฉพาะคอนเทนเนอร์ที่สร้าง archive จะเข้าร่วมและเข้าถึง proxy โดยไม่ได้รับ raw socket การตรวจแยกใช้ --network none การขาด proxy, endpoint ที่ไม่คาดคิด, เครือข่ายภายนอก/กำกวม หรือ raw socket จะทำให้ล้มเหลวก่อนหยุดแอปและเผยแพร่ archive
ทดสอบการกู้คืนไปยัง volume ใหม่:
LIBRE_WEBUI_RESTORE_IMAGE="$image_ref" \
libre-webui-restore \
/var/backups/libre-webui/libre-webui-integrated-YYYYMMDDTHHMMSSZ.lwb \
libre-webui-restore-drill
เครื่องมือช่วยปฏิเสธ volume หรือการกำหนดค่าเป้าหมายที่มีอยู่ ตรวจ archive และรายการภายในพื้นที่เก็บชั่วคราว จากนั้นคัดลอกข้อมูลไปยัง volume ใหม่และเขียน runtime.json, secrets.json ด้วยสิทธิ์ส่วนตัว โดยไม่แก้หรือเริ่ม stack ที่ active ให้ตรวจการกำหนดค่าและทดสอบด้วย stack ที่แยกต่างหาก
ดาวน์โหลดโมเดล Ollama ใหม่ได้ ส่วน Docker volume ของ Work, Kubernetes PVC ของ Work และโฟลเดอร์ Work บนโฮสต์อยู่นอกไดเรกทอรีแอป จึงต้องมี snapshot และ retention แยก
การอัปเดต
Libre WebUI เก็บสถานะแม้แท็ก image เปลี่ยนแปลงได้ Compose พื้นฐานจะแยกแอปออกจาก Watchtower เสมอ ให้อัปเดตเป็นการดำเนินการที่ผู้ดูแลประสานเท่านั้น:
- บันทึก ID ของ image ที่กำลังรันและ digest ทดแทนแบบเปลี่ยนไม่ได้ซึ่งตรวจแล้ว
- รัน
libre-webui recovery-check, เริ่ม backup และกำหนดให้มี archive/report ใหม่ - ตั้ง
LIBRE_WEBUI_IMAGEเป็น digest, pull และสร้างใหม่เฉพาะlibre-webui; ห้ามลบ data volume - ตรวจ
/health/ready, การลงชื่อเข้าใช้, session/history, เอกสาร และ Work หากล้มเหลว ให้ย้อนกลับไป digest เดิมและเก็บสถานะที่ล้มเหลวพร้อม backup
ขั้นตอนบนโฮสต์ตั้งใจให้ทำด้วยตนเอง ตรวจคู่ .lwb/.json ล่าสุดก่อน 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 ที่มี socket จำกัดเฉพาะ sidecar ที่ติด label:
docker compose \
-f docker-compose.yml \
-f docker-compose.watchtower.yml \
up -d
Watchtower ตรวจ Ollama และ SearXNG ทุก 30 นาที ข้อมูลโมเดล Ollama อยู่ใน named volume และการกำหนดค่า SearXNG อยู่ใน host mount Watchtower ไม่อัปเดต Libre WebUI, cloudflared, socket proxy ของ Work หรือ sandbox Work การติดตั้งทั่วไปติดตาม main; instance ทดลองเลือก :dev ได้ แต่แอปยังต้องอัปเดตด้วยตนเองผ่าน backup ที่ตรวจแล้ว ห้ามเชื่อม stack solo ส่วนตัวนี้กับบริการ persistence แบบ team ให้ติดตั้ง topology team แบบเต็ม