Pular para o conteúdo
Ollama

Ollama e Open WebUI: sua própria IA numa VPS

Atualizado em
Ilustração colorida de um unicórnio de crina arco-íris diante de um espelho mágico que mostra balões de conversa, com um pequeno dragão verde deitado ao lado, velas flutuantes e prateleiras de pergaminhos ao fundo

Olá meus Unicórnios! 🦄✨

Sabe aquela vontade de ter um ChatGPT que é seu? Que não manda o que você digita para servidor de ninguém, não cobra por mensagem, não tem limite de uso no fim do mês e continua funcionando mesmo se a empresa dona dele mudar os planos amanhã? 🙋‍♀️

Pois é exatamente isso que dá para montar numa VPS baratinha, e é o que a gente vai fazer hoje: instalar o Ollama, que é o motor que roda o modelo de IA, e o Open WebUI, que é a telinha de conversa igual à que você já conhece. No fim você abre o navegador, digita seu e-mail e senha, e conversa com uma IA que mora num servidor seu. 🏰

E eu sei o que você está pensando: "mas não precisa de uma placa de vídeo de dez mil reais?". Para modelo pequeno, não. A VPS que usei nesta instalação tem 4 vCPU, 7,8 GB de RAM e nenhuma GPU, e o modelo respondeu numa velocidade perfeitamente utilizável. Vou mostrar os números medidos ao longo do caminho.

Este tutorial é irmão do artigo da Evolution API: mesma VPS, mesmo jeito de trabalhar por SSH, e vou explicar cada comando supondo que você nunca abriu um terminal Linux na vida. Se você já é da casa, pule os quadrinhos de explicação. Se não é, eles são justamente para você. 🤗

🧩 O que é cada peça (em duas frases cada)

Antes de sair digitando, vale entender o que estamos instalando, porque são dois programas e eles fazem coisas bem diferentes.

O Ollama é o motor. É ele que baixa o modelo de IA, carrega na memória e responde às perguntas. Ele não tem tela nenhuma: fala só por uma API, numa porta, em JSON. Sozinho, ele é útil para programar, não para conversar.

O Open WebUI é a tela. É a interface de conversa, parecidíssima com a do ChatGPT, com histórico de conversas, login de usuário e painel de administração. Ele não sabe pensar: ele conversa com o Ollama e mostra o resultado bonitinho para você.

A imagem mental que eu uso é essa: o Ollama é o motor do carro e o Open WebUI é o painel com volante e banco. Você pode ter só o motor, mas vai dirigir com um alicate. 🔧

💻 Entrando na VPS pelo SSH

Tudo aqui acontece dentro da VPS, e a porta de entrada dela é o SSH. Se você nunca usou: SSH é um jeito de digitar comandos num computador que está longe, como se você estivesse sentado na frente dele.

No Windows 10 e 11 já vem pronto. Abra o Prompt de Comando ou o PowerShell (tecla Windows, digite "powershell", Enter) e mande:

ssh root@SEU_IP_AQUI

Troque SEU_IP_AQUI pelo endereço que o seu provedor de VPS te mandou por e-mail. Na primeira vez ele pergunta se você confia na máquina; responda yes e tecle Enter. Depois ele pede a senha.

E já que falei em colar: no terminal, Ctrl+V não funciona. Para colar use Ctrl+Shift+V, ou clique com o botão direito do mouse. Esse detalhe boba trava todo mundo na primeira vez, e a pessoa acha que o terminal quebrou. 😅

Deu certo? Você vai ver algo assim, e a linha passa a começar com o nome da sua máquina:

Welcome to Ubuntu 24.04.4 LTS (GNU/Linux 6.8.0-51-generic x86_64)

root@servidor:~#

🚀 Instalando o Ollama (é um comando só)

Essa parte é surpreendentemente simples. O Ollama tem um script oficial de instalação, e ele resolve tudo: baixa o programa, instala, cria o serviço do sistema e já deixa rodando.

curl -fsSL https://ollama.com/install.sh | sh

Traduzindo o comando: o curl baixa o script do site oficial, e o | sh manda executar o que veio. A barra vertical (|) é o "cano" do Linux: ela pega a saída de um comando e joga na entrada do outro.

Vai aparecer uma barra de progresso enquanto ele baixa (são uns 4 GB de programa, tenha paciência ☕). No fim, confira se deu certo:

ollama --version

A resposta na minha instalação:

ollama version is 0.32.14

E o serviço, que é o que garante que o Ollama sobe sozinho quando o servidor reinicia:

systemctl status ollama
● ollama.service - Ollama Service
     Loaded: loaded (/etc/systemd/system/ollama.service; enabled; preset: enabled)
     Active: active (running) since Mon 2026-08-17 22:44:32 CEST; 29min ago

As duas palavras que importam nessa saída são enabled (sobe sozinho depois de um reboot) e active (running) (está de pé agora). Se aparecer failed ou inactive, algo deu errado na instalação.

