SSD Nodes Learn 🎉 VPS desde $5.50/mês
Guias Matt ConnorPor Matt Connor · Atualizado 2026-08-21

Como hospedar o Moli headless para agentes

Instale o Moli em um VPS pequeno, exponha o CDP apenas em loopback e conecte um agente. Veja as limitações que fazem páginas falharem fora do Chrome.

Um navegador headless que cabe num VPS pequeno

Moli é um navegador headless para agentes de IA e é suficientemente pequeno para ser alojado num VPS onde o headless Chrome não cabe. É um motor de navegador escrito em Rust, não um wrapper em torno do Chromium, e responde ao Chrome DevTools Protocol (CDP), o protocolo que a sua biblioteca de automação já utiliza. Instale um único binário, execute moli serve e aponte o Playwright ou o código do seu próprio agente para http://127.0.0.1:9222.

Leia as limitações antes de instalar qualquer coisa. O projeto declara claramente o seu âmbito: não tem navegador com GUI, compositor GPU, paridade pixel a pixel com o Chrome nem suporte de alta fidelidade para Canvas ou reprodução de multimédia. As páginas que dependem desses recursos vão falhar. O Chrome real com Playwright continua a ser a alternativa, e a última secção mostra como decidir que páginas precisam dele.

Todos os comandos abaixo vêm do README do projeto e dos seus ficheiros de skill publicados, verificados em agosto de 2026. Todos os números dos gráficos são valores publicados pelo projeto sobre o seu próprio motor, não medições deste site, e cada legenda dos gráficos indica isso. Se ainda está a escolher um motor, o levantamento mais amplo sobre navegadores headless para agentes num VPS apresenta as alternativas.

Por que o Chrome headless usa tanta memória?

O Chrome é um navegador multiprocesso. Cada separador e cada iframe entre sites diferentes recebe o seu próprio processo renderer, e cada renderer mantém a sua própria heap V8 e os seus próprios buffers gráficos. Esse design é adequado para um desktop, onde um separador bloqueado não deve encerrar toda a janela. Num VPS com 2 GB, isso significa que uma única etapa de navegação pode consumir mais memória do que a aplicação que está efetivamente a executar.

O projeto percorreu 192 URLs públicas variadas com quatro engines e publicou o resultado.

ChartMixed public web crawl, 192 URLs, figures published by the Moli project
The data behind this chart
[
  {
    "engine": "Moli",
    "useful_pages": 103,
    "median_rss_mib": 73
  },
  {
    "engine": "Chrome Headless",
    "useful_pages": 101,
    "median_rss_mib": 773
  },
  {
    "engine": "Lightpanda",
    "useful_pages": 85,
    "median_rss_mib": 40
  },
  {
    "engine": "Obscura",
    "useful_pages": 57,
    "median_rss_mib": 39
  }
]

O Chrome Headless devolveu 101 páginas úteis e o Moli devolveu 103, portanto, nessa amostra, os dois engines leram aproximadamente a mesma parte da web. A memória é o ponto de separação: uma RSS mediana (resident set size, a memória que um processo mantém efetivamente na RAM) de 773 MiB para o Chrome, contra 73 MiB para o Moli. A tendência é credível porque resulta da arquitetura dos processos. Não deve assumir que a proporção exata será a mesma nas suas páginas.

A mediana não é o valor que causa problemas. O pico é. Quando uma máquina com 2 GB fica sem memória, o kernel escolhe um processo e encerra-o, e o registo aparece em dmesg -T ou journalctl -k:

Out of memory: Killed process 4211 (chrome) total-vm:2318936kB, anon-rss:1418324kB, file-rss:0kB, shmem-rss:0kB, UID:1000 pgtables:3540kB oom_score_adj:0

O seu agent nunca vê essa linha. Vê um browser que deixou de responder, normalmente como um erro do Playwright, como page.goto: Page crashed, ou como um target fechado. Esse erro não menciona a memória, por isso o OOM (out of memory) killer é a primeira coisa a verificar quando um agent falha aleatoriamente numa máquina pequena. Dimensionar o pico é o mesmo processo que escolher a RAM e a CPU para um VPS de agent.

Instale o binário do Moli, fixado numa versão

O projeto publica um instalador de shell e arquivos tar pré-compilados nas releases do GitHub. Em agosto de 2026, a versão atual é 1.0.1, publicada em 18 de agosto de 2026. Os valores de benchmark citados neste guia foram medidos pelo projeto na versão 0.1.1. Por isso, trate-os como uma indicação aproximada do comportamento do mecanismo, não como uma garantia sobre a compilação que instalar.

Fixe a versão. Um instalador que resolve sempre latest coloca o seu agente num mecanismo de navegador diferente na compilação seguinte. Uma alteração no comportamento do navegador deve ser programada, não descoberta por acaso.

O instalador de shell é a forma mais rápida de começar. Leia-o antes de o executar.

curl --proto '=https' --tlsv1.2 -fsSL \
  -o /tmp/moli-installer.sh \
  https://github.com/lexmount/moli/releases/download/v1.0.1/moli-installer.sh
less /tmp/moli-installer.sh
sh /tmp/moli-installer.sh

Leia o script antes de o executar. Ele é curto. Escolhe um arquivo a partir de uname -m e depois extrai um único binário para ~/.local/bin. Em x86_64, utiliza moli-x86_64-unknown-linux-gnu.tar.gz. Num servidor Arm, utiliza o arquivo aarch64. Assim, os planos VPS Arm e x86 são suportados. Defina MOLI_INSTALL_DIR para instalar noutra localização. Observe como ele resolve a versão: a release mais recente, não a tag da qual obteve o script. Isso é aceitável para uma primeira análise, mas está errado para uma recompilação que pretende repetir de forma previsível.

Para qualquer instalação permanente, faça manualmente o que o instalador faz e indique você mesmo o arquivo exato. Assim, também pode colocar o binário numa localização acessível por um serviço do sistema. Além disso, evita enviar um script transferido diretamente para um shell.

cd /tmp
curl --proto '=https' --tlsv1.2 -fsSLO \
  https://github.com/lexmount/moli/releases/download/v1.0.1/moli-x86_64-unknown-linux-gnu.tar.gz
mkdir -p moli-pkg
tar -xzf moli-x86_64-unknown-linux-gnu.tar.gz -C moli-pkg --strip-components=1
sudo install -m 0755 moli-pkg/moli /usr/local/bin/moli
moli --version

moli --version imprimir a versão que fixou é toda a verificação necessária. moli: command not found logo depois do instalador significa que o diretório de instalação não está no seu PATH. Nesse caso, o instalador imprime uma linha com o diretório que precisa de adicionar.

Extração única com moli fetch

Muitos dos trabalhos que um agente atribui a um navegador são “carregar este URL e dizer-me o que está escrito”. Isso não requer nenhum servidor. moli fetch inicia o motor, carrega uma página, escreve um artefacto na saída padrão e termina. Assim, não mantém memória entre chamadas.

moli fetch --dump markdown --wait-until networkidle https://example.com
moli fetch --dump semantic_tree_text --wait-selector "main" https://example.com
moli fetch --dump json --wait-until networkidle https://example.com > page.json

A primeira opção imprime a página como Markdown, começando por # Example Domain. Markdown é o formato mais económico para fornecer a um modelo, porque remove a marcação e mantém o texto. semantic_tree_text preserva as funções e a estrutura. É o que deve usar numa página com muita navegação, onde as ligações são tão importantes como o texto. --dump json inclui o estado HTTP e o rastreio do pedido. Use-o quando uma obtenção devolver conteúdo vazio e precisar de saber porquê.

A estratégia de espera determina se recebe conteúdo ou uma estrutura vazia. --wait-until networkidle devolve o resultado quando a rede deixa de ter atividade. --wait-until domstable devolve o resultado quando o DOM deixa de mudar. É a melhor opção numa página que faz polling em segundo plano e, por isso, nunca fica realmente sem atividade. --wait-selector espera por um seletor que indicar. É a única estratégia que conhece algo sobre a página que está a obter. Por isso, é a mais fiável quando conhece o destino.

As capturas de ecrã e os PDF precisam de um layout real. O layout está desativado por predefinição:

moli fetch --layout --dump screenshot https://example.com > page.png
moli fetch --layout --dump screenshot_full https://example.com > full-page.png
moli fetch --layout --dump pdf https://example.com > page.pdf

O README identifica a política de layout predefinida como LayoutPolicy::Mock: a geometria é simulada e nada é desenhado, porque o layout e o desenho são a parte mais dispendiosa de um navegador. Essa predefinição explica os valores de memória apresentados acima. Também significa que um PNG vazio normalmente resulta da ausência da flag --layout, e não de um problema na página.

