Ollama e Open WebUI: sua própria IA numa VPS
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. Aqueles1931496408sã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:
-droda em segundo plano, devolvendo o terminal para você.--name open-webuidá um nome à caixinha, para você não precisar decorar o código dela.--restart alwaysfaz 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:8080liga 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-gatewaycria, 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=Trueexige login. Nunca ponhaFalsenum servidor com IP público: seria deixar a IA aberta para o mundo inteiro.-v open-webui:/app/backend/dataguarda 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:
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:
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:
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:
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": falsena 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:
| Comando | Disco | Para que serve |
|---|---|---|
ollama pull qwen3:0.6b | 522 MB | O mais rápido de todos. Tarefa mecânica, teste, aprendizado |
ollama pull llama3.2:1b | 1,3 GB | Conversa simples, resumo curto |
ollama pull gemma3:1b | 815 MB | O leve do Google, bom em português |
ollama pull llama3.2:3b | 2,0 GB | O melhor equilíbrio para uma VPS de 8 GB |
ollama pull qwen3:4b | 2,6 GB | Raciocí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ília | De quem é | A versão que cabe aqui |
|---|---|---|
| Llama 3.2 | Meta | llama3.2:1b e llama3.2:3b |
| Qwen3 | Alibaba | qwen3:0.6b até qwen3:4b |
| Gemma 3 | gemma3:1b e gemma3:4b | |
| Phi-4 mini | Microsoft | phi4-mini (2,5 GB) |
| Mistral | Mistral AI | mistral (4,1 GB, já aperta) |
| DeepSeek-R1 | DeepSeek | deepseek-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.6b | llama3.2:3b | |
|---|---|---|
| Disco | 522 MB | 2,0 GB |
| Memória enquanto carregado | 1,0 GB | 2,6 GB |
| Velocidade | 15,6 tokens/s | 4,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 apareceria100% 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 login | API Key | |
|---|---|---|
| Como se pega | Fazendo login (e-mail e senha) | Um botão no painel |
| Validade | 4 semanas, depois morre | Não expira |
| Serve para | O navegador, enquanto você usa | Script, 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 ollama3.2não é um modelo de raciocínio: ele não gastou um token sequer pensando. Compare com oqwen3: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:
| Item | Consumo |
|---|---|
| Open WebUI (memória, em repouso) | 662,9 MB |
| Ollama (memória, em repouso) | 57 MB |
Modelo qwen3:0.6b em disco | 499 MB |
| Imagem Docker do Open WebUI | 7,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?
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?
Por que o Open WebUI abre mas não mostra nenhum modelo?
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?
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?
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?
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?
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?
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?
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?
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?
Leia também
Texto em áudio por CURL, com IA local na sua VPS
Transforme texto em áudio com IA local, por CURL. O Open WebUI não tem TTS embutido: instale o motor, baixe a voz brasileira e ligue os dois.
Áudio em texto por CURL, com IA local na sua VPS
Transcreva áudio com IA local, no seu servidor, por CURL. O áudio não sai da máquina, e as duas armadilhas que devolvem 400 e texto em latim.
Clonar voz por API, com IA local na sua VPS
Clone a sua voz com IA local e cadastre vozes por API: suba o Chatterbox, envie o .wav por HTTP e gere áudio em português no seu servidor.