Implantação remota privada
Este padrão executa o Libre WebUI, o Ollama e o Cloudflare Tunnel em um único host Docker sem publicar as portas do aplicativo nem do Ollama. O Cloudflare Access é o limite externo de identidade; a autenticação do Libre WebUI continua sendo o limite interno. Work e Watchtower são opções separadas, com privilégios equivalentes aos do root.
Este modelo usa a topologia solo com uma única réplica: SQLite, blobs locais criptografados, vetores integrados, coordenação local e um worker de tarefas integrado compartilham o volume de dados do aplicativo. Não o transforme em uma implantação de equipe alterando seletores de backend em .env. Implantações de equipe devem usar o arquivo docker-compose.team.yml do repositório (e docker-compose.team.work.yml quando o Work estiver habilitado), que provisiona PostgreSQL/PGVector, armazenamento S3 versionado, Redis, um worker externo e o gateway como uma única topologia coordenada.
Use deploy/private/docker-compose.yml como ponto de partida. Por padrão, ele usa a imagem main:
LIBRE_WEBUI_IMAGE=ghcr.io/libre-webui/libre-webui:main
A tag dev é adequada para uma instância de desenvolvimento que tenha aderido explicitamente, não para o padrão dos clientes.
Modelo de segurança
- O Cloudflare Access protege todo o nome do host, inclusive
/api/*e upgrades de WebSocket. Não adicione caminhos públicos de desvio. - O Libre WebUI exige uma conta atual para as APIs do aplicativo. Operações de ciclo de vida de modelos e do Work exigem que a função atual no banco de dados seja de administrador.
- O aplicativo, o Ollama, o SearXNG e o cloudflared usam somente uma rede privada do Compose. O host não publica nenhuma porta do aplicativo.
- O serviço SearXNG integrado fornece a pesquisa na web opcional. Ele é somente interno e permanece inerte até que um administrador habilite a pesquisa em Configurações > Pesquisa; defina
SEARXNG_SECRETem.envantes de iniciar a pilha. - O aplicativo é executado como usuário não root, com sistema de arquivos raiz somente para leitura, sem recursos do Linux, com no-new-privileges e limites de CPU, memória e PID.
- O Work fica desabilitado, a menos que uma de suas substituições seja incluída. Quando habilitados, seus contêineres acrescentam sistema de arquivos raiz somente para leitura, remoção de recursos, limites de recursos, volume do workspace e política de rede com negação por padrão.
A pilha base não monta nenhum socket do Docker. Habilitar o Work com docker-compose.work-proxy.yml mantém essa condição: um proxy de socket em uma rede interna detém o socket e encaminha somente as seções da API usadas pelo Work (contêineres, imagens, volumes, redes, exec, informações); endpoints de swarm, segredos, build e sistema são negados pelo proxy, e o aplicativo não precisa montar o socket nem pertencer ao grupo do socket. O proxy reduz a superfície da API do Docker, não o impacto potencial do que encaminha — quem pode criar contêineres ainda pode montar caminhos do host —; portanto, trate-o como uma camada real de reforço, não como isolamento para múltiplos locatários.
As alternativas com socket bruto continuam sendo o maior limite de confiança: as substituições docker-compose.work.yml e do Watchtower fornecem a um contêiner um processo capaz de emitir chamadas arbitrárias à API do Docker, o que pode controlar o host. Montar o socket como somente leitura não transforma o acesso à API do Docker em somente leitura. O auxiliar de backup integrado se recusa a herdar um socket bruto do Docker; migre o Work para o proxy filtrado antes de depender de backups integrados agendados.
Preparação inicial
- Crie um operador sudo que não seja root e verifique o login SSH por chave antes de desabilitar o SSH do root.
- Copie
deploy/private/.env.examplepara/opt/libre-webui/.env, defina o modo0600, gere segredos exclusivos e dimensioneBLOB_QUOTA_BYTES_PER_USERpara o host.BLOB_QUOTA_RESERVATION_TTL_MSexpira reservas de upload abandonadas; o padrão é uma hora. - Se o Work for habilitado, defina
DOCKER_GIDcomo o grupo numérico proprietário de/var/run/docker.sock. - Armazene o token do Cloudflare Tunnel em
/opt/libre-webui/secrets/tunnel-token, com modo0640ou mais restrito. - Crie no Cloudflare Access um aplicativo auto-hospedado para o nome de host completo, use uma sessão de 24 horas e autorize somente as identidades pretendidas. Habilite Protect with Access na rota do túnel. Se o monitoramento exigir uma verificação pública de integridade, crie um aplicativo ou uma política separada e limitada somente ao caminho
/health/live. Nunca adicione uma política Bypass ampla ao aplicativo principal: políticas Bypass correspondentes anulam sua política Allow. - Mantenha
ENABLE_SIGNUP=false. Depois que a lista de permissões do Access proteger o nome do host, crie o primeiro administrador local; um banco de dados vazio permite automaticamente essa única conta inicial. Habilite o cadastro somente durante uma janela posterior deliberada. - Configure as restrições de nome de host do Turnstile e defina
TURNSTILE_EXPECTED_HOSTNAMEcomo o nome de host público exato.
Inicie e verifique:
cd /opt/libre-webui
docker compose config --quiet
docker compose up -d
docker compose ps
Para habilitar o Work, inclua deliberadamente a substituição do proxy de socket:
docker compose -f docker-compose.yml -f docker-compose.work-proxy.yml up -d
A variante com socket bruto (docker-compose.work.yml) continua disponível para implantações que precisam dela, com as consequências de confiança descritas acima.
Quando o Access estiver ativo, testes rápidos pela linha de comando precisarão de um token de serviço do Cloudflare Access, a menos que o caminho exato tenha um desvio limitado. Armazene as credenciais fora do histórico do shell e envie os dois cabeçalhos:
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
Uma solicitação não autenticada a uma API protegida do aplicativo deve devolver 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
Reforço do host
O diretório inclui um drop-in do sshd e uma jail do fail2ban. Antes de aplicar o drop-in do sshd, verifique uma sessão sudo separada e não root em outro terminal. Teste a configuração com sshd -t antes de recarregar o SSH.
Use o UFW (ou um firewall equivalente) para negar por padrão o tráfego de entrada e permitir somente SSH com limite de taxa. O Docker não publica portas de serviço neste modelo:
ufw default deny incoming
ufw default allow outgoing
ufw limit OpenSSH
ufw enable
Mantenha habilitadas as atualizações de segurança automáticas. Desabilite o encaminhamento X11, do agente e TCP, a menos que a implantação tenha uma necessidade documentada.
Backups e recuperação
Antes de fazer um backup, execute o inventário de recuperação somente para leitura dentro do contêiner da implantação em execução. Isso usa a versão exata do aplicativo implantado, o ambiente e o volume de dados montado. Um comando executado a partir de um checkout no host pode inspecionar o banco de dados errado ou usar um código-fonte diferente da imagem implantada.
docker exec libre-webui \
libre-webui recovery-check --json --data-dir /app/backend/data
O status de saída 0 indica que nenhum impedimento para a recuperação foi encontrado, 1 indica que o relatório JSON contém impedimentos e 2 indica que o comando não pôde ser executado. O relatório inclui somente uma impressão digital da chave de criptografia e indicadores de presença de segredos; ele nunca imprime uma chave nem outro valor secreto. Mantenha o inventário junto com o backup correspondente para que os operadores possam comparar a versão do aplicativo, a impressão digital do esquema, os recursos esperados do Work e as exclusões antes de uma restauração.
Crie chaves dedicadas de criptografia e assinatura de backups usando exatamente a imagem implantada. Mantenha este diretório fora do volume do aplicativo e copie a chave de criptografia e a chave privada de assinatura para um local separado e protegido de recuperação:
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
A geração de chaves se recusa a substituir arquivos de saída existentes. Nunca gere novas chaves por cima de um conjunto de backups existente: perder a chave de criptografia do arquivo ou a identidade de assinatura torna inutilizável a prova de recuperação correspondente.
Instale os scripts de backup e restauração e as unidades systemd fornecidos e, depois, habilite o 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
A unidade pode ler substituições exclusivas de manutenção no arquivo /etc/libre-webui/backup.env; ela não carrega o .env do aplicativo. Crie o arquivo como root somente quando precisar de uma substituição:
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 e LIBRE_WEBUI_BACKUP_KEY_DIR podem ser definidos diretamente nesse arquivo. Mantenha-o como propriedade do root e no modo 0600. Um diretório de chaves personalizado deve continuar legível pelo root dentro do sandbox do systemd.
Alterar LIBRE_WEBUI_BACKUP_DIR também muda o limite de gravação do systemd. O diretório deve existir antes que o serviço seja iniciado, e a unidade precisa de um drop-in correspondente. Por exemplo, depois de definir LIBRE_WEBUI_BACKUP_DIR=/srv/backups/libre-webui em backup.env:
install -d -m 0700 /srv/backups/libre-webui
systemctl edit libre-webui-backup.service
Adicione este caminho exato no editor e recarregue a unidade:
[Service]
ReadWritePaths=/srv/backups/libre-webui
systemctl daemon-reload
systemctl start libre-webui-backup.service
Sem a entrada ReadWritePaths= correspondente, ProtectSystem=strict impede corretamente que o timer grave em um local personalizado.
O serviço de backup permite até seis horas para arquivos grandes. O auxiliar adquire um bloqueio no host, interrompe o aplicativo somente se ele já estava em execução e cria o arquivo sobre o volume inativo usando a imagem implantada exata. O arquivo contém um manifesto assinado e uma carga criptografada pelo operador; ele inclui o diretório de dados e a configuração de ambiente e segredos necessária para abrir esse estado. Em seguida, o auxiliar verifica de forma independente o arquivo completo antes de publicar atomicamente seu relatório de metadados. Seus contêineres de manutenção somente para leitura recebem um tmpfs /tmp privado e gravável para a inspeção do SQLite e a verificação autenticada do arquivo; nenhum texto simples temporário é persistido na camada do contêiner. Copie os dois arquivos e as chaves de recuperação protegidas separadamente para outro host.
Quando o Work usa docker-compose.work-proxy.yml, a recuperação também deve comprovar que todos os volumes do Work referenciados pelo banco de dados ainda existem. O auxiliar lê o DOCKER_HOST do aplicativo implantado, localiza o serviço de proxy de socket no mesmo projeto Compose em execução e descobre a única rede interna compartilhada por meio das conexões reais de rede do Docker. O Compose acrescenta o nome do projeto como prefixo dessa rede, portanto não configure nem codifique por suposição o nome dela. Somente o contêiner de criação do arquivo entra nessa rede interna e pode acessar o proxy filtrado; ele não recebe socket bruto. A verificação independente do arquivo continua com --network none. Proxy ausente, endpoint inesperado, rede compartilhada externa ou ambígua ou montagem de socket bruto causa uma falha antes que o aplicativo seja interrompido e antes que um arquivo seja publicado.
Teste a recuperação em um novo volume sem substituir o volume ativo:
LIBRE_WEBUI_RESTORE_IMAGE="$image_ref" \
libre-webui-restore \
/var/backups/libre-webui/libre-webui-integrated-YYYYMMDDTHHMMSSZ.lwb \
libre-webui-restore-drill
O auxiliar de restauração recusa um volume ou destino de configuração existente, verifica o arquivo e seu inventário interno de recuperação em um armazenamento descartável, copia os dados para o novo volume e grava runtime.json e secrets.json recuperados com permissões privadas. Ele nunca reconfigura nem inicia a pilha ativa. Inspecione a configuração recuperada, atualize deliberadamente os valores específicos da implantação e teste o volume restaurado com uma pilha isolada.
Modelos do Ollama podem ser baixados novamente. Volumes Docker do Work, PVCs do Work no Kubernetes e pastas do Work vinculadas ao host ficam fora do diretório de dados do aplicativo e exigem snapshots coordenados e uma política de retenção próprios.
Atualizações
O Libre WebUI mantém estado mesmo quando a tag de sua imagem é mutável. O arquivo Compose base marca permanentemente o aplicativo como excluído do Watchtower. Atualize-o somente como uma ação coordenada do operador:
- Registre o ID da imagem em execução e resolva a substituição revisada como um digest imutável.
- Execute
libre-webui recovery-check, inicie o serviço de backup e exija um arquivo recém-criado e seu relatório de verificação antes de continuar. - Defina
LIBRE_WEBUI_IMAGEcomo o digest revisado, baixe-o e recrie somentelibre-webuicom o Docker Compose. Não remova nem recrie o volume de dados. - Exija que os testes rápidos de
/health/ready, login, sessão/histórico, recuperação de documentos e Work sejam aprovados. Se isso não acontecer, reverta para o digest de imagem registrado e preserve tanto o estado com falha quanto o backup verificado para diagnóstico.
A sequência no host é intencionalmente manual. Substitua o digest somente depois de revisá-lo e inspecione o par .lwb e .json mais recente antes do download:
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}}'
A substituição opcional do Watchtower com acesso ao socket continua disponível somente para os sidecars identificados explicitamente no arquivo base:
docker compose \
-f docker-compose.yml \
-f docker-compose.watchtower.yml \
up -d
O Watchtower verifica o Ollama e o SearXNG a cada 30 minutos. Os dados de modelos do Ollama permanecem no volume nomeado, e a configuração do SearXNG permanece na montagem vinculada. Ele não atualiza o Libre WebUI, o cloudflared, o proxy de socket do Work nem os sandboxes do Work. Uma implantação de cliente acompanha main; uma instância experimental pode selecionar :dev, mas o aplicativo ainda exige a mesma atualização manual condicionada por backup. Nunca conecte esta pilha privada individual a serviços de persistência da topologia de equipe; implante a topologia de equipe completa.