This is the promoted deployment path based on the Debian+jlesage runtime.
It runs Claude Desktop in a container and exposes the GUI in a browser.
It also includes in-container Chromium for OAuth and external login flows.
Chromium runs with a persistent profile at /config/chromium-profile.
- Create local config:
cp .env.example .env- Set a real web password in
.env:
WEB_AUTHENTICATION_PASSWORD=<strong-secret>
- Start:
docker compose up -d --build- Open the GUI:
https://localhost:15800
- Smoke check
./scripts/smoke.sh- Web endpoint/auth check
./scripts/verify-web.shUse another host target when needed:
HOST_ADDR=localhost ./scripts/verify-web.shIf host port publishing is unavailable in your environment, verify-web.sh exits
with code 2 after validating in-container TLS.
- HTTPS enabled (
SECURE_CONNECTION=1) - Web authentication enabled (
WEB_AUTHENTICATION=1) - Published ports are bound to host loopback (
127.0.0.1:15800,127.0.0.1:15900) - Container listeners remain on all interfaces (
WEB_LOCALHOST_ONLY=0,VNC_LOCALHOST_ONLY=0) - Non-default published ports (
15800/15900) to reduce collisions - Electron sandbox disabled by default (
CLAUDE_ENABLE_SANDBOX=0) for runtime stability - Linux frame/titlebar compatibility patch enabled by default (
CLAUDE_FRAME_FIX=1) BROWSERis preconfigured to an in-container Chromium launcher (/usr/local/bin/container-browser)xdg-openis wrapped to route HTTP/HTTPS links into in-container Chromium for OAuth popups- Both
/usr/local/bin/xdg-openand/usr/bin/xdg-openare routed through the wrapper so browser protocol handlers cannot bypass callback handling - Chromium is launched in full browser mode (tabs, extensions, normal window controls)
- Chromium profile lock cleanup is automatic to avoid stale
SingletonLockafter container restarts - OAuth links are opened in a normal Chromium window, and
window.openpopup flows are rewritten to same-tab navigation for Claude/Google auth hosts (includingabout:blankbootstrap popups) xdg-open://andclaude://callbacks are forwarded to Claude Desktop with the original URL and then focus the existing app window- Claude OAuth callback URLs (
/login/app-google-auth...) are intercepted byxdg-openand mapped to Claude window focus, not a new browser launch - Linux BrowserWindow normalization forces framed, non-kiosk popup windows so auth dialogs remain usable even when created inside Electron
- Main Claude window is auto-maximized on show (
CLAUDE_AUTO_MAXIMIZE=1) to avoid constrained viewport inside noVNC
./data/config->/config(Claude profile, logs, certs, auth state)./data/workspace->/workspace(project files, optional shared working dir)
Logs are written to:
data/config/log/claude-desktop/output.logdata/config/log/claude-desktop/error.log
Container logs:
docker logs -f claude-jlesage-debianAt startup, the container ensures this file exists:
/config/.config/Claude/claude_desktop_config.json
If missing, it is initialized from:
mcp/claude_desktop_config.json.example
Edit the persisted file in ./data/config/.config/Claude/claude_desktop_config.json
to add MCP servers.
Automatic clipboard sync requires:
- HTTPS
- Chromium-based browser
- Clipboard permission granted by browser
Use CLIPBOARD_VALIDATION.md for full test steps.
- If browser shows
ERR_CONNECTION_RESETwhile logs sayListening for HTTPs connections on port 5800, checkWEB_LOCALHOST_ONLY/VNC_LOCALHOST_ONLY. They must be0when using Docker port publishing. - If
smoke.shreports port collisions, changeWEB_PORT/VNC_PORTin.env. - If sandbox mode is enabled and you see
SIGILLrestarts, switchCLAUDE_ENABLE_SANDBOX=0. - If host-side HTTPS checks fail but container TLS passes, validate from your real Docker host/browser path.
- If login works but Claude main view is black, keep
CLAUDE_FRAME_FIX=1and restart; if needed, setCLAUDE_MENU_BAR=visibleto test compositor/titlebar compatibility. - If auth still opens an odd window shape, temporarily set
CLAUDE_WINDOW_TRACE=1, reproduce once, and inspectdata/config/log/claude-desktop/window-events.log.