Pular para o conteúdo

Deploy — Docker & endpoint CDP

Rode o Clearcote como um endpoint CDP permanente e aponte para ele qualquer framework que você já usa — Playwright, Puppeteer, browser-use, Crawl4AI, Stagehand — sem mudar o código. Ele inicia o binário diretamente (sem --enable-automation), então navigator.webdriver continua false: discreto por construção.

Imagem Docker oficial

Baixe a imagem e pronto. Qualquer cliente CDP se conecta pela porta exposta.

bash
docker run -d --rm -p 127.0.0.1:9222:9222 -e CC_FINGERPRINT=user-7423 teamflatearth/clearcote   # CDP on http://localhost:9222

Isso roda o Chrome sem o sandbox dele, como todas as imagens anteriores à sdk-0.41.0 faziam. Acrescente o perfil seccomp da imagem e o Chrome roda com o sandbox; veja O sandbox do Chrome, mais abaixo.

python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp("http://localhost:9222")   # your code, unchanged
    page = browser.contexts[0].new_page()                            # the container's own profile
    page.goto("https://example.com")
    print(page.title())

A imagem já vem com o build aberto para Linux, com SHA-256 verificado, com as fontes metric-clone do Windows (para que texto em alfabeto latino tenha as mesmas medidas das fontes da persona) e, a partir da sdk-0.41.0, com fontes para os outros sistemas de escrita que uma máquina Windows desenha de fábrica (veja Fontes), e usa por padrão uma persona Linux nativa coerente. O navegador roda em modo headed num display virtual (CC_HEADLESS=1 para headless puro). Sem GPU no contêiner, o WebGL é renderizado por software, pelo backend cujos limites correspondem à GPU que a persona indica: Mesa para uma persona Linux, em modo headed ou headless, e SwiftShader para uma persona Windows. Configure tudo com variáveis de ambiente:

