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.
docker run -d --rm -p 127.0.0.1:9222:9222 -e CC_FINGERPRINT=user-7423 teamflatearth/clearcote # CDP on http://localhost:9222Isso 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.
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:
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ável | Significado |
|---|---|
CC_FINGERPRINT | Seed → 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_PLATFORM | linux (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_VERSION | Chrome (padrão) | Edge | Opera | Vivaldi, e a versão que a marca declara. |
CC_ACCEPT_LANGUAGE, CC_TIMEZONE | Lista de idiomas e fuso horário IANA. |
CC_TLS_PROFILE | Deixe 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_QUOTA | Valores individuais da persona. |
CC_HEADLESS, CC_SCREEN | 1 para headless puro; o tamanho da tela virtual (padrão 1920x1080x24). |
CC_EXTRA_ARGS | Switches 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_VERSION | Rodar o build licenciado — veja abaixo. |
CC_IDLE_EXIT_SECONDS | Encerra 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_FILE | Um 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_DIRS | As 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_DIRS | Fontes 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_ENV | 0 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 oCC_PLATFORM. Medido nesta imagem comCC_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 comCLEARCOTE_FONT_DIRScorrigem 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 paraserve()elaunch()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.
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=r36Sempre 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:
[clearcote] engine: /opt/xdg-cache/clearcote/pro-154.0.8037.57-r36/browser/chrome (licensed)
[clearcote] licence lease acquiredExige 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).
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 aCLEARCOTE_FALLBACK_FONT_DIRSlista 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\Fontsde uma máquina Windows, e indique-o emCLEARCOTE_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.
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 genuineSeguranç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:
# 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# 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 linhaserve-statetraz"sandbox": trueoufalse. 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_CHROOTde 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-sandboxe 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_ADMINnemseccomp=unconfinedno lugar dele: eles desligam uma parte muito maior da proteção do contêiner. CC_EXTRA_ARGS=--no-sandboxo 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.
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()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.
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):
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).
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"]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=1gpara evitar crashes de/dev/shmem páginas pesadas. Em Python é idêntico (from clearcote import launch_persistent_context, opções emsnake_case).
O que se conecta via CDP
| Cliente | Como |
|---|---|
| Playwright | chromium.connect_over_cdp(url) / connectOverCDP(url) |
| Puppeteer | puppeteer.connect({ browserURL: url, defaultViewport: null }) |
| browser-use · Crawl4AI · Stagehand | aponte a configuração de CDP/endpoint deles para a URL |
| Qualquer coisa que fale CDP | o 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.