Skip to content

Origin & Host Validation Block Usage in Docker Compose #3975

Open

Description

@larkwiot

Hello,

When running Lightpanda in Docker Compose, no containers can access the CDP endpoint. I believe this is due to Host validation, since no other container will ever be "localhost" and you cannot disable this check.

Compose file:

services:
  browser:
    container_name: browser
    image: lightpanda/browser:1.0.0
    command: lightpanda serve --host 0.0.0.0 --port 9222 --advertise-host browser
    restart: unless-stopped

Logs:

browser  | $time=1792018280751 $scope=serve $level=warn $msg="rejected websocket host" host=browser:9222
browser  | $time=1792018282061 $scope=serve $level=warn $msg="rejected websocket host" host=browser:9222
browser  | $time=1792018283376 $scope=serve $level=warn $msg="rejected websocket host" host=browser:9222

URL used by other container: ws://browser:9222

Requested change: A CLI arg/flag that disables this check.

Activity

  1. cdebled commented on Oct 15, 2026

    @cdebled
    Collaborator

    This is fixed by lightpanda-io/browser#3803, which shipped after 1.0.0: the --advertise-host name is now also accepted as a valid Host. With your compose file unchanged, ws://browser:9222 will work.

    It will be in the next release. Until then you can use the lightpanda/browser:nightly image.

  2. karlseguin commented on Oct 15, 2026

    @karlseguin
    Collaborator

    1.0.1 was released

  3. larkwiot commented on Oct 17, 2026

    @larkwiot
    Author

    I tried the 1.0.1 and nightly tags and still get the same behavior. Sharing the port to the host and curling on localhost gets 503.

  4. karlseguin commented on Oct 19, 2026

    @karlseguin
    Collaborator

    The 503 seems like a different issue than the host validation. The only time we return 503 is when --cdp-max-connections is reached.
    with 1.0.1 are you still seeing rejected websocket host. Are you able to share the log output, maybe with --log-level info.

    Also, how are you connecting (Playwright, Puppeteer, chromedp, ...). Is there reconnect logic? Are you able to share a reproducible playwright/puppeteer/chromedp/... script?

    If you do a fresh docker compose up, what happens when you try to connect via a simple script? or maybe curl to /json/version?

  5. larkwiot commented on Oct 19, 2026

    @larkwiot
    Author

    There is retry logic, it retries every second. I'm using the Java Playwright library and trying to use the remote CDP. I fiddled with it more and I can curl it from the host OS and fetch that /json/version you mentioned. This is what the connection code looks like (Scala):

    // reconnect logic stripped
    val browser = Playwright
          .create(Playwright.CreateOptions()
            .setEnv(Map("PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD" -> "1").asJava))
          .chromium()
          .connectOverCDP(browserUrl)

    And here are the logs:

    crawler  | failed to connect to playwright browser ws://browser:9222, retrying
    crawler  | com.microsoft.playwright.PlaywrightException: Error {
    crawler  |   message='WebSocket error: ws://browser:9222/ 503 
    crawler  | Too many connections
    crawler  |   name='Error
    crawler  |   stack='Error: WebSocket error: ws://browser:9222/ 503 
    crawler  | Too many connections
    crawler  |     at IncomingMessage.<anonymous> (/tmp/playwright-java-850376489303657827/package/lib/coreBundle.js:39581:22)
    crawler  |     at IncomingMessage.emit (node:events:514:28)
    crawler  |     at emitCloseNT (node:internal/streams/destroy:148:10)
    crawler  |     at process.processTicksAndRejections (node:internal/process/task_queues:89:21)
    crawler  | }
    crawler  | Call log:
    crawler  | -   - <ws connecting> ws://browser:9222/
    crawler  | -   - <ws unexpected response> ws://browser:9222/ 503 
    crawler  | Too many connections
    crawler  | -   - <ws error> ws://browser:9222/ error WebSocket was closed before the connection was established
    crawler  | -   - <ws connect error> ws://browser:9222/ WebSocket was closed before the connection was established
    crawler  | -   - <ws disconnected> ws://browser:9222/ code=1006 reason=
    crawler  | 
    crawler  | failed to connect to playwright browser ws://browser:9222, retrying
    
    ~/clones/crawler/infra main*
    ❯ docker compose logs browser
    browser  | $time=1792451404751 $scope=telemetry $level=info $msg="telemetry status" disabled=true
    browser  | $time=1792451404751 $scope=app $level=debug $msg="startup" mode=serve snapshot=true
    browser  | $time=1792451404751 $scope=note $level=note $msg="server running" address=0.0.0.0:9222 max_connections=1872 max_browser_connections=16
    browser  | $time=1792451420253 $scope=cdp $level=info $msg="disconnect" err=null
    browser  | $time=1792451420261 $scope=cdp $level=info $msg="disconnect" err=null
    browser  | $time=1792451420339 $scope=cdp $level=info $msg="disconnect" err=null
    browser  | $time=1792451420372 $scope=cdp $level=info $msg="disconnect" err=null
    
  6. karlseguin commented on Oct 20, 2026

    @karlseguin
    Collaborator

    Thanks, this helps. The rejected websocket host warnings are gone, so the Host part does appear fixed by #3803. The 503 means all 16 (default) CDP slots are held by open WebSocket connections. There are only 4 disconnects in the log.

    1. Is there a crawler log for the first failed attempt, before the 503 start?
    2. On a failed attempt, does your retry call playwright.close() (or browser.close()) before creating a new Playwright? If not, every abandoned attempt keeps its Node driver and its WebSocket alive, which quickly fills up the slots.
  7. larkwiot commented on Oct 24, 2026

    @larkwiot
    Author

    Thanks, this helps. The rejected websocket host warnings are gone, so the Host part does appear fixed by #3803. The 503 means all 16 (default) CDP slots are held by open WebSocket connections. There are only 4 disconnects in the log.

    1. Is there a crawler log for the first failed attempt, before the 503 start?
    
    2. On a failed attempt, does your retry call playwright.close() (or browser.close()) before creating a new Playwright? If not, every abandoned attempt keeps its Node driver and its WebSocket alive, which quickly fills up the slots.
    

    I cannot close the browser because it throws an exception during instantiation, so the object never exists for a second line of code to close with.

    Here is a log of both the crawler and browser, where the crawler is limited to only one retry:

    ~/clones/crawler/infra main*
    ❯ docker compose logs crawler
    crawler  | com.microsoft.playwright.PlaywrightException: Error {
    crawler  |   message='WebSocket error: ws://browser:9222/ 503 
    crawler  | Too many connections
    crawler  |   name='Error
    crawler  |   stack='Error: WebSocket error: ws://browser:9222/ 503 
    crawler  | Too many connections
    crawler  |     at IncomingMessage.<anonymous> (/tmp/playwright-java-17035539771850931840/package/lib/coreBundle.js:39581:22)
    crawler  |     at IncomingMessage.emit (node:events:514:28)
    crawler  |     at emitCloseNT (node:internal/streams/destroy:148:10)
    crawler  |     at process.processTicksAndRejections (node:internal/process/task_queues:89:21)
    crawler  | }
    crawler  | Call log:
    crawler  | -   - <ws connecting> ws://browser:9222/
    crawler  | -   - <ws unexpected response> ws://browser:9222/ 503 
    crawler  | Too many connections
    crawler  | -   - <ws error> ws://browser:9222/ error WebSocket was closed before the connection was established
    crawler  | -   - <ws connect error> ws://browser:9222/ WebSocket was closed before the connection was established
    crawler  | -   - <ws disconnected> ws://browser:9222/ code=1006 reason=
    crawler  | 
    crawler  | failed to connect to playwright browser ws://browser:9222, retrying
    crawler  | com.microsoft.playwright.PlaywrightException: Error {
    crawler  |   message='WebSocket error: ws://browser:9222/ 503 
    crawler  | Too many connections
    crawler  |   name='Error
    crawler  |   stack='Error: WebSocket error: ws://browser:9222/ 503 
    crawler  | Too many connections
    crawler  |     at IncomingMessage.<anonymous> (/tmp/playwright-java-17035539771850931840/package/lib/coreBundle.js:39581:22)
    crawler  |     at IncomingMessage.emit (node:events:514:28)
    crawler  |     at emitCloseNT (node:internal/streams/destroy:148:10)
    crawler  |     at process.processTicksAndRejections (node:internal/process/task_queues:89:21)
    crawler  | }
    crawler  | Call log:
    crawler  | -   - <ws connecting> ws://browser:9222/
    crawler  | -   - <ws unexpected response> ws://browser:9222/ 503 
    crawler  | Too many connections
    crawler  | -   - <ws error> ws://browser:9222/ error WebSocket was closed before the connection was established
    crawler  | -   - <ws connect error> ws://browser:9222/ WebSocket was closed before the connection was established
    crawler  | -   - <ws disconnected> ws://browser:9222/ code=1006 reason=
    crawler  | 
    crawler  | failed to connect to playwright browser ws://browser:9222, retrying
    crawler  | Exception in thread "main" java.util.NoSuchElementException: None.get
    crawler  |      at scala.None$.get(Option.scala:628)
    crawler  |      at scala.None$.get(Option.scala:628)
    crawler  |      at Main$package$.Main(Main.scala:51)
    crawler  |      at Main.main(Main.scala:37)
    
    ~/clones/crawler/infra main*
    ❯ docker compose logs browser
    browser  | $time=1792883499912 $scope=telemetry $level=info $msg="telemetry status" disabled=true
    browser  | $time=1792883499912 $scope=app $level=debug $msg="startup" mode=serve snapshot=true
    browser  | $time=1792883499912 $scope=note $level=note $msg="server running" address=0.0.0.0:9222 max_connections=1872 max_browser_connections=16
  8. karlseguin commented on Oct 26, 2026

    @karlseguin
    Collaborator

    Thanks. I'm not able to reproduce the issue, and I'm not sure what's going on. I used your exact serve command and Playwright's connectOverCDP("ws://browser:9222") from another container on the same network connects on the first try.

    A 503 "Too many connections" only happens when all 16 CDP slots hold open WebSocket connections. Opening a connection doesn't log anything at info level (only disconnects do), so this wouldn't show up in the browser log. Some client seems to be holding 16 connections before the crawler's attempt arrives. Could you:

    1. Share the full compose file, including any healthchecks and other services that can reach browser:9222.
    2. When the 503s start, run curl http://:9222/metrics and paste the output. serve_active_connections shows how many CDP sessions are open.

    Does the crawler connects once or once per thread/task (e.g., several connectOverCDP calls running at the same time).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions