Skip to content

petrixh/claude-container

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

48 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

This container is based on: https://code.claude.com/docs/en/devcontainer and https://github.com/anthropics/claude-code/tree/main/.devcontainer but somewhat heavily modified.

TL;DR - just get me claude in a contaner, I'll think of security later!

Make a folder with a workspace folder inside it:

mkdir -p claude-container-instance/workspace claude-container-instance/dot-claude claude-container-instance/m2-cache 

go into the folder and create a .env file

cd claude-container-instance
nano .env

in the env file copy the following (.env.example):

# Claude Container Environment Variables
# Copy this file to .env and customize as needed
# Usage: docker run --env-file .env ...

# Claude configuration directory (inside container)
CLAUDE_CONFIG_DIR=/home/node/.claude

# GitHub Personal Access Token for gh CLI
# GH_TOKEN=ghp_your_token_here

# Git identity (used for commits inside the container)
# GIT_USER_NAME=Your Name
# GIT_USER_EMAIL=your.email@example.com

# Timezone (defaults to Europe/Helsinki if not set)
# TZ=America/New_York

# Skip firewall initialization (set to 1 to disable)
SKIP_FIREWALL=1

# Playwright browsers path (pre-installed in image)
PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers

# Skip Playwright browser downloads (browsers are pre-installed)
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1

# Notification URL (e.g., ntfy.sh) - called when Claude is idle or needs permission
# Supports any URL that accepts POST requests (ntfy.sh, webhooks, etc.)
# NOTIFICATION_URL=https://ntfy.sh/your-topic-here

# Vaadin Pro/Commercial key (for Charts, Board, Acceleration Kits, etc.)
# Alternatively, mount a ~/.vaadin/proKey file into the container
# VAADIN_PRO_KEY=your-pro-key-here

# Node.js memory limit
NODE_OPTIONS=--max-old-space-size=4096

For a first test the above config disables the firewall, enables chrome CDP debuggin on port 9222 (if enabled inside the container etc).

  • If you want to commit inside the container, you probably want to set the GIT_USER related params
  • If you want to use Vaadin commercial components, either set your proKey (just the value starting with pro- in the environment variable, or later inside the contaier mkdir -p ~/.vaadin && nano ~/node/.vaadin/proKey and copy your entire prokey from a different system (or follow the browser process to sign in...)
  • GH_TOKEN is there if you want to use Github fine graned PATs for limited access to GH from within the container
  • etc..

then run the prebuilt container with (caches the .m2 folder on the host for less downloads on second try):

docker run -it --rm \
  --cap-add=NET_ADMIN \
  --cap-add=NET_RAW \
  --env-file .env \
  -v "./dot-claude:/home/node/.claude" \
  -v "./m2-cache:/home/node/.m2" \
  -v "./workspace:/workspace" \
  -p 9222:9222 \
  ghcr.io/petrixh/claude-container:latest

Clone your projects under /workspace, run claude do stuff :)

Read what the entrypoint.sh command tells you about versions if wou want to use built-in predownloaded browers, playwright MCP in headless mode etc. Or don't... you can re-run the entrypoint inside the container terminal by just typing entrypoint.sh to get to the info later also (should be fine to rerun). To update claude run sudo claude update as new versions are being pushed constantly...

TL;DR I just want a devcontainer from my IDE/Codespaces optinally with docker-in-docker fully understanding the risks it might bring

Start by checking out your project in a directory, then inside that directory, minimally you only need the .devcontainer directory, but the others reduce future downloads, preserves things etc.

mkdir .devcontainer dot-claude m2-cache 

then create a deccontainer file under .devcontainer/devcontainer.json with the content:

{
  "name": "Claude Code Sandbox",
  "image": "ghcr.io/petrixh/claude-container:latest",
  "runArgs": [
    "--cap-add=NET_ADMIN",
    "--cap-add=NET_RAW",
    "--env-file=.devcontainer/.env", //if desired to setup vars in a file instead, comment out containerEnv-entries if used
  ],
// If you want to bring env variables from your current environment to the devconatiner
  // "containerEnv": {
  //   "SKIP_FIREWALL": "1",
  //   "GH_TOKEN": "${localEnv:GH_TOKEN}",
  //   "GIT_USER_NAME": "${localEnv:GIT_USER_NAME}",
  //   "GIT_USER_EMAIL": "${localEnv:GIT_USER_EMAIL}",
  //   "NODE_OPTIONS": "--max-old-space-size=4096",
  //   "CLAUDE_CONFIG_DIR": "/home/node/.claude",
  //   "PLAYWRIGHT_BROWSERS_PATH": "/opt/playwright-browsers",
  //   "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD": "1",
  //   //"VAADIN_PRO_KEY": "${localEnv:VAADIN_PRO_KEY}",
  //   "NOTIFICATION_URL": "${localEnv:NOTIFICATION_URL}",
  // },
  "customizations": {
    "vscode": {
      "extensions": [
        "vscjava.vscode-java-pack",
        "vscjava.vscode-maven"
      ],
      "settings": {
        "terminal.integrated.defaultProfile.linux": "zsh",
        "editor.formatOnSave": true,
        "java.configuration.detectJdksAtStart": true
      }
    }
  },
// If you need docker inside the dev container, does open up options for the AI to do all kinds of things... 
//  "features": {
//    "ghcr.io/devcontainers/features/docker-in-docker:2": {
//      "version": "latest",
//      "enableNonRootDocker": "true",
//      "moby": "false"
//    }
//  },
  "remoteUser": "node",
  "mounts": [
    "source=./m2-cache,target=/home/vscode/.m2,type=bind,consistency=cached",
    //     "source=claude-code-bashhistory-${devcontainerId},target=/commandhistory,type=volume",
    "source=./dot-claude,target=/home/node/.claude,type=bind,consistency=cached"
  ],
  "forwardPorts": [
    9222
  ],
  "portsAttributes": {
    "9222": {
      "label": "Playwright CDP",
      "onAutoForward": "silent"
    }
  },
  "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated",
  "workspaceFolder": "/workspace",
  "postStartCommand": "/usr/local/bin/entrypoint.sh"
}

create an .env file under .devcontainer and add it to .gitignore immediatlely! Or create it somewhere else and map it at the top in the runArgs section. Inside the .env file look at .env.example or just copy paste:

# Claude Container Environment Variables
# Copy this file to .env and customize as needed
# Usage: docker run --env-file .env ...

# Claude configuration directory (inside container)
CLAUDE_CONFIG_DIR=/home/node/.claude

# GitHub Personal Access Token for gh CLI
# GH_TOKEN=ghp_your_token_here

# Git identity (used for commits inside the container)
# GIT_USER_NAME=Your Name
# GIT_USER_EMAIL=your.email@example.com

# Timezone (defaults to Europe/Helsinki if not set)
# TZ=America/New_York

# Skip firewall initialization (set to 1 to disable)
SKIP_FIREWALL=1

# Playwright browsers path (pre-installed in image)
PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers

# Skip Playwright browser downloads (browsers are pre-installed)
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1

# Notification URL (e.g., ntfy.sh) - called when Claude is idle or needs permission
# Supports any URL that accepts POST requests (ntfy.sh, webhooks, etc.)
# NOTIFICATION_URL=https://ntfy.sh/your-topic-here

# Vaadin Pro/Commercial key (for Charts, Board, Acceleration Kits, etc.)
# Alternatively, mount a ~/.vaadin/proKey file into the container
# VAADIN_PRO_KEY=your-pro-key-here

# Node.js memory limit
NODE_OPTIONS=--max-old-space-size=4096