🐉 Baixando o modelo Qwen3 0.6B

O Ollama instalado é um motor sem combustível: ele não vem com modelo nenhum. Vamos baixar o Qwen3 0.6B, e a escolha tem motivo.

Aquele 0.6B quer dizer 600 milhões de parâmetros. Para comparar: os modelos que você usa no ChatGPT têm centenas de bilhões. É um modelo minúsculo, e é exatamente por isso que ele cabe numa VPS sem placa de vídeo. Ele não vai escrever seu TCC, mas responde, conversa, resume e serve muito bem para aprender a mexer na ferramenta sem gastar em hardware.

ollama pull qwen3:0.6b

O pull significa "puxe para cá". Terminado o download, veja o que você tem na máquina:

ollama list
NAME          ID              SIZE      MODIFIED
qwen3:0.6b    7df6b6e09427    522 MB    35 minutes ago

522 MB. É menos que muito aplicativo de celular. 🤯

Dá para conversar com ele agora mesmo, direto no terminal:

ollama run qwen3:0.6b

Ele abre um prompt esperando você digitar. Para sair, digite /bye e tecle Enter.

🔌 A API do Ollama, que é onde a graça começa

Conversar no terminal é legal por cinco minutos. O que realmente interessa é que o Ollama já sobe com uma API pronta, na porta 11434, e é ela que permite ligar a IA a qualquer sistema seu.

Vamos fazer a primeira chamada. Como o texto tem aspas dentro de aspas e isso confunde o terminal, o caminho mais seguro é gravar a pergunta num arquivo e mandar o arquivo:

printf '%s' '{"model":"qwen3:0.6b","prompt":"Quanto e 7 vezes 6? Responda so o numero.","stream":false,"think":false}' > pergunta.json

O printf escreve o texto, e o > joga o resultado dentro do arquivo pergunta.json em vez de mostrar na tela. Agora manda para a API:

curl http://127.0.0.1:11434/api/generate -d @pergunta.json

O -d quer dizer "mande estes dados", e o @ antes do nome quer dizer "os dados estão neste arquivo". A resposta vem em JSON, tudo grudado numa linha só. Para ler melhor, instale o jq, que é um formatador de JSON:

apt install -y jq
curl -s http://127.0.0.1:11434/api/generate -d @pergunta.json | jq

E aí sim ela fica legível:

{
    "model": "qwen3:0.6b",
    "created_at": "2026-08-17T20:58:11.482Z",
    "response": "7 vezes 6 é 42.",
    "done": true,
    "eval_count": 10,
    "total_duration": 1931496408
}

Repare em dois campos que vão importar mais para a frente:

  • eval_count: quantos tokens ele gerou. Token é o pedacinho de palavra que o modelo produz por vez; dez tokens é mais ou menos uma frase curta.
  • total_duration: quanto tempo levou, em nanossegundos. Aqueles 1931496408 são 1,93 segundo. Divida por um bilhão para chegar em segundos.

E o "think": false que eu coloquei na pergunta? Segura essa dúvida por três seções, porque ela vira uma surpresa boa. 😏

🖥️ Instalando o Open WebUI com Docker

Motor pronto. Agora a tela.

O Open WebUI roda em Docker, que é um jeito de rodar programas dentro de "caixinhas" isoladas, com tudo de que precisam lá dentro. Se você seguiu o tutorial da Evolution, o Docker já está instalado. Se não, é um comando:

curl -fsSL https://get.docker.com | sh

Confira:

docker --version

Agora, antes de subir a tela, precisamos resolver um detalhe que é a origem de metade dos problemas desta instalação.

🚧 O Ollama nasce trancado por dentro

Aqui está a primeira armadilha de verdade, e ela é sorrateira porque nada dá erro.

Por padrão, o Ollama escuta apenas em 127.0.0.1, o endereço que significa "eu mesmo". Veja com o comando ss, que lista as portas abertas:

ss -lntp | grep 11434
LISTEN 0  4096  127.0.0.1:11434  0.0.0.0:*  users:(("ollama",pid=35136,fd=4))

Aquele 127.0.0.1:11434 é o problema. O Open WebUI vai rodar dentro de um contêiner Docker, que para efeitos de rede é outra máquina. Do ponto de vista do Ollama, o contêiner é um estranho, e estranho não entra.

A correção é dizer ao Ollama para escutar em todas as interfaces. Isso se faz com um arquivo de configuração extra do systemd, que é o gerenciador de serviços do Ubuntu. Crie a pasta e abra o arquivo:

mkdir -p /etc/systemd/system/ollama.service.d
nano /etc/systemd/system/ollama.service.d/override.conf

O mkdir cria pasta (o -p cria as intermediárias que faltarem) e o nano é o editor de texto mais simples do Linux. Dentro dele, digite:

[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"

Para salvar no nano: Ctrl+O (a letra O, de output), Enter para confirmar o nome, e Ctrl+X para sair. Aqueles ^O e ^X no rodapé da tela significam justamente Ctrl+O e Ctrl+X: o acento circunflexo é a forma antiga de escrever "Ctrl".

Agora avise o sistema que a configuração mudou e reinicie o Ollama:

systemctl daemon-reload
systemctl restart ollama

Confira que mudou:

ss -lntp | grep 11434
LISTEN 0  4096  *:11434  *:*  users:(("ollama",pid=36840,fd=4))

O 127.0.0.1 virou *, que quer dizer "todas as interfaces". Agora o contêiner consegue falar com ele.

📦 Subindo a tela de conversa

Agora sim, o comando que sobe o Open WebUI:

docker run -d \
  --name open-webui \
  --restart always \
  -p 3000:8080 \
  --add-host=host.docker.internal:host-gateway \
  -e OLLAMA_BASE_URL=http://host.docker.internal:11434 \
  -e WEBUI_AUTH=True \
  -v open-webui:/app/backend/data \
  ghcr.io/open-webui/open-webui:main

É um comando comprido, então vale destrinchar linha por linha, porque cada uma resolve uma coisa:

  • -d roda em segundo plano, devolvendo o terminal para você.
  • --name open-webui dá um nome à caixinha, para você não precisar decorar o código dela.
  • --restart always faz o Docker subir isso de novo sozinho depois de um reboot. Sem essa linha, um reinício do servidor derruba seu painel e você só descobre quando for usar.
  • -p 3000:8080 liga a porta 3000 do servidor à porta 8080 de dentro da caixinha. A da esquerda é a que você acessa pelo navegador.
  • --add-host=host.docker.internal:host-gateway cria, dentro do contêiner, um apelido que aponta para a máquina hospedeira. É como dar o endereço de casa para quem mora no quintal.
  • -e OLLAMA_BASE_URL=... diz ao Open WebUI onde encontrar o motor, usando o apelido da linha anterior.
  • -e WEBUI_AUTH=True exige login. Nunca ponha False num servidor com IP público: seria deixar a IA aberta para o mundo inteiro.
  • -v open-webui:/app/backend/data guarda os dados (usuários, conversas) fora da caixinha, para que atualizar a imagem não apague tudo.

Ele começa a baixar a imagem. E aqui apareceu uma armadilha que não é culpa de ninguém:

41d6e7528dc0: Pull complete
f66a0c398e9a: Pull complete
2fe491d7c8d6: Download complete
failed to copy: read tcp [2a02:...]:33458->[2606:50c0:8000::154]:443: read: connection reset by peer

O download caiu no meio. A imagem do Open WebUI tem 7,16 GB descompactada, e uma conexão longa assim às vezes se perde no caminho (repare nos endereços com dois-pontos: é IPv6). A solução é sem graça e funciona: rode o mesmo comando de novo. O Docker aproveita o que já baixou e continua de onde parou. Na segunda tentativa foi:

3f7ec5c675f4: Pull complete
Digest: sha256:6a773e5c3a246b65cbe74ce942b294292c0e5f81c138f703d111bc162f7d7c3d
Status: Downloaded newer image for ghcr.io/open-webui/open-webui:main

⏳ "Up" não quer dizer pronto

Container no ar. Vamos ver:

docker ps

Ele aparece como Up, e a tentação é ir correndo abrir o navegador. Não vai funcionar ainda, e você vai achar que errou alguma coisa.

Eu fiquei medindo enquanto ele subia, batendo na porta a cada 5 segundos. O resultado:

  1: HTTP 000
  2: HTTP 000
  ...
 20: HTTP 000
 21: HTTP 200

Cento e cinco segundos respondendo 000 (que é o jeito do curl dizer "não consegui nem conectar") antes do primeiro 200. E o docker ps dizia Up ... (unhealthy) esse tempo todo.

Não é bug: o Open WebUI está preparando o banco de dados interno e carregando um modelo de embeddings na primeira subida. Contêiner "Up" é o Docker dizendo que o processo iniciou, não que a aplicação terminou de acordar. É a mesma pegadinha que me pegou instalando a Evolution API, e ela se repete com toda aplicação pesada.

O jeito honesto de esperar é olhando os logs:

docker logs -f open-webui

O -f é de follow: a tela fica aberta mostrando o que acontece ao vivo. Saia com Ctrl+C (isso encerra a visualização, não o contêiner).

🔥 O firewall que quebra tudo em silêncio

Essa é a armadilha deste tutorial. Se você só for ler uma seção, leia esta. 🙏

Depois de tudo pronto, eu abri o painel, fiz login, e a lista de modelos estava vazia. Nenhum erro na tela. Nenhuma mensagem vermelha. Só um seletor de modelos sem nada dentro, como se o Ollama não existisse.

Pela API dava para ver o tamanho do estrago:

{
    "data": []
}

E qualquer tentativa de conversar respondia:

{
    "detail": "Model not found"
}

Só que o Ollama estava rodando perfeitamente. Do próprio servidor:

curl -s http://127.0.0.1:11434/api/tags | jq -r '.models[].name'
qwen3:0.6b

Estava lá. Então testei de dentro do contêiner, que é o teste que realmente importa. O docker exec executa um comando dentro da caixinha:

docker exec open-webui curl -sv -m 8 http://host.docker.internal:11434/api/tags
*   Trying 172.17.0.1:11434...
* Connection timed out after 8001 milliseconds
* Closing connection 0

Achei. E repare no detalhe cruel: a mensagem é Connection timed out, não Connection refused.

Essa diferença é o diagnóstico inteiro:

  • Refused quer dizer que alguém atendeu e disse não. Normalmente é o serviço desligado ou na porta errada.
  • Timed out quer dizer que ninguém atendeu: o pacote saiu e sumiu no vazio. Isso é assinatura de firewall descartando pacote em silêncio.

O culpado era o ufw, o firewall do Ubuntu, com a política de entrada em DROP (descarta tudo o que não for explicitamente liberado). O endereço 172.17.0.1 é a ponte que o Docker cria, e ela não estava na lista de liberados.

A correção certa não é abrir a porta para o mundo. É liberar só a faixa de rede do Docker:

ufw allow from 172.17.0.0/16 to 172.17.0.1 port 11434 proto tcp comment 'Ollama so para containers'

Leia como uma frase: "permita, vindo da rede 172.17.0.0/16, para o endereço 172.17.0.1, na porta 11434". Nada fora do Docker se encaixa nessa descrição.

Depois, reinicie o painel para ele refazer a lista de modelos:

docker restart open-webui

E confirme que o contêiner enxerga o motor:

docker exec open-webui curl -s http://host.docker.internal:11434/api/tags | jq -r '.models[].name'
qwen3:0.6b

🎉

🔒 Deixando só a porta certa aberta

Falta liberar a porta do painel, que é a única que deve ficar pública:

ufw allow 3000/tcp
ufw status

O estado final do meu firewall, com o Ollama já configurado:

Status: active

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       Anywhere
3000/tcp                   ALLOW       Anywhere
172.17.0.1 11434/tcp       ALLOW       172.17.0.0/16   # Ollama so para containers

Leia essa tabela com atenção, porque ela é o desenho de segurança inteiro:

  • A 22 é o SSH, que é como você administra a máquina.
  • A 3000 é o painel, aberta para todo mundo, protegida por login.
  • A 11434 é o Ollama, e ela não está aberta para "Anywhere": só para a rede do Docker.

Testando de fora, do meu computador, a diferença aparece:

curl -s -o /dev/null -w "HTTP %{http_code}\n" http://SEU_IP_AQUI:3000/
curl -s -m 10 -o /dev/null -w "HTTP %{http_code}\n" http://SEU_IP_AQUI:11434/api/tags
HTTP 200
HTTP 000

200 para o painel, 000 para o Ollama: a segunda chamada nem conseguiu conectar. Exatamente o que a gente queria. 🛡️

👤 A primeira conta é a do dono

Abra http://SEU_IP_AQUI:3000 no navegador. Você cai na tela de cadastro, e aqui vale uma regra que o Open WebUI não avisa em lugar nenhum:

A primeira conta criada vira administradora. Não existe usuário padrão, não existe senha inicial: quem chegar primeiro é o dono. Crie a sua antes de sair divulgando a URL. 🏃‍♀️

Feito isso, a tela de login fica assim:

Tela de login do Open WebUI com os campos Email e Senha e o botão Entrar, sem nenhuma opção de criar conta

Repare no que não tem nessa tela: não há link de "criar conta". O Open WebUI fecha o cadastro sozinho depois do primeiro admin. Dá para confirmar tentando criar outra conta pela API:

{
    "detail": "You do not have permission to access this resource. Please contact your administrator for assistance."
}

Um 403 limpinho. E tem uma segunda camada: mesmo que o cadastro fosse reaberto, novos usuários entram com papel pendente e ficam esperando aprovação. As duas coisas ficam visíveis no painel de administração, em Configurações → Autenticação:

Painel de autenticação do Open WebUI mostrando Novos cadastros desligado, padrão para novos usuários definido como pendente e expiração do JWT em 4w

Três coisas nessa tela merecem seu olhar:

  • Novos cadastros: desligado. É o que devolve aquele 403.
  • Padrão para novos usuários: pendente. A segunda tranca.
  • Expiração do JWT: 4w. O login dura quatro semanas antes de pedir senha de novo. Se você for gerar token para usar na API, é esse o prazo de validade dele.

💬 Conversando pela primeira vez

Login feito, o painel abre com o modelo já selecionado no alto:

Painel do Open WebUI aberto com o modelo qwen3:0.6b selecionado e o campo Como posso ajudar você hoje

Ver o qwen3:0.6b escrito ali é o sinal de que a ponte com o Ollama está funcionando. Se esse nome não aparecer, volte para a seção do firewall: é lá que mora o problema.

Perguntei alguma coisa temática e olha o que aconteceu:

Resposta do modelo qwen3:0.6b no Open WebUI, com a indicação Pensado por 24 segundos acima do texto gerado

Está lá, em cinza, acima da resposta: "Pensado por 24 segundos". 😳

🧠 O modelo que pensa antes de falar

Lembra do "think": false que eu escondi lá em cima na chamada da API? Chegou a hora dele.

O Qwen3 é um modelo de raciocínio. Antes de responder, ele escreve para si mesmo um rascunho de pensamento, e só depois formula a resposta final. Esse rascunho gasta tokens, gasta tempo, e é invisível na resposta.

Os números que eu medi mostram o tamanho da diferença. Uma pergunta simples com o pensamento desligado:

{
    "response": "7 vezes 6 é 42.",
    "eval_count": 10,
    "total_duration": 1931496408
}

Dez tokens, 1,93 segundo. Agora a mesma ordem de pergunta pelo painel, com o pensamento ligado, que é o padrão: 24 segundos. E numa terceira medição, pela API do painel, uma resposta de uma frase consumiu 162 tokens de saída para produzir umas 25 palavras visíveis. Todo o resto foi rascunho interno.

Isso muda como você usa a ferramenta:

  • Para conversar e raciocinar, deixe pensar. A resposta sai melhor.
  • Para tarefa mecânica (classificar, extrair um campo, traduzir), mande "think": false na chamada da API. A resposta sai em ~2 segundos em vez de ~25.

Confesso que eu levei um susto com os 24 segundos antes de entender o que estava acontecendo. Achei que a VPS fosse fraca demais. Não era: o modelo estava fazendo exatamente o que foi treinado para fazer, e eu é que estava medindo a coisa errada. 😅

🎁 Baixando outros modelos

Uma hora você vai querer sair do qwen3:0.6b, e a boa notícia é que baixar outro é um comando só. A notícia que ninguém conta é quanto isso custa em velocidade, e é justamente o que eu vou medir aqui.

A vitrine fica em ollama.com/library. Cada modelo tem várias versões, separadas por dois-pontos:

ollama pull llama3.2:3b
                 |        |
                 |        +-- o tamanho (3 bilhoes de parametros)
                 +-- o nome da familia

Aquele número seguido de B é o que manda em tudo: quanto ele ocupa de disco, quanta memória come e quão rápido responde. B é de bilhões de parâmetros.

🪶 Os mais leves (que cabem nesta VPS)

Como aqui não tem placa de vídeo, essa é a lista que interessa de verdade. Todos rodam numa VPS parecida com a minha:

ComandoDiscoPara que serve
ollama pull qwen3:0.6b522 MBO mais rápido de todos. Tarefa mecânica, teste, aprendizado
ollama pull llama3.2:1b1,3 GBConversa simples, resumo curto
ollama pull gemma3:1b815 MBO leve do Google, bom em português
ollama pull llama3.2:3b2,0 GBO melhor equilíbrio para uma VPS de 8 GB
ollama pull qwen3:4b2,6 GBRaciocínio, quando você tem paciência para esperar

🌟 Os mais populares (e o que esperar deles)

Esses são os nomes que você vai ver citados em todo lugar. Repare que quase todos têm uma versão pequena, e é ela que a gente usa aqui:

FamíliaDe quem éA versão que cabe aqui
Llama 3.2Metallama3.2:1b e llama3.2:3b
Qwen3Alibabaqwen3:0.6b até qwen3:4b
Gemma 3Googlegemma3:1b e gemma3:4b
Phi-4 miniMicrosoftphi4-mini (2,5 GB)
MistralMistral AImistral (4,1 GB, já aperta)
DeepSeek-R1DeepSeekdeepseek-r1:1.5b (raciocínio)

📉 O preço da esperteza: medindo os dois lado a lado

Baixei o llama3.2:3b para comparar de verdade. O download:

ollama pull llama3.2:3b
pulling dde5aa3fc5ff: 100%  2.0 GB/2.0 GB   19 MB/s
verifying sha256 digest
writing manifest
success

real    1m45.965s

Um minuto e 46 segundos. Agora os dois na máquina:

ollama list
NAME           ID              SIZE      MODIFIED
llama3.2:3b    a80c4f17acd5    2.0 GB    10 seconds ago
qwen3:0.6b     7df6b6e09427    522 MB    2 hours ago

Fiz a mesma pergunta para os dois, pela API, e li os campos eval_count e eval_duration da resposta. Dividindo um pelo outro sai a velocidade real:

qwen3:0.6bllama3.2:3b
Disco522 MB2,0 GB
Memória enquanto carregado1,0 GB2,6 GB
Velocidade15,6 tokens/s4,9 tokens/s

Olhe essa última linha com carinho, porque ela é a lição da seção inteira: o modelo ficou 4 vezes maior e 3 vezes mais lento. 😰

Sem placa de vídeo, é a CPU que faz a conta, e ela paga o preço proporcional. Aqueles 4,9 tokens por segundo querem dizer que uma resposta de parágrafo leva uns 15 segundos para terminar de sair. Dá para usar, mas você sente.

Em compensação, o llama3.2:3b escreve muito melhor em português e inventa bem menos coisa. É essa a troca: rápido e bobo, ou lento e caprichado. Não existe o terceiro caso numa máquina sem GPU.

⏱️ A armadilha do "testei e é lentíssimo"

Aqui está a pegadinha que faz muita gente desistir de um modelo bom.

Quando medi a primeira pergunta ao llama3.2:3b, a resposta trouxe isto:

{
    "eval_count": 51,
    "eval_duration": 10767474000,
    "load_duration": 12643319567
}

Repare no load_duration: 12,6 segundos. Isso não é o modelo pensando, é o Ollama carregando o arquivo do disco para a memória. Perguntei a mesma coisa de novo, logo em seguida:

{
    "eval_count": 37,
    "eval_duration": 7498388000,
    "load_duration": 890230040
}

O carregamento caiu de 12,6 s para 0,89 s. 🎉

Ou seja: se você baixar um modelo, fizer uma pergunta, achar horrível e apagar, você mediu o carregamento, não o modelo. Sempre pergunte duas vezes antes de julgar. Foi o mesmo erro que eu cometi com os 24 segundos do pensamento, duas seções atrás. 😅

👀 O comando que mostra o que está ocupando a memória

Existe um comando que o tutorial ainda não mostrou e que resolve o mistério do "por que a RAM sumiu". Ele lista o que está carregado agora:

ollama ps

Depois de eu testar os dois modelos em sequência:

NAME           ID              SIZE     PROCESSOR   UNTIL
qwen3:0.6b     7df6b6e09427   1.0 GB   100% CPU    4 minutes from now
llama3.2:3b    a80c4f17acd5   2.6 GB   100% CPU    4 minutes from now

Três coisas importantes nessa telinha:

  • Os dois estão carregados ao mesmo tempo, somando 3,6 GB. O Ollama não descarrega um para carregar o outro, ele empilha. É exatamente assim que alguém baixa cinco modelos para comparar e o servidor trava. 💥
  • 100% CPU é a confirmação de que não há GPU nenhuma trabalhando aqui. Numa máquina com placa de vídeo apareceria 100% GPU.
  • UNTIL é o prazo de validade: cinco minutos sem uso e ele libera a memória sozinho. Por isso a pergunta seguinte a um café volta a demorar os 12 segundos do carregamento.

🗑️ Apagando o que não presta

Testou, não gostou, quer o disco de volta:

ollama rm llama3.2:3b

Confira quanto sobrou com o du, que mede o tamanho de uma pasta (o -sh quer dizer "só o total, em tamanho legível"):

du -sh /usr/share/ollama/.ollama/models

Com os dois modelos baixados aqui, deu 2,4 GB. Modelo é a parte barata desta instalação: lembre que a imagem Docker do Open WebUI sozinha ocupa 7,16 GB. 🤷‍♀️

🔑 Gerando a API Key (a que não expira)

Agora a parte que interessa a quem programa. O Open WebUI expõe uma API compatível com a da OpenAI, o que quer dizer que muita biblioteca que já existe funciona só trocando o endereço.

Para chamar de fora você precisa de uma credencial, e aqui existe uma escolha que quase todo tutorial erra. São duas coisas diferentes:

Token de loginAPI Key
Como se pegaFazendo login (e-mail e senha)Um botão no painel
Validade4 semanas, depois morreNão expira
Serve paraO navegador, enquanto você usaScript, automação, integração

Lembra da tela de autenticação lá em cima, com "Expiração do JWT: 4w"? Era disso que ela estava falando. Se você montar um robô com o token de login, ele funciona lindamente e para de funcionar sozinho em quatro semanas, provavelmente num domingo. 😬

Para script, use a API Key.

🖱️ Pegando a chave no painel

É o caminho mais fácil, e o que eu recomendo. Clique no seu nome, lá no canto inferior esquerdo do painel, e vá em:

Configuracoes  ->  Conta  ->  Chaves de API

Ali tem um botão para criar a chave. Ela aparece uma vez, no formato sk- seguido de um monte de letras e números, e tem um ícone de copiar do lado.

⌨️ Ou pela linha de comando, se preferir

Dá para fazer tudo pelo terminal. Primeiro o login, guardando o token numa variável (o $(...) executa o comando e joga a saída dentro dela):

printf '%s' '{"email":"[email protected]","password":"SUA_SENHA_AQUI"}' > login.json

TOKEN=$(curl -s -X POST http://SEU_IP_AQUI:3000/api/v1/auths/signin \
  -H 'Content-Type: application/json' \
  -d @login.json | jq -r .token)

Com o token na mão, peça a chave permanente:

curl -s -X POST http://SEU_IP_AQUI:3000/api/v1/auths/api_key \
  -H "Authorization: Bearer $TOKEN" | jq
{
    "api_key": "sk-eb91b6019a80..."
}

Essa é ela. 🔑 O POST gera uma nova (substituindo a antiga); para só consultar a que já existe, sem trocar nada, use GET na mesma URL, tirando o -X POST.

🧪 Provando que a chave funciona (e que a proteção também)

Antes de usar, vale conferir os dois lados. Sem chave nenhuma:

curl -s -o /dev/null -w "HTTP %{http_code}\n" http://SEU_IP_AQUI:3000/api/models
HTTP 401

Com uma chave inventada, para ver se ele realmente confere:

curl -s -o /dev/null -w "HTTP %{http_code}\n" http://SEU_IP_AQUI:3000/api/models \
  -H "Authorization: Bearer sk-chaveerrada"
HTTP 401

E agora com a chave de verdade:

curl -s http://SEU_IP_AQUI:3000/api/models \
  -H "Authorization: Bearer sk-SUA_CHAVE_AQUI" | jq -r '.data[].id'
llama3.2:3b
qwen3:0.6b
arena-model

Os dois modelos que eu baixei apareceram. 🎉 (O arena-model é um recurso do próprio Open WebUI para comparar modelos, não é nada que você tenha instalado.)

💬 A conversa, enfim

O formato é o mesmo da API da OpenAI, e quem já mexeu com ela reconhece na hora:

printf '%s' '{"model":"llama3.2:3b","messages":[{"role":"user","content":"Explique em uma frase curta o que e uma VPS."}],"stream":false}' > chat.json

curl -s -X POST http://SEU_IP_AQUI:3000/api/chat/completions \
  -H "Authorization: Bearer sk-SUA_CHAVE_AQUI" \
  -H 'Content-Type: application/json' \
  -d @chat.json | jq

A resposta, recortada nos campos que interessam:

{
    "choices": [
        {
            "message": {
                "role": "assistant",
                "content": "Uma VPS (Virtual Private Server) e um tipo de hospedagem de servidor virtual que fornece uma maquina virtual dedicada com recursos e espaco de armazenamento personalizados."
            }
        }
    ],
    "usage": {
        "prompt_tokens": 39,
        "completion_tokens": 61,
        "response_token/s": 5.76,
        "reasoning_tokens": 0,
        "approximate_total": "0h0m12s"
    }
}

Esse usage é generoso e vale ler com calma, porque ele responde sozinho duas perguntas do artigo:

  • response_token/s: 5.76 é a velocidade já calculada para você, sem precisar dividir nada. Bate com os ~4,9 que eu tinha medido na mão.
  • reasoning_tokens: 0 é a prova de que o llama3.2 não é um modelo de raciocínio: ele não gastou um token sequer pensando. Compare com o qwen3:0.6b, que torrou 162 tokens de rascunho para escrever 25 palavras. 🧠

📊 Quanto isso tudo consome, de verdade

Terminada a instalação, medi a máquina em repouso. O docker stats mostra o consumo do contêiner, e o --no-stream tira uma foto em vez de ficar atualizando:

docker stats --no-stream
NAME         MEM USAGE / LIMIT     CPU %
open-webui   662.9MiB / 7.755GiB   1.12%

E o Ollama, que não é contêiner e por isso não aparece ali:

ps -o rss=,comm= -C ollama
ollama  57 MB

O disco dos modelos:

du -sh /usr/share/ollama/.ollama/models
499M	/usr/share/ollama/.ollama/models

Juntando tudo, o retrato desta instalação:

ItemConsumo
Open WebUI (memória, em repouso)662,9 MB
Ollama (memória, em repouso)57 MB
Modelo qwen3:0.6b em disco499 MB
Imagem Docker do Open WebUI7,16 GB
Velocidade de geração~18 tokens/s

A surpresa aqui é o contraste: a memória é modesta, cabe folgado numa VPS de 2 GB. Quem assusta é o disco, e não por causa da IA: a imagem do Open WebUI sozinha ocupa quatorze vezes mais espaço que o modelo. Se você for escolher plano de VPS pensando nisso, olhe o disco antes da RAM.

E os 57 MB do Ollama são enganosos no bom sentido: é o consumo parado. Quando chega uma pergunta, ele carrega o modelo na memória e sobe bastante; depois de uns minutos sem uso, ele libera sozinho.

🛠️ Os comandos do dia a dia

Guarde estes, que são os que você vai usar sempre:

docker logs -f open-webui     # ver o que o painel esta fazendo, ao vivo
docker restart open-webui    # reiniciar o painel (leva ~45s para responder)
docker stats --no-stream     # quanto esta consumindo agora

systemctl status ollama      # o motor esta de pe?
systemctl restart ollama     # reiniciar o motor
ollama list                  # quais modelos estao baixados
ollama ps                    # quais estao carregados na memoria AGORA
ollama pull <modelo>         # baixar outro modelo
ollama rm <modelo>           # apagar um modelo que nao usa mais

Uma observação sobre o docker restart: depois dele, o painel leva uns 45 segundos para voltar a responder. Menos que os 105 da primeira subida (o banco já está pronto), mas ainda o suficiente para você achar que quebrou. Não quebrou: espere. ⏱️

Está tudo aí: uma IA que é sua, num servidor seu, atrás de uma senha sua. ✨

Por hoje é só, meus unicórnios! 🦄✨

Que a magia do arco-íris continue brilhando em suas vidas! Até mais! 🌈🌟

Perguntas frequentes

Preciso de placa de vídeo para rodar o Ollama?
Não para modelo pequeno. Esta instalação roda numa VPS comum de 4 vCPU e 7,8 GB de RAM, sem GPU nenhuma, e o qwen3:0.6b responde a cerca de 18 tokens por segundo. A conta muda com modelo grande: aí a CPU vira gargalo e a espera passa de minutos.
Qual a diferença entre o Ollama e o Open WebUI?
O Ollama é o motor: ele baixa o modelo, carrega na memória e responde por uma API na porta 11434, sem tela nenhuma. O Open WebUI é a tela de conversa, parecida com a do ChatGPT, que fala com essa API. Um funciona sem o outro, mas juntos você tem a experiência completa no navegador.
Por que o Open WebUI abre mas não mostra nenhum modelo?
Quase sempre é o firewall. Se o ufw está com política DROP na entrada, ele barra a ponte do Docker em silêncio e o contêiner não alcança o Ollama do host. O sintoma característico é timeout, não "conexão recusada", e a página abre normalmente porque o painel em si está de pé. A regra que resolve é ufw allow from 172.17.0.0/16 to 172.17.0.1 port 11434 proto tcp.
Quanto de memória e disco isso consome?
Menos do que parece. Medido com docker stats e ps na máquina em repouso: 662,9 MB para o contêiner do Open WebUI e 57 MB para o Ollama, que roda como serviço do sistema. O modelo qwen3:0.6b ocupa 499 MB de disco, mas a imagem Docker do Open WebUI sozinha tem 7,16 GB descompactada.
Qualquer pessoa que achar a URL consegue criar conta?
Não. O Open WebUI fecha o cadastro sozinho depois do primeiro usuário, que vira administrador. A partir daí, tentar criar conta pela tela responde 403, e o padrão para novos usuários fica em "pendente", exigindo aprovação do admin. São duas camadas, e vale conferir as duas no painel de Autenticação.
A porta do Ollama fica exposta na internet?
Não deve ficar, e é o ponto mais importante de segurança deste tutorial. A API do Ollama não tem senha nenhuma: quem alcança a porta 11434 usa o seu servidor de graça. Neste tutorial a 11434 é liberada apenas para a faixa 172.17.0.0/16 do Docker, então de fora ela nem responde. Público mesmo, só o Open WebUI, que tem login.
Como instalo outros modelos no Ollama?
Com ollama pull nome:tamanho, por exemplo ollama pull llama3.2:3b. A vitrine fica em ollama.com/library, e o número seguido de B é o de bilhões de parâmetros, que manda no disco, na memória e na velocidade. Para apagar, ollama rm.
Qual o melhor modelo para uma VPS sem placa de vídeo?
O llama3.2:3b é o melhor equilíbrio numa VPS de 8 GB: 2,0 GB de disco, escreve bem em português e alucina pouco. Se a prioridade for velocidade, o qwen3:0.6b é 3 vezes mais rápido. Outros leves que cabem: gemma3:1b, llama3.2:1b, phi4-mini e deepseek-r1:1.5b.
Quanto a velocidade cai ao usar um modelo maior?
Mais do que o tamanho sobe. Medido nesta VPS de 4 vCPU sem GPU, com a mesma pergunta: o qwen3:0.6b gera 15,6 tokens por segundo e o llama3.2:3b, quatro vezes maior, gera 4,9. Sem placa de vídeo é a CPU que faz a conta, e ela paga o preço proporcional.
Como gerar uma API Key no Open WebUI?
No painel, em Configurações → Conta → Chaves de API, ou pela linha de comando com POST /api/v1/auths/api_key. Ela sai no formato sk- e, ao contrário do token de login (que expira em 4 semanas), não vence nunca. É a credencial certa para script e automação.
Qual a diferença entre o token de login e a API Key?
O token do login vale 4 semanas e serve ao navegador enquanto você usa o painel; é o que a tela de autenticação chama de "Expiração do JWT: 4w". A API Key não expira e é a certa para automação. Cada usuário tem uma só: gerar de novo substitui a anterior e derruba quem usava a antiga.

Leia também