Saltar al contenido

Despliegue: Docker & endpoint CDP

Ejecuta Clearcote como un endpoint CDP permanente y apunta hacia él cualquier framework que ya uses —Playwright, Puppeteer, browser-use, Crawl4AI, Stagehand— sin cambiar el código. Lanza el binario directamente (sin --enable-automation), así que navigator.webdriver sigue en false: sigiloso por construcción.

Imagen oficial de Docker

Descarga la imagen y listo. Cualquier cliente CDP se conecta a través del puerto expuesto.

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

Así se ejecuta Chrome sin su sandbox, como hacían todas las imágenes anteriores a sdk-0.41.0. Agrega el perfil seccomp de la imagen y Chrome se ejecuta con su sandbox; consulta el sandbox de Chrome más abajo.

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())

La imagen trae integrados el build abierto para Linux, verificado con SHA-256, las fuentes con métricas clonadas de las de Windows (para que el texto latino mida como las fuentes de la persona) y, desde sdk-0.41.0, fuentes para los demás sistemas de escritura que una máquina Windows dibuja de serie (consulta Fuentes), y usa por defecto una persona coherente de Linux nativo. El navegador se ejecuta en modo headed sobre una pantalla virtual (CC_HEADLESS=1 para headless puro). Sin GPU en el contenedor, WebGL se renderiza por software a través del backend cuyos límites corresponden a la GPU que nombra la persona: Mesa para una persona de Linux, en modo headed o headless, y SwiftShader para una persona de Windows. Se configura por completo con variables de entorno:

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
VariableSignificado
CC_FINGERPRINTSeed → identidad estable. Sin ella, cada contenedor recibe su propio seed aleatorio, que se imprime al arrancar y se conserva en el perfil del contenedor entre reinicios (las imágenes anteriores a sdk-0.39.0 daban a todos los contenedores el mismo seed). Defínela para elegir o reutilizar una identidad.
CC_PLATFORMlinux (por defecto) | windows | macos | android. Una persona de Windows también activa Widevine y, en el build con licencia (151 r15+), el dialecto de shaders HLSL (CC_WIDEVINE / CC_SHADER_DIALECT para cambiarlo).
CC_BRAND, CC_BRAND_VERSIONChrome (por defecto) | Edge | Opera | Vivaldi, y la versión que declara.
CC_ACCEPT_LANGUAGE, CC_TIMEZONELista de idiomas y zona horaria IANA.
CC_TLS_PROFILEDéjala sin definir: el TLS sigue a la versión de Chrome que declara la persona. Fija un chrome-<major> solo junto con un CC_BRAND_VERSION que coincida.
CC_HARDWARE_CONCURRENCY, CC_GPU_VENDOR, CC_GPU_RENDERER, CC_STORAGE_QUOTAValores individuales de la persona.
CC_HEADLESS, CC_SCREEN1 para headless puro; el tamaño de la pantalla virtual (por defecto 1920x1080x24).
CC_EXTRA_ARGSSwitches adicionales del navegador, separados por espacios. --disable-runtime-suppression aquí equivale a stock_runtime del SDK (se conserva solo en un motor que lo tenga); --no-sandbox desactiva el sandbox de Chrome.
CLEARCOTE_LICENSE_KEY, CC_VERSIONEjecutan el build con licencia; ver más abajo.
CC_IDLE_EXIT_SECONDSDetiene el contenedor cuando lleva esta cantidad de segundos sin ningún cliente CDP conectado, contados desde que el navegador responde por primera vez (por defecto: nunca). Combínala con --rm para que un contenedor abandonado también desaparezca. Disponible desde la imagen sdk-0.40.0.
CC_SECRETS_FILEUn archivo JSON dentro del contenedor con CLEARCOTE_LICENSE_KEY y/o CC_PROXY, que se lee una vez al arrancar y luego se borra; consulta cómo mantener los secretos fuera de docker inspect. Disponible desde la imagen sdk-0.40.0.
CLEARCOTE_FONT_DIRSTus propias fuentes, montadas en el contenedor (por ejemplo, una copia de la carpeta Fonts de una máquina Windows); consulta Fuentes. Disponible desde la imagen sdk-0.41.0.
CLEARCOTE_FALLBACK_FONT_DIRSFuentes que se usan solo para los caracteres que ninguna otra fuente cubre. La imagen la define con sus propias fuentes de escrituras (/usr/local/share/clearcote/fonts); déjala así salvo que las reemplaces.
CLEARCOTE_PERSONA_ENV0 mantiene la persona en la línea de comandos del navegador. Por defecto, en un motor que lo admite (build con licencia desde r32), la persona llega al navegador del contenedor a través de su entorno, así que no aparece en la lista de procesos del host; la línea serve-state reporta persona_env. Disponible desde la imagen sdk-0.41.0.
Declara el sistema operativo en el que corre el contenedor. Parte de lo que una página puede leer viene del sistema operativo del host, por debajo del navegador, y ningún ajuste de la persona llega hasta ahí. En esta imagen de Linux, el tamaño del texto lo calcula el escalador FreeType de Linux y las fuentes vienen de fontconfig, diga lo que diga CC_PLATFORM. Medido en esta imagen con CC_PLATFORM=windows: variar el tamaño de fuente en pasos de 0.01 px cambia el ancho del texto en el 66% de los pasos (Chrome real en Windows: 99%), y Segoe UI y Georgia aparecen como instaladas al medirlas, pero no se pueden cargar por nombre. El test de huella digital señala ambas cosas. La persona de Linux por defecto pasa el test. Las fuentes genuinas de Windows montadas con CLEARCOTE_FONT_DIRS corrigen los nombres en el build con licencia desde r32 (Fuentes), pero no el escalador, al que no llega ningún ajuste. Para una identidad de Windows, ejecuta el build para Windows o usa los navegadores alojados, que corren en máquinas Windows. Lo mismo aplica a serve() y launch() en un servidor Linux. Detalles: el stack de fuentes por debajo del user agent.

Build con licencia en Docker: pasa una clave y monta la caché

La imagen trae integrado el build abierto. Define CLEARCOTE_LICENSE_KEY y el contenedor resuelve en su lugar el build con licencia: el más reciente o, en el plan Pro, uno concreto con CC_VERSION. Las claves gratuitas siempre ejecutan el build más reciente y se rechaza cualquier fijación de versión.

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

Monta siempre el volumen de caché. Un contenedor con licencia descarga el motor en el primer arranque; sin un volumen persistente, cada contenedor lo vuelve a hacer. Con el volumen, los contenedores siguientes arrancan desde el build en caché.

El log de arranque te dice qué motor obtuviste, así que una clave mal configurada se nota de inmediato y no en la primera solicitud bloqueada:

text
[clearcote] engine: /opt/xdg-cache/clearcote/pro-154.0.8037.57-r36/browser/chrome (licensed)
[clearcote] licence lease acquired
Requiere una imagen construida con el SDK 0.26.1 o posterior, y 0.30.0 o posterior para una clave gratuita de GitHub: el navegador con licencia espera que un contenedor gratuito mantenga su licencia al día mientras se ejecuta, y rechaza una imagen más antigua. Las imágenes publicadas más antiguas ignoran la clave y sirven el build abierto sin avisar; ejecuta docker pull teamflatearth/clearcote para actualizarla. Una ejecución con licencia también obtiene un lease de concurrencia al arrancar, así que el contenedor necesita acceso saliente a la API de licencias; si el lease falla, el contenedor termina indicando el motivo en lugar de iniciar un motor que no puede ejecutarse. Una clave gratuita ejecuta un navegador a la vez entre todos tus contenedores; consulta cómo se cuentan los navegadores con licencia.

Desde la imagen sdk-0.40.0, si varios contenedores con licencia arrancan a la vez con el mismo volumen de caché, el motor se descarga una sola vez: el primero lo descarga y los demás esperan a que termine.

Mantén los secretos fuera de docker inspect