If you had the folder open in VS Code, it should have already prompted you that a devcotnainer config was found. If not open the command palette and with > at the front look for "Dev Containers: Rebuild and Reopend in Container". It will download the internet and reopen the folder inside the devcotnaienr in VS Code.

Read what the entrypoint.sh command tells you about versions if wou want to use built-in predownloaded browers, playwright MCP in headless mode etc. Or don't... you can re-run the entrypoint inside the container terminal by just typing entrypoint.sh to get to the info later also (should be fine to rerun). To update claude run sudo claude update as new versions are being pushed constantly...

Separate devcontainer and workspaces

mkdir workspace

Then clone your project(s) under workspace

After starting the devconatiner in VS Code use the command palette (top center) and with > at the beginning look for "File: Open Folder" and then navigate to /workspace/workspace/your-project. Now your devcontainer config and project are separate, but all still runs inside the devcontainer. To get back to your devcontainer config do the same open but select /workspace instead.

To keep your devcontainer and workspace permananently separate change the workspaceMount entry, use: -v "${localWorkspaceFolder}/workspace/:/workspace" for the bind mount instead.

The not TL;DR version

Below is a more compehensive example with instructions etc... Reading through them can give you insight into the whats and the whys...

Claude Container

A Docker devcontainer for running Claude Code in a sandboxed environment with:

  • Node.js 20
  • Java 21 (Eclipse Temurin)
  • Playwright with headless Chromium (arm64/amd64)
  • GitHub CLI with PAT authentication
  • Domain-whitelist firewall (default deny)
  • Configurable Claude config directory for subscription authentication
  • Optional Docker-in-Docker (DinD) support for containerized development

Prerequisites

Before running the container, ensure:

  1. Claude config directory exists on your Docker host for persisting authentication:
    mkdir -p /path/to/claude-container-config
  2. GitHub PAT (optional) - set the GH_TOKEN environment variable:
    export GH_TOKEN=ghp_your_token_here

Container Variants

This repository provides three container variants:

Variant Size Docker Best For Limitations
claude (base) ⭐ 3.47GB ❌ No General Claude Code development No Docker support
claude-docker-host 3.92GB ✅ Via host Docker development, testing Requires host Docker
claude-dind 3.92GB ✅ Isolated Secure isolation, CI/CD Firewall blocks Docker Hub

Quick Decision Guide

Choose claude (base) if: ⭐ RECOMMENDED

  • General development (Java, Node.js, etc.)
  • You don't need Docker inside the container
  • You want the smallest, fastest container

Choose claude-docker-host if:

  • You need Docker and have Docker on your host
  • You want to pull from Docker Hub
  • You want lower resource usage than DinD

Choose claude-dind if:

  • You need complete isolation from host Docker
  • You're testing untrusted code
  • You're willing to work around firewall limitations

Quick Start

Most Common Use Cases

Standard Development (recommended):

docker compose up -d claude
docker compose exec claude zsh
claude --version

Docker Development (host socket):

docker compose up -d claude-docker-host
docker compose exec claude-docker-host zsh
docker pull alpine  # Works without firewall issues!

Isolated Docker Environment:

docker compose up -d claude-dind
docker compose exec claude-dind zsh
# Note: Docker Hub pulls blocked by firewall - see Known Limitations

Build the Images

# Base variant (default)
docker build -t claude-container:base --target base .devcontainer/

# DinD variant (both claude-dind and claude-docker-host use this)
docker build -t claude-container:dind --target dind .devcontainer/

Interactive Shell Options

Option 1: Docker Compose (Recommended)

Start an interactive development environment:

# Start container in background and attach
docker compose up -d && docker compose exec claude zsh

# Or start and attach directly (container removed on exit)
docker compose run --rm claude

# Stop the container
docker compose down

# DinD variant with separate Docker daemon
docker compose up -d claude-dind && docker compose exec claude-dind zsh

# DinD variant mounting host Docker socket
docker compose up -d claude-docker-host && docker compose exec claude-docker-host zsh

