dockerimg: fix Chromium support with multi-stage Docker build

OMG, people, OMG. So, an earlier commit moved us to Ubuntu, and it turns
out that "apt-get install chromium-browser" or whatever just does
nothing, and tells you to use the snap. Snap requires systemd, and if
you're using containers, you don't usually have systemd, and ... yeah,
no screenshots. There are no great stories for where to get Chromium.
There's a dude who publishes the Mint Linux packages in a compatible way
for Ubuntu. I chose instead the headless-chrome from a Docker build
recommended by the Chromedp library that we use to control Chromium.

I'm a bit snappy about all of this.

...

Replace Ubuntu 24 snap-based Chromium installation with chromedp/headless-shell
to resolve container compatibility issues where snaps don't work properly.

Changes include:

1. Multi-stage Dockerfile.base build:
   - Stage 1: Extract headless-shell from docker.io/chromedp/headless-shell:stable
   - Stage 2: Main Ubuntu 24.04 application image with required Chrome dependencies
   - Remove chromium package from apt-get install (replaced with headless-shell)
   - Add required libraries: libglib2.0-0, libnss3, libx11-6, libxcomposite1,
     libxdamage1, libxext6, libxi6, libxrandr2, libgbm1, libgtk-3-0
   - Add headless-shell to PATH so chromedp can find it automatically

2. Updated documentation in browse/README.md:
   - Document Docker multi-stage build approach
   - Clarify requirements for Docker vs local development

Benefits:
- Resolves Ubuntu 24 snap incompatibility issues in containers
- Provides self-contained Chrome installation without system dependencies
- Maintains backward compatibility for local development
- Uses proven chromedp/headless-shell for reliable browser automation
- Eliminates need for manual Chrome/Chromium installation in containers
- No code changes needed in browse.go - chromedp finds headless-shell via PATH

The headless-shell binary is automatically discovered by chromedp's default
executable search since it's added to PATH in the Docker environment.

Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: se4808dca7afba802k
diff --git a/dockerimg/testdata/testcreatedockerfile_python_misery.httprr b/dockerimg/testdata/testcreatedockerfile_python_misery.httprr
index 6b4a271..960c2be 100644
--- a/dockerimg/testdata/testcreatedockerfile_python_misery.httprr
+++ b/dockerimg/testdata/testcreatedockerfile_python_misery.httprr
@@ -1,9 +1,9 @@
 httprr trace v1
-4804 1989
+5240 1817
 POST https://api.anthropic.com/v1/messages HTTP/1.1

 Host: api.anthropic.com

 User-Agent: Go-http-client/1.1

-Content-Length: 4607

+Content-Length: 5043

 Anthropic-Version: 2023-06-01

 Content-Type: application/json

 

@@ -15,7 +15,7 @@
    "content": [
     {
      "type": "text",
-     "text": "\nCall the dockerfile tool to create a Dockerfile.\nThe parameters to dockerfile fill out the From and ExtraCmds\ntemplate variables in the following Go template:\n\n```\nFROM ubuntu:24.04\n\n# Switch from dash to bash by default.\nSHELL [\"/bin/bash\", \"-euxo\", \"pipefail\", \"-c\"]\n\n# attempt to keep package installs lean\nRUN printf '%s\\n' \\\n      'path-exclude=/usr/share/man/*' \\\n      'path-exclude=/usr/share/doc/*' \\\n      'path-exclude=/usr/share/doc-base/*' \\\n      'path-exclude=/usr/share/info/*' \\\n      'path-exclude=/usr/share/locale/*' \\\n      'path-exclude=/usr/share/groff/*' \\\n      'path-exclude=/usr/share/lintian/*' \\\n      'path-exclude=/usr/share/zoneinfo/*' \\\n    \u003e /etc/dpkg/dpkg.cfg.d/01_nodoc\n\n# Install system packages including ca-certificates for Go downloads\nRUN apt-get update; \\\n\tapt-get install -y --no-install-recommends \\\n\t\tca-certificates wget \\\n\t\tgit jq sqlite3 npm nodejs gh ripgrep fzf python3 curl vim chromium lsof iproute2 less \u0026\u0026 \\\n\tapt-get clean \u0026\u0026 \\\n\trm -rf /var/lib/apt/lists/* \u0026\u0026 \\\n\trm -rf /usr/share/{doc,doc-base,info,lintian,man,groff,locale,zoneinfo}/*\n\n# Install Go 1.24\nENV GO_VERSION=1.24.3\nENV GOROOT=/usr/local/go\nENV GOPATH=/go\nENV PATH=$GOROOT/bin:$GOPATH/bin:$PATH\n\nRUN ARCH=$(uname -m) \u0026\u0026 \\\n\tcase $ARCH in \\\n\t\tx86_64) GOARCH=amd64 ;; \\\n\t\taarch64) GOARCH=arm64 ;; \\\n\t\t*) echo \"Unsupported architecture: $ARCH\" \u0026\u0026 exit 1 ;; \\\n\tesac \u0026\u0026 \\\n\twget -O go.tar.gz \"https://golang.org/dl/go${GO_VERSION}.linux-${GOARCH}.tar.gz\" \u0026\u0026 \\\n\ttar -C /usr/local -xzf go.tar.gz \u0026\u0026 \\\n\trm go.tar.gz\n\n# Create GOPATH directory\nRUN mkdir -p \"$GOPATH/src\" \"$GOPATH/bin\" \u0026\u0026 chmod -R 755 \"$GOPATH\"\n\n# While these binaries install generally useful supporting packages,\n# the specific versions are rarely what a user wants so there is no\n# point polluting the base image module with them.\n\nRUN go install golang.org/x/tools/cmd/goimports@latest; \\\n\tgo install golang.org/x/tools/gopls@latest; \\\n\tgo install mvdan.cc/gofumpt@latest; \\\n\tgo clean -cache -testcache -modcache\n\nENV GOTOOLCHAIN=auto\nENV SKETCH=1\n\nRUN mkdir -p /root/.cache/sketch/webui\nARG GIT_USER_EMAIL\nARG GIT_USER_NAME\n\nRUN git config --global user.email \"$GIT_USER_EMAIL\" \u0026\u0026 \\\n    git config --global user.name \"$GIT_USER_NAME\" \u0026\u0026 \\\n    git config --global http.postBuffer 524288000\n\nLABEL sketch_context=\"{{.InitFilesHash}}\"\nCOPY . /app\nRUN rm -f /app/tmp-sketch-dockerfile\n\nWORKDIR /app{{.SubDir}}\nRUN if [ -f go.mod ]; then go mod download; fi\n\n# Switch to lenient shell so we are more likely to get past failing extra_cmds.\nSHELL [\"/bin/bash\", \"-uo\", \"pipefail\", \"-c\"]\n\n{{.ExtraCmds}}\n\n# Switch back to strict shell after extra_cmds.\nSHELL [\"/bin/bash\", \"-euxo\", \"pipefail\", \"-c\"]\n\nCMD [\"/bin/sketch\"]\n\n```\n\nIn particular:\n- Assume it is primarily a Go project.\n- Python env setup is challenging and often no required, so any RUN commands involving python tooling should be written to let docker build continue if there is a failure.\n- Include any tools particular to this repository that can be inferred from the given context.\n- Append || true to any apt-get install commands in case the package does not exist.\n- MINIMIZE the number of extra_cmds generated. Straightforward environments do not need any.\n- Do NOT expose any ports.\n- Do NOT generate any CMD or ENTRYPOINT extra commands.\nHere is the content of several files from the repository that may be relevant:\n\n"
+     "text": "\nCall the dockerfile tool to create a Dockerfile.\nThe parameters to dockerfile fill out the From and ExtraCmds\ntemplate variables in the following Go template:\n\n```\n# Stage 1: Get Chrome/Chromium from chromedp/headless-shell\nFROM docker.io/chromedp/headless-shell:stable AS chrome\n\n# Stage 2: Main application image\nFROM ubuntu:24.04\n\n# Switch from dash to bash by default.\nSHELL [\"/bin/bash\", \"-euxo\", \"pipefail\", \"-c\"]\n\n# attempt to keep package installs lean\nRUN printf '%s\\n' \\\n      'path-exclude=/usr/share/man/*' \\\n      'path-exclude=/usr/share/doc/*' \\\n      'path-exclude=/usr/share/doc-base/*' \\\n      'path-exclude=/usr/share/info/*' \\\n      'path-exclude=/usr/share/locale/*' \\\n      'path-exclude=/usr/share/groff/*' \\\n      'path-exclude=/usr/share/lintian/*' \\\n      'path-exclude=/usr/share/zoneinfo/*' \\\n    \u003e /etc/dpkg/dpkg.cfg.d/01_nodoc\n\n# Install system packages (removed chromium, will use headless-shell instead)\nRUN apt-get update; \\\n\tapt-get install -y --no-install-recommends \\\n\t\tca-certificates wget \\\n\t\tgit jq sqlite3 npm nodejs gh ripgrep fzf python3 curl vim lsof iproute2 less \\\n\t\tlibglib2.0-0 libnss3 libx11-6 libxcomposite1 libxdamage1 \\\n\t\tlibxext6 libxi6 libxrandr2 libgbm1 libgtk-3-0 \u0026\u0026 \\\n\tapt-get clean \u0026\u0026 \\\n\trm -rf /var/lib/apt/lists/* \u0026\u0026 \\\n\trm -rf /usr/share/{doc,doc-base,info,lintian,man,groff,locale,zoneinfo}/*\n\n# Install Go 1.24\nENV GO_VERSION=1.24.3\nENV GOROOT=/usr/local/go\nENV GOPATH=/go\nENV PATH=$GOROOT/bin:$GOPATH/bin:$PATH\n\nRUN ARCH=$(uname -m) \u0026\u0026 \\\n\tcase $ARCH in \\\n\t\tx86_64) GOARCH=amd64 ;; \\\n\t\taarch64) GOARCH=arm64 ;; \\\n\t\t*) echo \"Unsupported architecture: $ARCH\" \u0026\u0026 exit 1 ;; \\\n\tesac \u0026\u0026 \\\n\twget -O go.tar.gz \"https://golang.org/dl/go${GO_VERSION}.linux-${GOARCH}.tar.gz\" \u0026\u0026 \\\n\ttar -C /usr/local -xzf go.tar.gz \u0026\u0026 \\\n\trm go.tar.gz\n\n# Create GOPATH directory\nRUN mkdir -p \"$GOPATH/src\" \"$GOPATH/bin\" \u0026\u0026 chmod -R 755 \"$GOPATH\"\n\n# While these binaries install generally useful supporting packages,\n# the specific versions are rarely what a user wants so there is no\n# point polluting the base image module with them.\n\nRUN go install golang.org/x/tools/cmd/goimports@latest; \\\n\tgo install golang.org/x/tools/gopls@latest; \\\n\tgo install mvdan.cc/gofumpt@latest; \\\n\tgo clean -cache -testcache -modcache\n\n# Copy the self-contained Chrome bundle from chromedp/headless-shell\nCOPY --from=chrome /headless-shell /headless-shell\nENV PATH=\"/headless-shell:${PATH}\"\n\nENV GOTOOLCHAIN=auto\nENV SKETCH=1\n\nRUN mkdir -p /root/.cache/sketch/webui\nARG GIT_USER_EMAIL\nARG GIT_USER_NAME\n\nRUN git config --global user.email \"$GIT_USER_EMAIL\" \u0026\u0026 \\\n    git config --global user.name \"$GIT_USER_NAME\" \u0026\u0026 \\\n    git config --global http.postBuffer 524288000\n\nLABEL sketch_context=\"{{.InitFilesHash}}\"\nCOPY . /app\nRUN rm -f /app/tmp-sketch-dockerfile\n\nWORKDIR /app{{.SubDir}}\nRUN if [ -f go.mod ]; then go mod download; fi\n\n# Switch to lenient shell so we are more likely to get past failing extra_cmds.\nSHELL [\"/bin/bash\", \"-uo\", \"pipefail\", \"-c\"]\n\n{{.ExtraCmds}}\n\n# Switch back to strict shell after extra_cmds.\nSHELL [\"/bin/bash\", \"-euxo\", \"pipefail\", \"-c\"]\n\nCMD [\"/bin/sketch\"]\n\n```\n\nIn particular:\n- Assume it is primarily a Go project.\n- Python env setup is challenging and often no required, so any RUN commands involving python tooling should be written to let docker build continue if there is a failure.\n- Include any tools particular to this repository that can be inferred from the given context.\n- Append || true to any apt-get install commands in case the package does not exist.\n- MINIMIZE the number of extra_cmds generated. Straightforward environments do not need any.\n- Do NOT expose any ports.\n- Do NOT generate any CMD or ENTRYPOINT extra commands.\nHere is the content of several files from the repository that may be relevant:\n\n"
     },
     {
      "type": "text",
@@ -53,25 +53,25 @@
 }HTTP/2.0 200 OK

 Anthropic-Organization-Id: 3c473a21-7208-450a-a9f8-80aebda45c1b

 Anthropic-Ratelimit-Input-Tokens-Limit: 200000

-Anthropic-Ratelimit-Input-Tokens-Remaining: 144000

-Anthropic-Ratelimit-Input-Tokens-Reset: 2025-05-28T17:53:53Z

+Anthropic-Ratelimit-Input-Tokens-Remaining: 193000

+Anthropic-Ratelimit-Input-Tokens-Reset: 2025-05-29T03:24:16Z

 Anthropic-Ratelimit-Output-Tokens-Limit: 80000

-Anthropic-Ratelimit-Output-Tokens-Remaining: 75000

-Anthropic-Ratelimit-Output-Tokens-Reset: 2025-05-28T17:53:43Z

+Anthropic-Ratelimit-Output-Tokens-Remaining: 80000

+Anthropic-Ratelimit-Output-Tokens-Reset: 2025-05-29T03:24:16Z

 Anthropic-Ratelimit-Requests-Limit: 4000

 Anthropic-Ratelimit-Requests-Remaining: 3999

-Anthropic-Ratelimit-Requests-Reset: 2025-05-28T17:53:35Z

+Anthropic-Ratelimit-Requests-Reset: 2025-05-29T03:24:12Z

 Anthropic-Ratelimit-Tokens-Limit: 280000

-Anthropic-Ratelimit-Tokens-Remaining: 219000

-Anthropic-Ratelimit-Tokens-Reset: 2025-05-28T17:53:43Z

+Anthropic-Ratelimit-Tokens-Remaining: 273000

+Anthropic-Ratelimit-Tokens-Reset: 2025-05-29T03:24:16Z

 Cf-Cache-Status: DYNAMIC

-Cf-Ray: 946f9cc0f9ce155d-SJC

+Cf-Ray: 9472e0a2b9361761-SJC

 Content-Type: application/json

-Date: Wed, 28 May 2025 17:53:40 GMT

-Request-Id: req_011CPaMaqAuex9rgEk11Wzaj

+Date: Thu, 29 May 2025 03:24:16 GMT

+Request-Id: req_011CPb76e9UX9D1LzcnwbayF

 Server: cloudflare

 Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

 Via: 1.1 google

 X-Robots-Tag: none

 

-{"id":"msg_01GTg6Do9GRWTxECSaTQFD1v","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"text","text":"Based on the repository content, I can see this is a Go project that also requires Python 3.11 and the DVC tool. I'll create a Dockerfile with the necessary extra commands to set up this environment."},{"type":"tool_use","id":"toolu_01Bbr3Pdz8N8KisNrwx2NMiM","name":"dockerfile","input":{"extra_cmds":"RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common || true\nRUN add-apt-repository ppa:deadsnakes/ppa || true\nRUN apt-get update && apt-get install -y --no-install-recommends python3.11 python3.11-pip python3.11-venv || true\nRUN python3.11 -m pip install --upgrade pip || true\nRUN python3.11 -m pip install dvc || true"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":4,"cache_creation_input_tokens":1598,"cache_read_input_tokens":0,"output_tokens":220,"service_tier":"standard"}}
\ No newline at end of file
+{"id":"msg_01LWAS7sZaenX5s3WJLKckdr","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"text","text":"Based on the repository content, I can see this is a Go project that also requires Python 3.11 and the DVC tool. I'll create a Dockerfile that sets up both environments."},{"type":"tool_use","id":"toolu_01WtP3RQpYCfwm7LPACAZTrX","name":"dockerfile","input":{"extra_cmds":"RUN apt-get update && apt-get install -y --no-install-recommends python3.11 python3.11-pip python3.11-venv || true && apt-get clean && rm -rf /var/lib/apt/lists/*\nRUN python3.11 -m pip install --no-cache-dir dvc || true"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":4,"cache_creation_input_tokens":1766,"cache_read_input_tokens":0,"output_tokens":175,"service_tier":"standard"}}
\ No newline at end of file