Cada variable -e forma parte de la configuración del contenedor, así que cualquiera que pueda ejecutar docker inspect sobre él (o leer docker compose config, o la mayoría de los paneles de contenedores) ve en texto plano CLEARCOTE_LICENSE_KEY y la contraseña de una URL de CC_PROXY. Desde la imagen sdk-0.40.0 puedes pasarlos en un archivo: un archivo JSON dentro del contenedor, indicado por CC_SECRETS_FILE, que el contenedor lee una vez al arrancar y luego borra. El archivo debe pertenecer al usuario de la imagen (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

Es lo que hace el lanzamiento en macOS de los SDKs. Cualquiera con acceso al daemon de Docker puede seguir leyendo los archivos y la memoria de un contenedor en ejecución, así que trata el acceso al daemon como acceso a la clave.

Desde sdk-0.40.0, la imagen ejecuta tini como proceso init, así que los procesos auxiliares del navegador que terminan dentro del contenedor (por ejemplo, los de scripts que lanzas con docker exec y que inician sus propios navegadores) se limpian en lugar de acumularse. docker stop sigue llegando al navegador y libera el slot de la licencia.

Fuentes

El navegador solo ve las fuentes a las que lo dirige el SDK: las fuentes parecidas a las de Windows del paquete de la versión y, después, los directorios indicados abajo. Nada más de /usr/share/fonts le llega, así que una fuente instalada en la imagen nunca puede cambiar los anchos que reporta una persona de Windows. Desde la imagen sdk-0.41.0:

  • Las fuentes de escrituras vienen integradas. Emoji a color, chino, japonés y coreano (WenQuanYi Zen Hei, IPA Gothic) y tipografías Noto para árabe, armenio, bengalí, cheroqui, devanagari, etíope, georgiano, guyaratí, gurmují, canarés, jemer, lao, malayalam, birmano, cingalés, tamil, telugu, tailandés, tibetano y más, en /usr/local/share/clearcote/fonts, que CLEARCOTE_FALLBACK_FONT_DIRS lista después del paquete. Solo se usan para los caracteres que el paquete no puede dibujar, así que esos caracteres se renderizan en lugar de mostrarse como cuadros vacíos, tanto en el build abierto como en el build con licencia. No se agrega ninguna familia latina.
  • Tus propias fuentes. Monta un directorio de fuentes, normalmente una copia de C:\Windows\Fonts de una máquina Windows, e indícalo en CLEARCOTE_FONT_DIRS. Se lista antes del paquete. En el build con licencia desde r32, cada familia que aporta se renderiza como ella misma, con su propio nombre, y Helvetica, Times, Courier y las genéricas de CSS la siguen; los motores más antiguos conservan sus fuentes parecidas y usan el directorio solo para los caracteres que ninguna otra fuente cubre. Usa solo fuentes para las que tengas licencia. Fuera de Docker, el mismo ajuste es fontDirs del 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
Seguridad: un endpoint CDP da control total del navegador. Publícalo solo en redes de confianza; -p 127.0.0.1:9222:9222 lo mantiene local al host. El docker/ Dockerfile se puede auditar: recompílalo y verifícalo tú mismo.

El sandbox de Chrome

El sandbox de Chrome en Linux ejecuta cada renderer (el proceso que analiza y ejecuta una página) en namespaces propios, bajo un filtro seccomp, así que una página que compromete su renderer sigue confinada en él. Con --no-sandbox, ese renderer tiene todo lo que tiene el usuario del contenedor: sus archivos, su red y sus demás procesos. El perfil seccomp por defecto de Docker rechaza las llamadas que crean esos namespaces, así que las imágenes anteriores a sdk-0.41.0 siempre ejecutaban Chrome con --no-sandbox.

Desde la imagen sdk-0.41.0, el contenedor lo comprueba primero: hace las mismas llamadas que hace el sandbox de Chrome y, si funcionan, ejecuta Chrome con su sandbox. Inícialo con el perfil seccomp que trae la imagen, que es el perfil por defecto de Docker más las dos llamadas de namespace que necesita Chrome:

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"]
  • El log indica qué camino tomó: [clearcote] sandbox: on (…), o [clearcote] sandbox: OFF, Chrome runs with --no-sandbox: … con el motivo y cómo activarlo. La línea serve-state incluye "sandbox": true o false. Sin el perfil, el contenedor funciona exactamente igual que antes.
  • También necesita el propio usuario de la imagen (no --user 0), CAP_SYS_CHROOT (concedida por defecto; vuelve a agregar --cap-add SYS_CHROOT después de --cap-drop ALL), un host que permita user namespaces sin privilegios y una máquina amd64. Si falta alguno, el contenedor recurre a --no-sandbox e indica el motivo.
  • La contrapartida: con el perfil, cualquier proceso del contenedor, no solo Chrome, puede crear un user namespace con su propio network namespace, lo que da acceso a código de red del kernel que el perfil por defecto de Docker deja fuera de alcance. Los renderers de Chrome, donde se ejecutan las páginas no confiables, siguen bajo el filtro propio de Chrome. Sin el sandbox, un renderer comprometido tiene todo lo que tiene el usuario del contenedor, así que el perfil sigue siendo la mejor opción. No uses --privileged, --cap-add SYS_ADMIN ni seccomp=unconfined en su lugar: desactivan mucha más protección del contenedor.
  • CC_EXTRA_ARGS=--no-sandbox lo desactiva a propósito. --no-zygote, --disable-namespace-sandbox, --renderer-cmd-prefix= y el canvas bridge (--canvas-bridge-url=) también lo desactivan, porque con ellos Chrome no puede ejecutar aquí su sandbox.
  • El lanzamiento en macOS de los SDKs pasa el perfil por sí mismo, en un Docker que se ejecuta en una CPU x86_64. En Apple silicon la imagen se ejecuta emulada y, por ahora, el sandbox queda desactivado ahí.

Endpoint CDP permanente desde el SDK: serve()

Lanza el binario tú mismo y obtén una cdp_url a la que se conecta cualquier cliente. Es el mismo lanzamiento directo y sigiloso que el de la imagen, controlado desde tu propio proceso.

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();

En modo headless, el navegador servido recibe una pantalla de tamaño real —la de la persona, o una tomada de equipos de escritorio reales— y su ventana se ajusta al área de trabajo antes de que se conecte ningún cliente, así que cada página, pestaña y popup reporta una ventana que cabe en su pantalla (SDK 0.31+). Pasa windowSize: { width, height } (window_size en Python, WindowSize en .NET) si quieres una ventana más pequeña, o tu propio --window-size en args para tomar el control.

Un endpoint, muchas identidades: clearcote serve

Desde la shell (paquetes de Python y Node, 0.29+), clearcote serve ejecuta un endpoint permanente que inicia un navegador distinto para cada identidad que pide una conexión, con su seed, proxy, zona horaria e idioma tomados de la URL de conexión. Un mismo seed reutiliza el navegador que ya está en ejecución.

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 defecto escucha en localhost y rechaza las solicitudes que podría hacer una página web. Las identidades inactivas se cierran tras --idle-timeout segundos, --max-browsers limita cuántas se ejecutan a la vez (las identidades adicionales reciben HTTP 429), --data-dir conserva el perfil de cada identidad entre reinicios, y --allow-host / --allow-origin permiten ejecutarlo detrás de un reverse proxy. Abre http://127.0.0.1:9222/ para ver qué está en ejecución. Con una clave Gratis con GitHub, solo se ejecuta un navegador a la vez. El script anterior de identidad única, clearcote-serve --port 9222 --fingerprint seed-123, sigue incluido en el paquete de Python.

Directo: el build abierto, sin SDK

El build abierto de la página de Releases es un binario de Chromium normal que puedes lanzar tú mismo con executable_path y switches. El build con licencia necesita el SDK, que es el que tiene su token de licencia. Los argumentos adicionales de abajo son los principales valores por defecto que el SDK agrega para este seed en Windows (además, combina feature flags y, detrás de un proxy, desactiva 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")

Construye tu propia imagen (con el SDK)

Clearcote distribuye un binario para Linux x64, así que se ejecuta en modo headless dentro de un contenedor. La imagen necesita las bibliotecas de runtime del navegador, fontconfig y el SDK. El navegador usa el paquete de fuentes de su propia versión en lugar de las fuentes del sistema, así que las fuentes que instalas con apt le resultan invisibles hasta que las indiques: enlaza las fuentes de escrituras en un directorio e indícalo en CLEARCOTE_FALLBACK_FONT_DIRS (SDK 0.41.0+), como hace la imagen oficial, o tus propias fuentes en CLEARCOTE_FONT_DIRS (consulta Fuentes). Nunca indiques todo /usr/share/fonts: sus familias latinas cambiarían los anchos de una persona de Windows. En Linux, la persona usa por defecto una identidad coherente de Linux nativo. La protección contra fugas de WebRTC está activada por defecto; las APIs de Privacy Sandbox siguen activadas, como en Google Chrome. Con libegl1 y libgl1-mesa-dri instalados, el SDK renderiza el WebGL de una persona de Linux a través de Mesa sobre EGL, con los límites y la forma del renderer que reportan las máquinas Linux reales: en headless desde el SDK 0.39.0, y también en headed desde 0.42.0, así que ambos modos muestran una sola superficie WebGL. Sin ellos, recurre a SwiftShader. Un lanzamiento headed bajo Xvfb, sin gestor de ventanas, recibe una ventana ajustada a la pantalla antes de la primera 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();
Ejecuta el contenedor con --shm-size=1g para evitar fallos de /dev/shm en páginas pesadas. En Python es idéntico (from clearcote import launch_persistent_context, opciones en snake_case).

Qué se conecta por CDP

ClienteCómo
Playwrightchromium.connect_over_cdp(url) / connectOverCDP(url)
Puppeteerpuppeteer.connect({ browserURL: url, defaultViewport: null })
browser-use · Crawl4AI · Stagehandapunta su ajuste de CDP/endpoint a la URL
Cualquier cosa que hable CDPel DevTools Protocol en bruto, en ese puerto

¿Prefieres que lo maneje un agente de IA? Consulta el servidor MCP. ¿Te preguntas por qué un lanzamiento directo es más sigiloso? Consulta Cómo funciona la detección.