Option 2: Interactive Shell (Docker Run)

docker run -it --rm \
  --cap-add=NET_ADMIN \
  --cap-add=NET_RAW \
  -v "/path/to/claude-container-config:/home/node/.claude" \
  -e CLAUDE_CONFIG_DIR="/home/node/.claude" \
  -w /workspace \
  claude-container:base

Note: The CLAUDE_CONFIG_DIR environment variable tells Claude where to store authentication credentials. Mount a host directory to persist login across container restarts.

Option 2b: Docker Run with Environment File

For easier management of environment variables, use a .env file:

# Copy the example env file and edit with your settings
cp .env.example .env
vim .env

# Minimal: just env file
docker run -it --rm \
  --cap-add=NET_ADMIN \
  --cap-add=NET_RAW \
  --env-file .env \
  claude-container:base

Add Maven cache to avoid re-downloading dependencies:

mkdir -p ~/.m2-cache

docker run -it --rm \
  --cap-add=NET_ADMIN \
  --cap-add=NET_RAW \
  --env-file .env \
  -v "${HOME}/.m2-cache:/home/node/.m2" \
  claude-container:base

Add workspace to work on a project:

docker run -it --rm \
  --cap-add=NET_ADMIN \
  --cap-add=NET_RAW \
  --env-file .env \
  -v "${HOME}/.m2-cache:/home/node/.m2" \
  -v "$(pwd):/workspace" \
  claude-container:base

Full setup with all common mounts:

docker run -it --rm \
  --cap-add=NET_ADMIN \
  --cap-add=NET_RAW \
  --env-file .env \
  -v "${HOME}/.claude:/home/node/.claude" \
  -v "${HOME}/.m2-cache:/home/node/.m2" \
  -v "${HOME}/.npm-cache:/home/node/.npm" \
  -v "${HOME}/.config/gh:/home/node/.config/gh" \
  -v "$(pwd):/workspace" \
  -p 9222:9222 \
  claude-container:base

Volume mounts explained:

Host Path Container Path Purpose
~/.claude /home/node/.claude Claude authentication and config
~/.m2-cache /home/node/.m2 Maven local repository cache
~/.npm-cache /home/node/.npm NPM cache
~/.config/gh /home/node/.config/gh GitHub CLI authentication
$(pwd) /workspace Your project directory

Option 3: Run a One-Off Claude Prompt

Execute a single prompt and exit:

docker run -it --rm \
  --cap-add=NET_ADMIN \
  --cap-add=NET_RAW \
  -v "/path/to/claude-container-config:/claude-container-config" \
  -e CLAUDE_CONFIG_DIR="/claude-container-config" \
  -w /workspace \
  claude-container \
  claude -p "Your prompt here"

VS Code Dev Container

Base Variant

  1. Open this repository in VS Code
  2. Install the "Dev Containers" extension
  3. Run "Dev Containers: Reopen in Container" from the command palette

DinD Variant

Use the alternative configuration for Docker-in-Docker support:

# Using devcontainer CLI
devcontainer up --workspace-folder . \
  --config .devcontainer/devcontainer-dind.json

# Or rename the config (backup original first)
mv .devcontainer/devcontainer.json .devcontainer/devcontainer-base.json
mv .devcontainer/devcontainer-dind.json .devcontainer/devcontainer.json

Docker-in-Docker Usage

⚠️ Important: The claude-dind variant's firewall blocks Docker Hub image pulls due to dynamic CDN domains. For Docker development with image pulling, use the claude-docker-host variant instead. See Known Limitations for details.

When to Use Each Variant

Base Variant (claude) ⭐ Recommended

  • ✅ Standard Claude Code development
  • ✅ Smallest image size (3.47GB)
  • ✅ Fastest startup (~2 seconds)
  • ✅ Lowest resource usage
  • Use when: General development without Docker needs (most users)