bash
docker run -d -p 127.0.0.1:9222:9222 -e CC_PLATFORM=linux -e CC_FINGERPRINT=user-7423 -e CC_ACCEPT_LANGUAGE=en-US -e CC_TIMEZONE=America/New_York teamflatearth/clearcote
VariávelSignificado
CC_FINGERPRINTSeed → identidade estável. Sem essa variável, cada contêiner recebe o seu próprio seed aleatório, exibido na inicialização e mantido no perfil do contêiner entre reinicializações (imagens anteriores a sdk-0.39.0 davam o mesmo seed a todos os contêineres). Defina a variável para escolher ou reutilizar uma identidade.
CC_PLATFORMlinux (padrão) | windows | macos | android. Uma persona Windows também ativa o Widevine e, no build licenciado (151 r15+), o dialeto de shader HLSL (use CC_WIDEVINE / CC_SHADER_DIALECT para substituir).
CC_BRAND, CC_BRAND_VERSIONChrome (padrão) | Edge | Opera | Vivaldi, e a versão que a marca declara.
CC_ACCEPT_LANGUAGE, CC_TIMEZONELista de idiomas e fuso horário IANA.
CC_TLS_PROFILEDeixe sem definir: o TLS segue a versão do Chrome que a persona declara. Fixe um chrome-<major> só junto com um CC_BRAND_VERSION correspondente.
CC_HARDWARE_CONCURRENCY, CC_GPU_VENDOR, CC_GPU_RENDERER, CC_STORAGE_QUOTAValores individuais da persona.
CC_HEADLESS, CC_SCREEN1 para headless puro; o tamanho da tela virtual (padrão 1920x1080x24).
CC_EXTRA_ARGSSwitches extras do navegador, separados por espaço. --disable-runtime-suppression aqui equivale ao stock_runtime do SDK (mantido só num motor que o tenha); --no-sandbox desliga o sandbox do Chrome.
CLEARCOTE_LICENSE_KEY, CC_VERSIONRodar o build licenciado — veja abaixo.
CC_IDLE_EXIT_SECONDSEncerra o contêiner quando nenhum cliente CDP fica conectado durante esse número de segundos, contados a partir da primeira resposta do navegador (padrão: nunca). Combine com --rm para que um contêiner abandonado também desapareça. Imagens a partir de sdk-0.40.0.
CC_SECRETS_FILEUm arquivo JSON dentro do contêiner com CLEARCOTE_LICENSE_KEY e/ou CC_PROXY, lido uma vez na inicialização e depois apagado — veja como manter segredos fora do docker inspect. Imagens a partir de sdk-0.40.0.
CLEARCOTE_FONT_DIRSAs suas próprias fontes, montadas no contêiner (por exemplo, uma cópia da pasta Fonts de uma máquina Windows) — veja Fontes. Imagens a partir de sdk-0.41.0.
CLEARCOTE_FALLBACK_FONT_DIRSFontes usadas só para caracteres que nada mais cobre. A imagem a define com as próprias fontes de escrita (/usr/local/share/clearcote/fonts); não mexa nela, a menos que as substitua.
CLEARCOTE_PERSONA_ENV0 mantém a persona na linha de comando do navegador. Por padrão, num motor que aceita isso (build licenciado a partir do r32), a persona chega ao navegador do contêiner pelo ambiente dele, então não aparece na lista de processos do host; a linha serve-state informa persona_env. Imagens a partir de sdk-0.41.0.
Declare o mesmo sistema operacional em que o contêiner roda. Parte do que uma página consegue ler vem do sistema operacional do host, abaixo do navegador, e nenhuma configuração de persona chega até lá. Nesta imagem Linux, o texto é dimensionado pelo scaler FreeType do Linux e as fontes vêm do fontconfig, não importa o que diga o CC_PLATFORM. Medido nesta imagem com CC_PLATFORM=windows: variar o tamanho da fonte em passos de 0,01 px muda a largura do texto em 66% dos passos (Chrome real no Windows: 99%), e Segoe UI e Georgia aparecem como instaladas nas medições, mas não podem ser carregadas pelo nome. O teste de fingerprint sinaliza as duas coisas. A persona Linux padrão passa. Fontes genuínas do Windows montadas com CLEARCOTE_FONT_DIRS corrigem os nomes no build licenciado a partir do r32 (Fontes), mas não o scaler, que nenhuma configuração alcança. Para uma identidade Windows, rode o build para Windows ou use os navegadores hospedados, que rodam em máquinas Windows. O mesmo vale para serve() e launch() num servidor Linux. Detalhes: a pilha de fontes por baixo do user agent.

Build licenciado no Docker — passe uma chave, monte o cache

A imagem já vem com o build aberto embutido. Defina CLEARCOTE_LICENSE_KEY e o contêiner passa a resolver o build licenciado — o mais recente ou, no plano Pro, um específico com CC_VERSION. Chaves gratuitas sempre rodam o build mais recente, e a fixação de versão é recusada.

bash
docker run -d -p 127.0.0.1:9222:9222 -v clearcote-cache:/opt/xdg-cache -e CLEARCOTE_LICENSE_KEY=cc_lic_... teamflatearth/clearcote

# Pro only: pin a major, an exact build, or a revision
#   -e CC_VERSION=154        -e CC_VERSION=154.0.8037.57        -e CC_VERSION=r36

Sempre monte o volume de cache. Um contêiner licenciado baixa o motor na primeira inicialização; sem um volume persistente, cada contêiner faz isso de novo. Com o volume, os contêineres seguintes iniciam a partir do build em cache.

O log de inicialização mostra qual motor você recebeu, então uma chave mal configurada aparece na hora, e não na primeira requisição bloqueada:

text
[clearcote] engine: /opt/xdg-cache/clearcote/pro-154.0.8037.57-r36/browser/chrome (licensed)
[clearcote] licence lease acquired
Exige uma imagem gerada a partir do SDK 0.26.1 ou mais recente, e 0.30.0 ou mais recente para uma chave gratuita via GitHub — o navegador licenciado espera que um contêiner gratuito mantenha a licença em dia enquanto roda, e recusa uma imagem mais antiga. Imagens publicadas mais antigas ignoram a chave e servem o build aberto sem avisar — rode docker pull teamflatearth/clearcote para atualizar. Uma execução licenciada também reserva um lease de concorrência na inicialização, então o contêiner precisa de acesso de saída à API de licenças; se o lease falhar, o contêiner encerra informando o motivo, em vez de iniciar um motor que não consegue rodar. Uma chave gratuita roda um navegador por vez, somando todos os seus contêineres — veja como os navegadores licenciados são contados.