Para URLs que o seu agente encontrou, em vez de URLs que escolheu, adicione --block-private-networks. Um agente que segue ligações lidas numa página pode ser induzido a obter http://169.254.169.254/ para obter credenciais de instâncias cloud ou uma porta de base de dados em localhost que nunca deveria estar exposta à Internet. Essa flag impede a navegação para endereços privados, e --block-cidrs restringe-a ainda mais. Quando o trabalho consiste em rastrear páginas, e não em ler uma única página, o formato desse pipeline é abordado em alternativas self-hosted ao Firecrawl. O passo anterior, que consiste em encontrar URLs, é abordado em uma skill de pesquisa para agentes baseada em SearXNG.

Apontar um agente para o Moli via CDP

Para um agente que navega e clica em muitas etapas, execute o servidor separadamente.

moli serve --host 127.0.0.1 --port 9222

127.0.0.1 e a porta 9222 são os valores predefinidos, por isso um moli serve simples já fica associado apenas ao loopback. Mesmo assim, especifique ambos em qualquer configuração permanente. Assim, a próxima pessoa que ler o ficheiro de serviço não precisa de se lembrar do valor predefinido.

Verifique o servidor antes de ligar um cliente a ele:

curl -s http://127.0.0.1:9222/json/version

Um servidor funcional responde com um objeto JSON que contém um campo webSocketDebuggerUrl. Esse URL é o ponto ao qual um cliente CDP se liga. curl: (7) Failed to connect to 127.0.0.1 port 9222: Connection refused significa que não há nada a escutar. Nesse caso, consulte o terminal onde iniciou o servidor ou use journalctl -u moli -n 50 quando ele estiver configurado como serviço. /json/list apresenta os destinos abertos e /json/protocol apresenta os domínios implementados nesta compilação. Assim, pode confirmar se existe aqui um método CDP de que dependa.

O Playwright liga-se a esse endpoint em vez de iniciar o seu próprio navegador:

import { chromium } from "playwright";

const browser = await chromium.connectOverCDP("http://127.0.0.1:9222");
const context = browser.contexts()[0];
const page = context.pages()[0] ?? await context.newPage();

await page.goto("https://example.com");
console.log(await page.locator("body").innerText());

await browser.close();

A linha relevante é connectOverCDP, não chromium.launch(). Não existe aqui um processo Chromium filho. Por isso, executablePath e as opções habituais de contentores, como --no-sandbox, não têm qualquer efeito. As definições de proxy, cookies e user-agent devem ser passadas ao servidor Moli como opções próprias, pelo mesmo motivo. Espere suporte apenas para um subconjunto do CDP, não para todo o protocolo do Chrome. Um erro explícito de método não suportado indica um limite do motor, não um erro no seu código.

Duas opções do servidor determinam o que o agente pode fazer. --layout ativa a geometria real, necessária para cliques por coordenadas e capturas de ecrã. --resource obtém as imagens, os tipos de letra e os conteúdos multimédia opcionais. Isto consome largura de banda e memória em cada carregamento de página, por isso mantenha a opção desativada até uma página demonstrar que precisa dela. --profile-dir mantém os cookies e o armazenamento entre execuções. Sem essa opção, cada execução é descartável.

ChartOne agent episode, Moli against Chromium, figures published by the Moli project
The data behind this chart
[
  {
    "engine": "Moli",
    "cdp_ready_ms": 34.85,
    "peak_pss_mib": 102.46,
    "processes": 1
  },
  {
    "engine": "Chromium",
    "cdp_ready_ms": 169.37,
    "peak_pss_mib": 348.82,
    "processes": 11
  }
]

Na carga de trabalho de agente de exemplo do projeto, o Moli aceitou uma ligação CDP após 34.85 ms, contra 169.37 ms no Chromium, com um PSS máximo (proportional set size, memória contabilizada com as páginas partilhadas divididas entre os processos que as partilham) de 102.46 MiB, contra 348.82 MiB. A diferença estrutural está na última coluna: 1 processo, contra 11. Um processo é uma única unidade para o systemd supervisionar e um único cgroup ao qual aplicar limites. É isso que torna curta a próxima secção.

Executar moli serve como um serviço systemd em loopback

Execute o servidor como um serviço quando um agente precisar de um browser à sua espera. Continue a utilizar moli fetch por URL quando não precisar disso, porque um servidor inativo continua a ocupar memória.

Não coloque a porta 9222 numa interface pública. O CDP não tem qualquer etapa de autenticação. Qualquer pessoa que consiga aceder a essa porta pode controlar o browser e ler tudo o que o browser conseguir alcançar, incluindo quaisquer cookies no diretório do seu perfil. Mantenha-o em 127.0.0.1. Aceda-lhe a partir de outra máquina através de um túnel SSH (ssh -L 9222:127.0.0.1:9222 user@your-vps) ou de uma interface VPN privada, e faça o agente ligar-se a http://127.0.0.1:9222 no seu próprio lado desse túnel.

Crie um utilizador de serviço e, em seguida, o ficheiro da unidade:

sudo useradd --system --home-dir /var/lib/moli --shell /usr/sbin/nologin moli

Escreva /etc/systemd/system/moli.service:

[Unit]
Description=Moli headless browser CDP server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=moli
Group=moli
ExecStart=/usr/local/bin/moli serve --host 127.0.0.1 --port 9222 --profile-dir /var/lib/moli/profile --block-private-networks
Restart=on-failure
RestartSec=2
StateDirectory=moli
MemoryAccounting=yes
MemoryMax=768M
NoNewPrivileges=yes
PrivateTmp=yes
ProtectHome=yes
ProtectSystem=strict

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now moli.service
systemctl status moli.service
curl -s http://127.0.0.1:9222/json/version

systemctl status deve mostrar active (running), e curl deve devolver o JSON de descoberta. ProtectSystem=strict monta todo o sistema de ficheiros como somente leitura para esta unidade. Por isso, StateDirectory=moli não é opcional neste caso: cria /var/lib/moli, pertencente ao utilizador de serviço, e torna esse único caminho gravável. Uma unidade que inicia e depois termina com um erro de permissões em journalctl -u moli está quase sempre a tentar escrever num local que ProtectSystem acabou de tornar somente leitura. Mova esse caminho para o diretório de estado.

MemoryMax=768M é o que torna seguro executar isto juntamente com a sua aplicação. A unidade obtém o seu próprio cgroup. Quando esse cgroup ultrapassa o limite, o kernel termina algo dentro dele e deixa o resto do sistema intacto. O journal regista o evento:

moli.service: A process of this unit has been killed by the OOM killer.

Interprete essa linha como um sinal para dimensionamento. As páginas podem ser mais pesadas do que previsto, ou o limite pode ser demasiado baixo. Defina o valor com base numa medição das suas próprias páginas. Essa é a próxima secção. Os mesmos flags de contabilização limitam qualquer outro serviço no sistema, e limitar a memória e a CPU com systemd funciona através dos restantes serviços.

Meça você mesmo o pico de memória

Os valores publicados vieram do hardware e das páginas de outra pessoa. O pico de memória determina se o seu servidor aguenta, e o pico depende totalmente do que você carrega. Faça a medição antes de dimensionar.

Para obter uma página uma única vez, use o binário time, que apresenta muito mais informações do que o builtin do shell com o mesmo nome:

sudo apt update && sudo apt install -y time
/usr/bin/time -v moli fetch --dump markdown --wait-until networkidle https://example.com > /dev/null

A saída termina com um bloco de estatísticas de recursos que inclui Maximum resident set size (kbytes). Divida esse valor por 1024 para obter MiB. Execute o comando em dez páginas que o seu agente realmente visita, e não em example.com. Registe o pior resultado em vez da média, porque o OOM killer reage aos picos.

Para o serviço, leia o contador que o kernel já mantém para o respetivo cgroup:

cat /sys/fs/cgroup/system.slice/moli.service/memory.peak
systemd-cgtop -m

memory.peak é uma contagem de bytes e representa o maior valor atingido desde que a unidade foi iniciada pela última vez, por isso um restart repõe o contador. Esse valor deve ficar abaixo de MemoryMax, com margem adicional para a página mais pesada que ainda não visitou. systemd-cgtop -m mostra o uso atual por unidade. É a forma mais rápida de identificar qual serviço do servidor está a consumir mais recursos hoje.

Onde o Moli falha e quando ainda precisa do Chrome?

O projeto também executa um benchmark com 1,308 tarefas comparáveis de automação de browser e publica a pontuação de vários engines.

ChartLexbench headless browser suite, 1,308 tasks, figures published by the Moli project
The data behind this chart
[
  {
    "engine": "Chrome",
    "success_rate_pct": 99.85
  },
  {
    "engine": "Moli 0.1.1",
    "success_rate_pct": 81.88
  },
  {
    "engine": "Kitesurf",
    "success_rate_pct": 62.08
  },
  {
    "engine": "Lightpanda",
    "success_rate_pct": 53.29
  },
  {
    "engine": "Obscura",
    "success_rate_pct": 44.88
  }
]

Entre esses 5 engines, o Moli 0.1.1 concluiu 81.88 por cento das tarefas, enquanto o Chrome, o engine de referência, concluiu 99.85 por cento. O próprio projeto calcula esta pontuação com base na sua própria suite. Por isso, interprete-a como uma afirmação do projeto, não como um resultado independente.

A interpretação prática é simples. Aproximadamente uma em cada cinco tarefas falhou no Moli, mas foi concluída pelo Chrome. Se o seu agente visita um conjunto fixo de páginas que controla, essa proporção diz muito pouco. As suas páginas funcionam ou não funcionam, e pode descobrir a causa ainda hoje. Se o seu agente navega pela web aberta, esta é uma taxa de falha real para a qual tem de preparar o sistema.

É previsível o que falha, com base no âmbito declarado do projeto.

  • Aplicações que desenham a interface num elemento Canvas em vez de no DOM, porque a fidelidade do Canvas está explicitamente fora do âmbito
  • Qualquer funcionalidade que precise de WebGL ou composição por GPU, porque não existe um compositor de GPU
  • Vídeo protegido por DRM e reprodução de conteúdos multimédia exigente
  • Testes visuais que validam screenshots com correspondência exata de pixels face ao Chrome, porque a paridade com o Chrome não é um objetivo

O outro número citado pelo projeto, uma execução completa que passa 1.612 milhões de testes da plataforma web, é uma afirmação sobre a cobertura de padrões. Não é uma garantia sobre os sites que o seu agente visitará. Uma página pode usar apenas padrões bem suportados e ainda assim falhar numa verificação de bot. Nenhuma pontuação de engine cobre esse caso.

Por isso, mantenha o fallback no desenho da solução. Envie primeiro todos os URLs para o Moli. Quando uma página voltar vazia ou um seletor nunca aparecer, repita apenas esse URL com o Playwright a controlar o Chrome real, numa máquina maior ou com um agendamento em que um processo de 773 MiB seja aceitável. A maioria dos agentes passa a maior parte do tempo em páginas comuns. Assim, o engine pequeno processa o volume e o engine mais dispendioso trata os casos extremos.

FAQ

O Moli pode substituir o Chrome headless para o meu agente?

Para ler páginas, extrair texto e fazer cliques comuns, normalmente sim. No benchmark do próprio projeto, com 1,308 tarefas, concluiu 81.88 por cento, contra 99.85 por cento do Chrome. Assim, cerca de uma em cada cinco tarefas precisa de algo que o Moli não faz. Aplicações renderizadas em Canvas, WebGL e vídeo DRM são as limitações conhecidas. Encaminhe esses URLs para o Chrome real em vez de voltar a usar o Chrome em tudo.

De quanta RAM o Moli precisa numa VPS?

O projeto indica um RSS mediano de 73 MiB num crawl de 192 URLs e um PSS máximo de 102.46 MiB num episódio de agente de amostra, contra uma mediana de 773 MiB do Chrome headless. Estes são os valores medidos pelo projeto nas próprias páginas. Meça os seus com /usr/bin/time -v em torno de uma chamada moli fetch para uma utilização pontual, ou leia /sys/fs/cgroup/system.slice/moli.service/memory.peak para o serviço. Depois, defina MemoryMax acima do pior valor observado.

É seguro expor a porta 9222 à Internet?

Não. O CDP não tem autenticação. Qualquer pessoa que consiga alcançar essa porta pode controlar o seu browser e ler tudo a que o browser consiga aceder. Mantenha --host 127.0.0.1 e aceda ao endpoint a partir de outra máquina através de um túnel SSH ou de uma interface VPN privada. Se tiver de associar o serviço a outro endereço, use uma interface privada e controle o acesso com a firewall.

Porque é que a minha captura de ecrã está vazia ou o meu clique não acerta em nada?

O layout está desativado por predefinição. O README identifica LayoutPolicy::Mock como a política predefinida. Por isso, a geometria dos elementos não é real e tudo o que dependa de uma caixa na página não tem dados para funcionar. Inicie o servidor com moli serve --layout ou adicione --layout a moli fetch. A captura de ecrã e os caminhos baseados em coordenadas começam então a funcionar. Imagens em falta dependem de outro sinalizador: --resource.

Que versão do Moli devo instalar?

Fixe uma versão e registe qual foi instalada. Em agosto de 2026, a versão atual é 1.0.1, enquanto os valores do benchmark publicados pelo projeto foram medidos na versão 0.1.1. Por isso, as duas versões não são diretamente comparáveis quando troca informações com outra pessoa. Transfira o moli-x86_64-unknown-linux-gnu.tar.gz dessa tag e instale o binário manualmente, em vez de depender do instalador da shell. O instalador resolve a versão mais recente, não a tag que transferiu. Depois, confirme com moli --version.