Host Socket Variant (claude-docker-host) - For Docker development

  • ✅ Access to Docker without firewall issues
  • ✅ Shares host Docker daemon and images
  • ✅ Lower resource usage than separate daemon
  • ✅ No privileged mode required
  • ⚠️ Can affect host Docker state
  • ⚠️ Requires host Docker installation
  • Use when: You need Docker and trust your environment

Separate Daemon Variant (claude-dind) - For isolated environments

  • ✅ Full isolation from host Docker
  • ✅ Persistent Docker cache in container
  • ✅ Works without host Docker
  • ⚠️ Requires privileged mode
  • ⚠️ Higher resource usage (+100-200MB RAM)
  • ⚠️ Docker Hub pulls blocked by firewall
  • ⚠️ Slower startup (~8 seconds)
  • Use when: You need isolated Docker or untrusted code testing

Testing Docker Inside Container

# Enter DinD container
docker compose exec claude-dind zsh

# Verify Docker installation
docker version
docker compose version

# Note: Docker Hub image pulls may be blocked by firewall
# See "Known Limitations" section below for workarounds

Known Limitations and Workarounds

Docker Hub Image Pulls with Firewall

Issue: Docker Hub now uses Cloudflare R2 storage with dynamic subdomains that cannot be whitelisted by domain name. Image pulls will timeout when the firewall is enabled.

Symptoms:

failed to do request: Get "https://docker-images-prod.*.r2.cloudflarestorage.com/...":
dial tcp 172.64.66.1:443: i/o timeout

Workarounds:

Option 1: Disable Firewall for DinD Container

# Method A: Skip firewall initialization
docker run -it --rm --privileged \
  -e SKIP_FIREWALL=1 \
  claude-container:dind

# Method B: Run with permissive OUTPUT policy
docker run -it --rm --privileged \
  claude-container:dind bash -c \
  "sudo iptables -P OUTPUT ACCEPT && exec zsh"

Option 2: Use Host Docker Socket (Recommended)

The claude-docker-host variant mounts the host's Docker socket, bypassing the firewall issue:

docker compose up -d claude-docker-host
docker compose exec claude-docker-host zsh

# Now use host's Docker (images, containers shared with host)
docker pull alpine
docker images  # Shows host images

Option 3: Pre-pull Images on Host

Pull images on your host machine, then they're available inside DinD:

# On host
docker pull alpine:latest
docker pull node:20

# In claude-docker-host container
docker images  # Images available immediately

Option 4: Use Local/Private Registry

Configure a local or private registry with known domain names:

# Add your registry to allowed-domains.conf
echo "registry.mycompany.com" >> .devcontainer/allowed-domains.conf

# Rebuild and use
docker pull registry.mycompany.com/myimage:latest

Option 5: Build Images Locally

Build images inside the container without pulling from Docker Hub:

# Inside DinD container
cat > Dockerfile <<'EOF'
FROM scratch
COPY ./myapp /app
CMD ["/app/myapp"]
EOF

docker build -t myapp:latest .
docker run myapp:latest

Firewall Configuration

The container uses a domain-whitelist firewall that blocks all outbound traffic except to approved domains. A default whitelist is baked into the image at /usr/local/etc/allowed-domains.conf.

Default Allowed Domains

  • api.anthropic.com - Claude API
  • registry.npmjs.org - NPM packages
  • github.com, api.github.com - GitHub
  • repo1.maven.org, repo.maven.apache.org - Maven Central
  • playwright.azureedge.net - Playwright browser downloads
  • VS Code marketplace domains
  • registry-1.docker.io, auth.docker.io - Docker Hub (DinD variant)

Customizing the Domain Whitelist

To use your own domain whitelist, bind mount a custom config file:

docker run -it --rm \
  --cap-add=NET_ADMIN \
  --cap-add=NET_RAW \
  -v "/path/to/your/allowed-domains.conf:/usr/local/etc/allowed-domains.conf:ro" \
  -v "/path/to/claude-container-config:/claude-container-config" \
  -e CLAUDE_CONFIG_DIR="/claude-container-config" \
  claude-container

Or copy the default config and modify it:

# Copy from this repo
cp .devcontainer/allowed-domains.conf ~/my-allowed-domains.conf

# Edit to add your domains
echo "example.com" >> ~/my-allowed-domains.conf

Runtime Firewall Commands

Inside the container:

# View current firewall rules
firewall-status

# Reload firewall after editing the config
firewall-reload

Environment Variables

Variable Description
CLAUDE_CONFIG_DIR Directory for Claude authentication and config (mount from host for persistence)
GH_TOKEN GitHub Personal Access Token for gh CLI authentication
GIT_USER_NAME Git author/committer name (sets git config --global user.name)
GIT_USER_EMAIL Git author/committer email (sets git config --global user.email)
TZ Timezone (default: Europe/Helsinki). Pass -e TZ=$TZ to inherit from host
CLAUDE_CODE_VERSION Claude Code version to install (default: latest)
SKIP_FIREWALL Set to 1 to skip firewall initialization (useful for DinD troubleshooting)
FIREWALL_CONFIG Custom path to allowed-domains.conf (default: workspace or /usr/local/etc)
DOCKER_HOST Docker daemon socket (default: unix:///var/run/docker.sock)
PLAYWRIGHT_BROWSERS_PATH Path to pre-installed Playwright browsers (default: /opt/playwright-browsers)
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD Set to 1 to skip browser downloads (default: 1, browsers are pre-installed)
NOTIFICATION_URL URL to POST notifications to when Claude is idle or needs permission (e.g., https://ntfy.sh/your-topic)
VAADIN_PRO_KEY Vaadin Pro/Commercial subscription key for commercial components (Charts, Board, etc.) and Acceleration Kits

Notifications

The container can notify you when Claude needs attention via any URL that accepts POST requests (e.g., ntfy.sh, Slack webhooks, custom endpoints).

Setup

Set the NOTIFICATION_URL environment variable:

# In .env file
NOTIFICATION_URL=https://ntfy.sh/your-topic-here

# Or via docker run
docker run -it --rm \
  -e NOTIFICATION_URL=https://ntfy.sh/your-topic-here \
  claude-container:base

The entrypoint automatically configures Claude Code hooks in settings.json for these events:

Event Message
idle_prompt Claude finished and is waiting for your input (60+ seconds idle)
permission_prompt Claude needs your permission to proceed
elicitation_dialog Claude is asking a question and waiting for your answer

Additional Hook Events

You can manually add more hooks to ~/.claude/settings.json for these events:

Matcher / Event Description
auth_success Authentication completed
Stop (event, not matcher) Claude finished responding (fires every time)
TaskCompleted (event) A task was marked as completed

See the Claude Code hooks documentation for the full reference.

Vaadin Commercial Components

The container supports Vaadin commercial components (Charts, Board, Grid Pro, etc.) and Acceleration Kits via the VAADIN_PRO_KEY environment variable.

Providing the Pro Key

There are two ways to provide your Vaadin Pro subscription key:

Option 1: Environment variable (recommended for containers)

Set VAADIN_PRO_KEY in your .env file or pass it directly:

# In .env file
VAADIN_PRO_KEY=your-pro-key-here

# Or via docker run
docker run -it --rm \
  -e VAADIN_PRO_KEY=your-pro-key-here \
  claude-container:base

Option 2: Mount a proKey file

If you prefer file-based configuration, mount your ~/.vaadin/proKey file from the host:

docker run -it --rm \
  -v "${HOME}/.vaadin:/home/node/.vaadin:ro" \
  claude-container:base

Firewall Domains

If you're using Vaadin commercial components with the firewall enabled, uncomment the Vaadin domains in allowed-domains.conf:

maven.vaadin.com
tools.vaadin.com
vaadin.com
cdn.vaadin.com

Or add them at runtime:

# Inside the container
echo -e "maven.vaadin.com\ntools.vaadin.com\nvaadin.com\ncdn.vaadin.com" | sudo tee -a /usr/local/etc/allowed-domains.conf
firewall-reload

Playwright

The container includes Playwright with Chromium pre-installed, ready to use with both Node.js and Java Playwright libraries.

Pre-installed Browsers

The container includes two versions of Chromium to support different use cases:

  • Standard Playwright Chromium (e.g., chromium-1208) - For Java Playwright and direct Node.js Playwright usage
  • MCP Playwright Chromium (e.g., chromium-1209) - For Claude's browser automation tools (@playwright/mcp)
  • FFmpeg (for video recording)

This dual installation ensures that Claude's MCP browser tools work without downloading browsers at runtime.

Browsers are installed at /opt/playwright-browsers and owned by the node user (writable for lock files).

MCP Configuration (.mcp.json)

To use the pre-installed MCP browsers (avoiding downloads at runtime), pin the @playwright/mcp version in your .mcp.json:

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": [
        "@playwright/mcp@0.0.64",
        "--headless",
        "--browser",
        "chromium"
      ]
    }
  }
}