A partir da imagem sdk-0.40.0, vários contêineres licenciados que iniciam ao mesmo tempo com o mesmo volume de cache baixam o motor uma única vez: o primeiro faz o download e os outros esperam por ele.

Mantenha segredos fora do docker inspect

Toda variável -e faz parte da configuração do contêiner, então qualquer pessoa que consiga rodar docker inspect nele (ou ler o docker compose config, ou a maioria dos painéis de contêineres) vê em texto puro a CLEARCOTE_LICENSE_KEY e a senha contida na URL do CC_PROXY. A partir da imagem sdk-0.40.0, você pode passá-las por um arquivo: um arquivo JSON dentro do contêiner, indicado por CC_SECRETS_FILE, que o contêiner lê uma vez na inicialização e apaga. O arquivo precisa pertencer ao usuário da imagem (uid 10001).

bash
printf '{"CLEARCOTE_LICENSE_KEY": "%s"}' "$CLEARCOTE_LICENSE_KEY" > clearcote-secrets.json
tar --owner=10001 --group=10001 --mode=600 -cf clearcote-secrets.tar clearcote-secrets.json
id=$(docker create -p 127.0.0.1:9222:9222 -e CC_SECRETS_FILE=/tmp/clearcote-secrets.json   -v clearcote-cache:/opt/xdg-cache teamflatearth/clearcote)
docker cp - "$id:/tmp" < clearcote-secrets.tar    # copy the file in before the container starts
docker start "$id"
rm clearcote-secrets.json clearcote-secrets.tar

É o que os SDKs fazem ao iniciar no macOS. Quem tem acesso ao daemon do Docker ainda consegue ler os arquivos e a memória de um contêiner em execução, então trate o acesso ao daemon como acesso à chave.

A partir da sdk-0.40.0, a imagem roda o tini como processo init, então os processos auxiliares do navegador que terminam dentro do contêiner (por exemplo, de scripts que você roda com docker exec e que iniciam os próprios navegadores) são recolhidos em vez de se acumularem. O docker stop continua chegando ao navegador e libera a vaga da licença.

Fontes

O navegador só enxerga as fontes para as quais o SDK o aponta: as substitutas do Windows que vêm no pacote da versão e, depois delas, os diretórios abaixo. Nada mais em /usr/share/fonts chega até ele, então uma fonte instalada na imagem nunca muda as larguras que uma persona Windows reporta. A partir da imagem sdk-0.41.0:

  • As fontes de escrita já vêm embutidas. Emoji colorido, chinês, japonês e coreano (WenQuanYi Zen Hei, IPA Gothic) e faces Noto para árabe, armênio, bengali, cherokee, devanágari, etíope, georgiano, guzerate, gurmukhi, canarês, khmer, laosiano, malaiala, birmanês, cingalês, tâmil, télugo, tailandês, tibetano e outros, em /usr/local/share/clearcote/fonts, que a CLEARCOTE_FALLBACK_FONT_DIRS lista depois do pacote. Elas só são usadas para caracteres que o pacote não consegue desenhar, para que esses caracteres sejam renderizados em vez de aparecerem como caixas vazias, tanto no build aberto quanto no licenciado. Nenhuma família latina é adicionada.
  • As suas próprias fontes. Monte um diretório de fontes, normalmente uma cópia da C:\Windows\Fonts de uma máquina Windows, e indique-o em CLEARCOTE_FONT_DIRS. Ele é listado antes do pacote. No build licenciado a partir do r32, cada família que ele fornece é renderizada como ela mesma, com o próprio nome, e Helvetica, Times, Courier e as famílias genéricas do CSS acompanham; motores mais antigos mantêm as substitutas e usam o diretório só para caracteres que nada mais cobre. Use só fontes que você tem licença para usar. A mesma configuração fora do Docker é o fontDirs do SDK.
bash
docker run -d -p 127.0.0.1:9222:9222 -v /srv/windows-fonts:/fonts:ro -e CLEARCOTE_FONT_DIRS=/fonts \
  -e CC_PLATFORM=windows -v clearcote-cache:/opt/xdg-cache -e CLEARCOTE_LICENSE_KEY=cc_lic_... teamflatearth/clearcote
docker exec <container> clearcote info --quick   # which writing systems render, which families are genuine
Segurança: um endpoint CDP dá controle total do navegador. Publique-o apenas em redes confiáveis — -p 127.0.0.1:9222:9222 o mantém restrito ao host. O docker/ Dockerfile é auditável — faça o build e verifique você mesmo.

O sandbox do Chrome

O sandbox do Chrome no Linux roda cada renderer (o processo que interpreta e executa uma página) em namespaces próprios, sob um filtro seccomp, então uma página que compromete o seu renderer continua confinada a ele. Com --no-sandbox, esse renderer tem tudo o que o usuário do contêiner tem: os arquivos, a rede e os outros processos dele. O perfil seccomp padrão do Docker recusa as chamadas que criam esses namespaces, então as imagens anteriores à sdk-0.41.0 sempre rodavam o Chrome com --no-sandbox.

A partir da imagem sdk-0.41.0, o contêiner verifica antes: faz as mesmas chamadas que o sandbox do Chrome faz e roda o Chrome com o sandbox quando elas funcionam. Inicie-o com o perfil seccomp que vem na imagem, que é o perfil padrão do Docker acrescido das duas chamadas de namespace de que o Chrome precisa:

bash
# copy the profile out of the image (--tmpfs keeps docker from first copying the engine into a volume)
docker run --rm --tmpfs /opt/xdg-cache --entrypoint cat teamflatearth/clearcote /etc/clearcote/seccomp.json > clearcote-seccomp.json
docker run -d -p 127.0.0.1:9222:9222 --security-opt seccomp=clearcote-seccomp.json teamflatearth/clearcote
yaml
# docker compose
services:
  clearcote:
    image: teamflatearth/clearcote
    ports: ["127.0.0.1:9222:9222"]
    security_opt: ["seccomp=./clearcote-seccomp.json"]
  • O log diz qual caminho foi tomado: [clearcote] sandbox: on (…) ou [clearcote] sandbox: OFF, Chrome runs with --no-sandbox: …, com o motivo e como ligá-lo. A linha serve-state traz "sandbox": true ou false. Sem o perfil, o contêiner roda exatamente como antes.
  • Também é preciso o próprio usuário da imagem (não --user 0), CAP_SYS_CHROOT (concedida por padrão; adicione --cap-add SYS_CHROOT de volta depois de --cap-drop ALL), um host que permita user namespaces sem privilégios e uma máquina amd64. Quando falta algum desses requisitos, o contêiner volta para --no-sandbox e diz por quê.
  • A contrapartida: com o perfil, qualquer processo no contêiner, não só o Chrome, pode criar um user namespace com o seu próprio network namespace, o que alcança código de rede do kernel que o perfil padrão do Docker deixa fora de alcance. Os renderers do Chrome, onde rodam as páginas não confiáveis, continuam sob o filtro do próprio Chrome. Sem o sandbox, um renderer comprometido tem tudo o que o usuário do contêiner tem, então o perfil continua sendo a melhor escolha. Não use --privileged, --cap-add SYS_ADMIN nem seccomp=unconfined no lugar dele: eles desligam uma parte muito maior da proteção do contêiner.
  • CC_EXTRA_ARGS=--no-sandbox o desliga de propósito. --no-zygote, --disable-namespace-sandbox, --renderer-cmd-prefix= e a canvas bridge (--canvas-bridge-url=) também o desligam, porque o Chrome não consegue rodar o sandbox com eles aqui.
  • A inicialização no macOS dos SDKs passa o perfil por conta própria, num Docker que roda numa CPU x86_64. No Apple silicon, a imagem roda emulada, e o sandbox fica desligado ali por enquanto.

Endpoint CDP permanente pelo SDK — serve()

Inicie o binário você mesmo e receba uma cdp_url à qual qualquer cliente se conecta. A mesma inicialização direta e discreta da imagem, controlada pelo seu próprio processo.

python
from clearcote import serve

srv = serve(fingerprint="seed-123", platform="windows")   # -> srv.cdp_url
# attach ANY CDP client:
#   playwright:  p.chromium.connect_over_cdp(srv.cdp_url)
#   puppeteer:   puppeteer.connect({ browserURL: srv.cdp_url, defaultViewport: null })
#   browser-use / Crawl4AI / Stagehand: point them at srv.cdp_url
srv.close()
javascript
import { serve } from "clearcote";
const srv = await serve({ fingerprint: "seed-123", platform: "windows" });
// srv.cdpUrl -> connectOverCDP / puppeteer.connect({ browserURL, defaultViewport: null })
await srv.close();

No modo headless, o navegador servido recebe um display de tamanho real — o da persona, ou um sorteado entre desktops reais — e a janela é ajustada à área útil da tela antes que qualquer cliente se conecte, então toda página, aba e popup reporta uma janela que cabe na tela (SDK 0.31+). Passe windowSize: { width, height } (window_size no Python, WindowSize no .NET) para uma janela menor, ou o seu próprio --window-size em args para assumir o controle.

Um endpoint, muitas identidades — clearcote serve

No shell (pacotes de Python e Node, 0.29+), o clearcote serve roda um endpoint permanente que inicia um navegador separado para cada identidade que uma conexão pede — com seed, proxy, fuso horário e idioma tirados da URL de conexão. O mesmo seed reaproveita o navegador que já está rodando.

bash
clearcote serve --port 9222 --idle-timeout 300 --max-browsers 16

# then, from any Playwright client:
#   chromium.connect_over_cdp("http://127.0.0.1:9222?fingerprint=acct-1&platform=windows")
#   chromium.connect_over_cdp("http://127.0.0.1:9222?fingerprint=acct-2&proxy=socks5://user:pass@host:1080&geoip=true")
#   (a password-protected proxy needs the licensed build)

Por padrão, ele escuta só em localhost e recusa requisições que uma página web poderia fazer. Identidades ociosas são fechadas depois de --idle-timeout segundos, --max-browsers limita quantas rodam ao mesmo tempo (as identidades além do limite recebem HTTP 429), --data-dir mantém o perfil de cada identidade entre reinicializações, e --allow-host / --allow-origin permitem rodá-lo atrás de um proxy reverso. Abra http://127.0.0.1:9222/ para ver o que está rodando. Com uma chave Grátis com GitHub, só um navegador roda por vez. O script antigo de identidade única, clearcote-serve --port 9222 --fingerprint seed-123, continua no pacote Python.

Direto — o build aberto, sem SDK

O build aberto da página de Releases é um binário comum do Chromium, que você pode iniciar por conta própria com executable_path e switches. O build licenciado precisa do SDK, que guarda o token de licença. Os argumentos extras abaixo são os principais padrões que o SDK adiciona para este seed no Windows (ele também mescla feature flags e, atrás de um proxy, desliga o QUIC):

python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(
        executable_path=r"C:\clearcote\chrome.exe",
        ignore_default_args=["--enable-automation", "--enable-unsafe-swiftshader"],
        args=[
            "--fingerprint=seed-123",
            "--fingerprint-platform=windows",
            "--fingerprint-brand=chrome",
            "--accept-lang=en-US,en",
            "--lang=en-US",
            "--timezone=America/New_York",
            "--webrtc-ip-handling-policy=disable_non_proxied_udp",
            "--ignore-gpu-blocklist",
        ],
    )
    browser.new_page().goto("https://example.com")

Crie a sua própria imagem (com o SDK)

O Clearcote distribui um binário Linux x64, então ele roda em modo headless num contêiner. A imagem precisa das bibliotecas de runtime do navegador, do fontconfig e do SDK. O navegador usa o pacote de fontes da própria versão, e não as fontes do sistema, então as fontes que você instala com apt continuam invisíveis para ele até você listá-las: crie links das fontes de escrita num único diretório e indique-o em CLEARCOTE_FALLBACK_FONT_DIRS (SDK 0.41.0+), como faz a imagem oficial, ou indique as suas próprias fontes em CLEARCOTE_FONT_DIRS (veja Fontes). Nunca liste todo o /usr/share/fonts: as famílias latinas dele mudariam as larguras de uma persona Windows. No Linux, a persona usa por padrão uma identidade Linux nativa coerente. A proteção contra vazamentos do WebRTC vem ativada por padrão; as APIs do Privacy Sandbox continuam ativas, como no Google Chrome. Com libegl1 e libgl1-mesa-dri instalados, o SDK renderiza o WebGL de uma persona Linux pelo Mesa sobre EGL, com os limites e a forma de renderer que máquinas Linux reais reportam: em modo headless a partir do SDK 0.39.0 e também em modo headed a partir do 0.42.0, então os dois modos mostram uma só superfície WebGL. Sem eles, o WebGL passa a usar o SwiftShader. Uma inicialização em modo headed sob Xvfb, sem gerenciador de janelas, tem a janela ajustada à tela antes da primeira página (SDK 0.42.0).

dockerfile
FROM node:22-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
      xz-utils libnss3 libnspr4 libgbm1 libasound2 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 \
      libxkbcommon0 libxcomposite1 libxdamage1 libxrandr2 libxfixes3 libxext6 libxrender1 \
      libpango-1.0-0 libcairo2 libx11-6 libxcb1 libexpat1 libdbus-1-3 ca-certificates libegl1 libgl1-mesa-dri \
      fontconfig fonts-noto-color-emoji fonts-ipafont-gothic fonts-wqy-zenhei \
 && F=/usr/local/share/clearcote/fonts && mkdir -p $F && cd /usr/share/fonts \
 && cp -l truetype/noto/NotoColorEmoji.ttf truetype/wqy/wqy-zenhei.ttc opentype/ipafont-gothic/ipag.ttf $F/ \
 && rm -rf /var/lib/apt/lists/*
# emoji + CJK, used only where the bundle has no glyph
ENV CLEARCOTE_FALLBACK_FONT_DIRS=/usr/local/share/clearcote/fonts
WORKDIR /app
RUN npm i clearcote
RUN node --input-type=module -e "import { download } from 'clearcote'; await download();"   # bake the binary in
COPY run.mjs .
CMD ["node", "run.mjs"]
run.mjs
import { launchPersistentContext } from "clearcote";
const ctx = await launchPersistentContext("/tmp/prof", {
  headless: true,
  fingerprint: "user-1",
  proxy: { server: "http://gateway:8080", username: "u", password: "p" },
  geoip: true,      // timezone + languages + WebRTC IP matched to the proxy exit
  humanize: true,   // trusted bezier input; navigator.webdriver stays false
  args: ["--no-sandbox"],
});
const page = ctx.pages()[0] ?? (await ctx.newPage());
await page.goto("https://example.com");
await ctx.close();
Rode o contêiner com --shm-size=1g para evitar crashes de /dev/shm em páginas pesadas. Em Python é idêntico (from clearcote import launch_persistent_context, opções em snake_case).

O que se conecta via CDP

ClienteComo
Playwrightchromium.connect_over_cdp(url) / connectOverCDP(url)
Puppeteerpuppeteer.connect({ browserURL: url, defaultViewport: null })
browser-use · Crawl4AI · Stagehandaponte a configuração de CDP/endpoint deles para a URL
Qualquer coisa que fale CDPo DevTools Protocol puro, na porta

Prefere que um agente de IA controle o navegador? Veja o servidor MCP. Quer saber por que uma inicialização direta é mais discreta? Veja Como a detecção funciona.