Key options:

  • @playwright/mcp@X.X.X - Pin to the installed version (check with playwright-info)
  • --headless - Run without visible browser UI (recommended for containers)
  • --browser chromium - Explicitly use Chromium

Check the installed MCP version:

playwright-info
# Shows: MCP Package: @playwright/mcp@0.0.64

Using @latest instead of a pinned version will download new browsers at runtime, which may be slow or fail if the firewall blocks downloads.

Java Playwright

The container is configured to work with Java Playwright out of the box:

  • PLAYWRIGHT_BROWSERS_PATH points to pre-installed browsers
  • PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 prevents unnecessary download attempts
  • Required GUI libraries (libgtk-3, libXcursor) are pre-installed

Important: Match your Java Playwright version to the container's Playwright version to use pre-installed browsers.

Check compatibility info:

# Inside the container, run:
playwright-info

# Or check the VERSION file directly:
cat /opt/playwright-browsers/VERSION

Example output:

Standard Playwright: 1.58.1
  Chromium:          chromium-1208

MCP Package:         @playwright/mcp@0.0.64
  Playwright:        1.59.0-alpha
  Chromium:          chromium-1209

Use this version in your Maven pom.xml:

<dependency>
    <groupId>com.microsoft.playwright</groupId>
    <artifactId>playwright</artifactId>
    <version>1.58.1</version>  <!-- Match PLAYWRIGHT_VERSION -->
</dependency>

Remote Debugging (CDP)

The container includes cdp-proxy-monitor, a background script that lets you connect Chrome DevTools to Playwright browsers running inside the container.

The Problem

Playwright MCP launches Chrome with a random --remote-debugging-port each time. The container exposes port 9222 for external Chrome DevTools connections, but there's no way to force Playwright MCP to use a fixed port.

How It Works

cdp-proxy-monitor runs in the background and:

  1. Polls every second for a running Chrome process with --remote-debugging-port
  2. Extracts the actual port Chrome is listening on
  3. Sets up a socat proxy: 0.0.0.0:9222 -> 127.0.0.1:<chrome-port>
  4. Detects when Chrome dies and waits for a new instance
  5. Auto-reconnects when Chrome restarts on a different port
Event Monitor action
Chrome not running Idles, checks every 1s
Chrome starts (port N) Starts proxy: 9222 -> N, verifies
Chrome still on port N No action
Chrome dies Kills proxy, waits
Chrome restarts (port M) Re-proxies: 9222 -> M, verifies

Quick Start

  1. Start the container with port 9222 exposed:

    docker run -it --rm \
      --cap-add=NET_ADMIN \
      --cap-add=NET_RAW \
      -e SKIP_FIREWALL=1 \
      -p 9222:9222 \
      claude-container:base
  2. Inside the container, start the CDP proxy monitor:

    nohup cdp-proxy-monitor > /tmp/cdp-proxy.log 2>&1 &
  3. Connect from Chrome:

    • Forward port 9222 if the container is on a remote host: ssh -L 9222:localhost:9222 user@docker-host
    • Open chrome://inspect in your local Chrome
    • Click "Configure..." and add localhost:9222
    • Playwright-controlled browsers will appear as remote targets

Check the proxy log:

tail -f /tmp/cdp-proxy.log

Security Note

Remote debugging exposes browser internals. Only enable when needed and do not expose port 9222 to untrusted networks.

Troubleshooting

Docker Hub Pulls Timeout in DinD

Symptom: dial tcp 172.64.66.1:443: i/o timeout when pulling Docker images

Solution: Use the claude-docker-host variant instead, or disable the firewall:

# Recommended: Use host socket variant
docker compose up -d claude-docker-host

# Alternative: Disable firewall in DinD
docker run -it --rm --privileged -e SKIP_FIREWALL=1 claude-container:dind

Docker Daemon Fails to Start

Symptom: "Error: Docker daemon failed to start"

Check logs:

# Inside container
cat /var/log/docker.log

# Common issues:
# - Missing privileged mode: Add --privileged flag
# - Missing SYS_ADMIN capability: Add --cap-add=SYS_ADMIN

Solution: Ensure proper flags:

docker run --rm --privileged \
  --cap-add=NET_ADMIN \
  --cap-add=NET_RAW \
  --cap-add=SYS_ADMIN \
  claude-container:dind

Firewall Blocking Required Domain

Symptom: Connection timeouts or "Could not resolve" warnings

Check firewall status:

# Inside container
firewall-status

# Check if domain is allowed
grep "mydomain.com" /usr/local/etc/allowed-domains.conf

Solution: Add domain to whitelist:

# Edit allowed-domains.conf
echo "mydomain.com" >> .devcontainer/allowed-domains.conf

# Rebuild image
docker build -t claude-container:base --target base .devcontainer/

# Or reload firewall at runtime (temporary)
# Inside container:
echo "mydomain.com" | sudo tee -a /usr/local/etc/allowed-domains.conf
firewall-reload

Permission Denied Errors

Symptom: "Permission denied" when accessing Docker socket

For host socket variant:

# Ensure your user is in docker group on host
sudo usermod -aG docker $USER
newgrp docker

# Or run container with your user's UID
docker run --rm -u $(id -u):$(getent group docker | cut -d: -f3) \
  -v /var/run/docker.sock:/var/run/docker.sock \
  claude-container:dind

Overlay2 Storage Driver Issues

Symptom: "invalid argument" when running nested containers

Solution: Use a named volume for /var/lib/docker:

docker run --rm --privileged \
  -v claude-docker-data:/var/lib/docker \
  claude-container:dind

Rebuilding

After modifying the Dockerfile:

# Docker Compose (base variant)
docker compose build --no-cache claude

# Docker Compose (DinD variants)
docker compose build --no-cache claude-dind
docker compose build --no-cache claude-docker-host

# Docker directly
docker build --no-cache -t claude-container:base --target base .devcontainer/
docker build --no-cache -t claude-container:dind --target dind .devcontainer/

Quick Reference (Docker Remote)

For Docker remote setups where the Docker daemon runs on a separate host, use absolute paths on the Docker host:

docker run -it --rm \
  --cap-add=NET_ADMIN \
  --cap-add=NET_RAW \
  -v "/home/deb/claude-container-config:/claude-container-config" \
  -w /workspace \
  -e CLAUDE_CONFIG_DIR="/claude-container-config" \
  claude-container

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors