User Tools

Site Tools


programming:docker

This is an old revision of the document!


Docker

This is the crawler-image note Multilingual support points at, and the place to pin a browser you will run inside a container. It is not a Docker tutorial. For the choice of crawler, see Crawler; for OpenWPM's own image, OpenWPM; for pinning the rest of a wave, Longitudinal.

Docker's default /dev/shm is 64m. Chromium and Firefox need more than that, or they crash. OpenWPM's README still says so in 2026: without --shm-size=2g, “you can expect Firefox to crash on 20-30% of sites.”1) Of the 1,120 crawled papers in this corpus, 0 name /dev/shm, shm-size, or –disable-dev-shm-usage. Of the 5,855 papers with readable full text (4 of 5,859 have no .cols), 0 name a container image digest (one homograph among the 5,859: “illicit image digests” in a perceptual-hashing paper). A tag is not a pin.

What this page is for

  • Here: pin the image with a digest, size shared memory, and run headed under Xvfb. The compose checker fails if a crawler image is unpinned or left on the 64m /dev/shm default. It does not check that the command is headed.
  • Crawler: Playwright vs Puppeteer vs Selenium vs a patched browser. Docker does not pick the instrument.
  • OpenWPM: the OpenWPM image, display_mode, and the Firefox pin. This page only repeats the shm-size line, because that line is about Docker, not about OpenWPM's tables.
  • Multilingual support: a LibreTranslate compose that uses libretranslate/libretranslate:latest. That tag last moved on 2026-08-23. Pin it the same way you pin a browser, or accept that a rerun next month is a different translator.

What to read first

Paper Why
Retana et al., IMC 2025, A Permissions Odyssey [1Retana, Alberto Fernández de; Rautenstrauch, Jannis; Santos-Grueiro, Igor; Stock, Ben (2025): "A Permissions Odyssey: A Systematic Study of Browser Permissions on Modern Websites", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] A live crawl that names a Playwright Docker image. The package is v1.45.1; the image tag is v1.46.0-jammy. Those are not the same pin.
Borgolte et al., TheWebConf 2020, Understanding the Performance Costs and Benefits of Privacy-focused Browser Extensions [2Borgolte, Kevin; Feamster, Nick (2020): "Understanding the Performance Costs and Benefits of Privacy-focused Browser Extensions", in: Proceedings of the ACM Web Conference. (DOI)] Docker as the reproducibility wrapper: “To ease deployment and reproducibility, we created Docker containers.”
Bui et al., IEEE S&P 2023, Detection of Inconsistencies in Privacy Practices of Browser Extensions [3Bui, Duc; Tang, Brian; Shin, Kang G. (2023): "Detection of Inconsistencies in Privacy Practices of Browser Extensions", in: Proceedings of the IEEE Symposium on Security and Privacy. (DOI)] Docker Swarm, and Chromium started “with arguments to make it run in the resourceconstrained docker environments.” The arguments are not named.

What the corpus actually reports

Membership is a full-text sweep: crawled papers whose paper.cols.txt matches /\bdocker\b/i. That is 81 of 1,120 crawled papers (7.2%). The same regex over the whole corpus is 293 of 5,859 (5.0%), with miss=4 (no .cols; the 293 are among the 5,855 readable papers). None of the crawled papers are missing a .cols file.

The 81 are not all crawler images. A hand map (scripts/docker_fold.mjs) splits them. Residue is empty: every sweep hit has a role, and every role has a sweep hit, or the report exits 1.

Role Papers of 81 Share of 1,120 crawled Meaning
crawler 41 3.7% The measurement browser or crawler ran inside Docker.
sandbox 20 1.8% Docker isolates analysis of an app, package, firmware, or malware sample.
target 8 0.7% Docker, a registry, or Kubernetes is what the paper studies.
artifact 6 0.5% Docker is how the paper ships code, not how it crawled.
citation 6 0.5% Related work, a bibliography, a .env file, a vendor page.

The page population is the 41 crawler-role papers. The other four roles are printed so they cannot vanish into “81 papers used Docker.” Schema tools[].name matching /docker/i among crawled papers is 56, almost all category infrastructure; 5 of 56 (8.9%) state a version. That schema view sees 36 of the 41 crawler-role papers and misses 5. Exact-string “Docker” undercounts the schema name field: 53 vs 56.

First crawler-role paper is 2017. 2025–2026 rows in the report are starred: those venue-years are incomplete by construction (Corpus).

The knobs this page is about are almost unreported:

Probe (full text, crawled = 1,120) Papers
/dev/shm or shm-size or disable-dev-shm-usage 0
image digest / sha256: + 12 or more hex 0 (1 corpus hit, the homograph above)
Dockerfile 11 (1.0%)
docker-compose / compose.yaml 8 (0.7%)
Xvfb / xvfb-run 13 (1.2%)

Of the 13 Xvfb papers, 12 used it and 1 recommended it. 7 of 41 crawler-role papers are in that sweep (17.1%). 5 used Xvfb without mentioning Docker — headed crawls are not a container story.

Among the 41, crawlConfig.headless is stated (not a sentinel) by 15 (36.6%), against 140 of 1,120 crawled (12.5%). A version on a used or produced automation tool is stated by 2 of 41 (4.9%). Two crawled papers mention Playwright and Docker in the same window; one is Retana et al. (load-bearing); the other is a bibliography URL in Bui et al.

These are reporting rates, not practice rates. A paper can pin a digest in a repository and never say so. Read them as what you can reconstruct from the PDF.

Pin the image, not the tag

mcr.microsoft.com/playwright:v1.62.0-noble is the tag Playwright's docs named on 2026-08-27. Tags move. The registry v2 Docker-Content-Digest for that tag on that day was an OCI index of 2 manifests (amd64 and arm64):

mcr.microsoft.com/playwright:v1.62.0-noble@sha256:baed2032d533817f3dbe6425de795788430ba345e819a1201337009ba17c9d07

Re-fetch before you pin; the digest above is a worked example, not a blessing. Retana et al. show the other failure mode: a package version (Playwright v1.45.1) packaged in an image tag (v1.46.0-jammy) [1Retana, Alberto Fernández de; Rautenstrauch, Jannis; Santos-Grueiro, Igor; Stock, Ben (2025): "A Permissions Odyssey: A Systematic Study of Browser Permissions on Modern Websites", in: Proceedings of the ACM Internet Measurement Conference. (DOI)]. Name both, or name the digest and skip the story.

libretranslate/libretranslate:latest on Multilingual support is the same class of pin. Hub last_updated on 2026-08-27 was 2026-08-23. The amd64 digest that day was sha256:8402eea7845b6890acdd4b1fb70320c130d2771935d36d697a901e8c149682ca; arm64 was sha256:110905ed2b68530de438c9126c951b0bb1c79669b8cd59d2f3bf2ecf679ea534. A host that is not amd64 must not copy the amd64 line.

Playwright's own Docker page, fetched 2026-08-27: “This Docker image is intended to be used for testing and development purposes only. It is not recommended to use this Docker image to visit untrusted websites.”2) A measurement crawl visits the live web. Treat Microsoft's image as a convenient browser pin for a lab, or build your own; do not ignore the sentence because the alternative is more work.

Three knobs for shared memory

Pick one that matches the browser. Combining them is fine.

Knob Who documents it What it does
Compose shm_size: 2g (or docker run --shm-size=2g) Docker Engine; OpenWPM README Raises /dev/shm from the omitted default of 64m. OpenWPM: Firefox crashes on 20-30% of sites without it.
Compose ipc: host (or --ipc=host) Playwright Docker docs Playwright: “recommended when using Chromium. Without it, Chromium can run out of memory and crash.”3)
Chromium --disable-dev-shm-usage Chromium base/base_switches.h (kDisableDevShmUsage) Linux-only workaround. The comment still cites crbug 715363 (Chromium source fetched 2026-08-27; the bugs.chromium.org page now redirects to a Google sign-in wall): in small-/dev/shm VMs, anonymous shm files go to a temp directory instead. It is a workaround, not a pin of the limit.

The checker below accepts shm_size or ipc: host. It does not parse Chromium flags out of command:. If you rely on --disable-dev-shm-usage alone, say so in the methods section; the checker will still fail the compose file, which is the point.

Playwright also wants --init (zombie PID 1) and, for crawling, user pwuser plus a seccomp profile. The example sets init: true and user: pwuser. The seccomp profile is a host file Playwright documents (seccomp_profile.json); it is not inlined here. They are not shm.

Headful under Xvfb

Headless Chromium is not the same instrument as a window. Extensions, some permission prompts, and anything that keys off document.hidden want a display. OpenWPM's knob is display_mode; Playwright's docs for headed Linux CI are xvfb-run npx playwright test. The Playwright image ships Xvfb.

13 of 1,120 crawled papers (1.2%) mention Xvfb. The corpus does not tell you how they sized the virtual screen.

On this host (no Docker binary, aarch64), xvfb-run is not enough by itself:

$ xvfb-run -a true
xvfb-run: error: xauth command not found
# exit 3

/usr/bin/Xvfb and xdpyinfo are present; xauth is not. Starting Xvfb :98 -ac -screen 0 1280x720x24 and then xdpyinfo -display :98 succeeded here on 2026-08-27. If you document xvfb-run as the command you ran, install xauth or you are documenting a wrapper that never started the server. The compose example still uses xvfb-run because that is what Playwright documents; the image has the pieces, this host does not.

We did not start a browser in a container. There is no Docker or Podman binary here. The digest, the Hub timestamps, and the docs pages were fetched over HTTP. The compose checker talks to no daemon.

A compose file you can check

The example pins the live Playwright index digest from 2026-08-27 (amd64 and arm64 manifests frozen together), raises shm, shares the host IPC namespace, runs as pwuser, and starts headed under Xvfb. It is a digest/shm skeleton, not a hardened crawl sandbox: Playwright's seccomp profile is a host file and is not inlined. Re-fetch the digest before you copy the image line. Compose without an explicit platform: runs the host architecture; name uname -m in the methods section. The npx playwright test command assumes a lockfile in the bind mount — without --offline / --no-install, npx can fetch a package that does not match the image's browsers.

crawler.compose.yaml
services:
  crawler:
    # Index digest (amd64+arm64). Compose uses the host arch unless you set
    # platform:. Re-fetch before you pin; tags move.
    image: mcr.microsoft.com/playwright:v1.62.0-noble@sha256:baed2032d533817f3dbe6425de795788430ba345e819a1201337009ba17c9d07
    init: true
    ipc: host
    shm_size: 2g
    user: pwuser
    working_dir: /work
    volumes:
      - ./:/work
    command: xvfb-run npx playwright test

The checker is stdlib Python. It is not a YAML parser: two-space service keys under services:, scalar image: lines. A file that needs anchors or quoted values with spaces will fail here rather than be half-parsed. A service looks like a crawler if its image name matches playwright, chromium, chrome, firefox, selenium, puppeteer, or openwpm. –all digest-pins every service (so the LibreTranslate sidecar cannot hide behind :latest) but still applies the shm rule only to crawler-looking images. Advertised flags: a compose path, –demo, –all. Service properties must sit at four-space indent; a nested build.shm_size does not count. shm_size: 64m is not a raise.

check_crawler_compose.py
#!/usr/bin/env python3
"""Does this Compose file pin a crawler image the way a measurement needs?
 
A tag is not a pin. ``libretranslate/libretranslate:latest`` (the example on
programming:multilingual_support) and ``mcr.microsoft.com/playwright:v1.62.0-noble``
both move. A digest does not. Chromium and Firefox also need shared memory
larger than Docker's default 64m ``/dev/shm``, or they crash — OpenWPM's
README says Firefox dies on 20–30% of sites without ``--shm-size=2g``.
 
    python3 check_crawler_compose.py compose.yaml
    python3 check_crawler_compose.py --demo
 
Exit status is 1 if any service that looks like a browser crawler is missing a
digest pin or a shared-memory knob (``shm_size`` strictly above 64m, or
``ipc: host``). ``--all`` digest-pins every service but still applies the shm
rule only to crawler-looking images. Stdlib only; does not talk to a daemon,
so it runs on a host with no Docker.
 
A service looks like a crawler if its image name matches playwright, chromium,
chrome, firefox, selenium, puppeteer, or openwpm. Service properties are
exactly four spaces; ``build.shm_size`` (six spaces) does not count.
"""
from __future__ import annotations
 
import argparse
import re
import sys
 
CRAWLER_IMAGE_RE = re.compile(
    r"playwright|chromium|chrome|firefox|selenium|puppeteer|openwpm",
    re.IGNORECASE,
)
DIGEST_RE = re.compile(r"@sha256:[0-9a-f]{64}$")
LATEST_RE = re.compile(r":latest(?:@sha256:[0-9a-f]{64})?\s*$")
# Service properties are exactly four spaces. Nested keys (build.shm_size, etc.)
# are six or more and must not satisfy the runtime check.
IMAGE_LINE_RE = re.compile(r"^    image:\s*(\S+)\s*$")
SHM_LINE_RE = re.compile(r"^    shm_size:\s*(\S+)\s*$")
IPC_HOST_RE = re.compile(r"^    ipc:\s*host\s*$")
SERVICE_RE = re.compile(r"^  ([A-Za-z0-9._-]+):\s*$")
INIT_RE = re.compile(r"^    init:\s*true\s*$")
SHM_SIZE_RE = re.compile(r"^(\d+)([kKmMgG])b?$")
DEFAULT_SHM_BYTES = 64 * 1024 * 1024
 
 
DEMO_BAD = """\
services:
  libretranslate:
    image: libretranslate/libretranslate:latest
    ports:
      - 5000:5000
  crawler:
    image: mcr.microsoft.com/playwright:v1.62.0-noble
    command: npx playwright test
"""
 
DEMO_GOOD = """\
services:
  crawler:
    image: mcr.microsoft.com/playwright:v1.62.0-noble@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
    init: true
    ipc: host
    shm_size: 2g
    user: pwuser
    command: xvfb-run npx playwright test
"""
 
 
def parse_services(text: str) -> list[dict]:
    """Pull service name, image, shm_size, ipc, init out of a Compose file.
 
    This is not a YAML parser. It reads the subset Compose files on this wiki
    actually use: two-space service keys under ``services:``, scalar ``image:``
    lines. A file that needs anchors, quotes-with-spaces, or extension fields
    will fail here rather than be half-parsed.
    """
    if "\t" in text:
        raise ValueError("compose file contains a tab; this checker does not guess indentation")
    services: list[dict] = []
    current = None
    in_services = False
    for line in text.splitlines():
        if line == "services:":
            in_services = True
            continue
        if in_services and line and not line.startswith(" ") and not line.startswith("#"):
            in_services = False
            current = None
        if not in_services:
            continue
        m = SERVICE_RE.match(line)
        if m:
            current = {
                "name": m.group(1),
                "image": None,
                "shm_size": None,
                "ipc_host": False,
                "init": False,
            }
            services.append(current)
            continue
        if current is None:
            continue
        im = IMAGE_LINE_RE.match(line)
        if im:
            current["image"] = im.group(1)
            continue
        sm = SHM_LINE_RE.match(line)
        if sm:
            current["shm_size"] = sm.group(1)
            continue
        if IPC_HOST_RE.match(line):
            current["ipc_host"] = True
            continue
        if INIT_RE.match(line):
            current["init"] = True
    return services
 
 
def is_crawler_image(image: str | None) -> bool:
    if image is None:
        return False
    return CRAWLER_IMAGE_RE.search(image) is not None
 
 
def shm_above_default(val: str) -> bool:
    m = SHM_SIZE_RE.fullmatch(val)
    if m is None:
        return False
    n = int(m.group(1))
    mul = {"k": 1024, "m": 1024**2, "g": 1024**3}[m.group(2).lower()]
    return n * mul > DEFAULT_SHM_BYTES
 
 
def check_service(svc: dict, require_shm: bool) -> list[str]:
    problems = []
    image = svc["image"]
    name = svc["name"]
    if image is None:
        problems.append(f"{name}: no image:")
        return problems
    if LATEST_RE.search(image) and DIGEST_RE.search(image) is None:
        problems.append(f"{name}: image uses :latest with no digest ({image})")
    if DIGEST_RE.search(image) is None:
        problems.append(f"{name}: image is not digest-pinned ({image})")
    if require_shm:
        if svc["shm_size"] is None and not svc["ipc_host"]:
            problems.append(
                f"{name}: neither shm_size nor ipc: host — Docker default /dev/shm is 64m"
            )
        elif svc["shm_size"] is not None and not svc["ipc_host"] and not shm_above_default(svc["shm_size"]):
            problems.append(
                f"{name}: shm_size {svc['shm_size']} is not above Docker's 64m default"
            )
    return problems
 
 
def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("compose", nargs="?", help="path to a compose YAML file")
    ap.add_argument("--demo", action="store_true", help="run the built-in good/bad fixtures")
    ap.add_argument(
        "--all",
        action="store_true",
        help="digest-pin every service; shm still only for crawler-looking images",
    )
    args = ap.parse_args()
 
    if args.demo:
        return run_demo()
    if args.compose is None:
        ap.error("compose path required (or pass --demo)")
    text = open(args.compose, encoding="utf-8").read()
    return check_text(text, args.compose, args.all)
 
 
def check_text(text: str, label: str, check_all: bool) -> int:
    services = parse_services(text)
    if len(services) == 0:
        print(f"FAILURE: {label}: no services block parsed")
        return 1
    n_checked = 0
    n_crawler = 0
    problems = []
    for svc in services:
        looks_crawler = is_crawler_image(svc["image"])
        if not check_all and not looks_crawler:
            print(f"skip  {svc['name']}: image={svc['image']}")
            continue
        n_checked += 1
        if looks_crawler:
            n_crawler += 1
        ps = check_service(svc, require_shm=looks_crawler)
        shm = svc["shm_size"] if svc["shm_size"] is not None else ("ipc:host" if svc["ipc_host"] else "MISSING")
        pin = "digest" if svc["image"] and DIGEST_RE.search(svc["image"]) else "unpinned"
        print(
            f"check {svc['name']}: image={svc['image']} pin={pin} shm={shm} init={svc['init']}"
        )
        problems.extend(ps)
        if looks_crawler and not svc["init"]:
            print(f"note  {svc['name']}: init: true not set (Playwright recommends --init)")
    if n_checked == 0:
        print(f"FAILURE: {label}: no crawler-looking service (pass --all to check every service)")
        return 1
    if problems:
        for p in problems:
            print(f"FAILURE: {p}")
        return 1
    if n_crawler:
        print(f"OK {label}: {n_crawler} crawler service(s) digest-pinned with a shm knob")
    else:
        print(f"OK {label}: {n_checked} service(s) digest-pinned (no crawler image; shm not required)")
    return 0
 
 
def run_demo() -> int:
    print("=== DEMO_BAD (expect FAILURE) ===")
    rc_bad = check_text(DEMO_BAD, "DEMO_BAD", check_all=False)
    print(f"exit {rc_bad}")
    print()
    print("=== DEMO_GOOD (expect OK) ===")
    rc_good = check_text(DEMO_GOOD, "DEMO_GOOD", check_all=False)
    print(f"exit {rc_good}")
    if rc_bad == 1 and rc_good == 0:
        print("self-test OK: bad fixture failed, good fixture passed")
        return 0
    print(
        f"FAILURE: self-test expected bad=1 good=0, got bad={rc_bad} good={rc_good}"
    )
    return 1
 
 
if __name__ == "__main__":
    raise SystemExit(main())

Real output of python3 check_crawler_compose.py –demo (exit 0), 2026-08-27. The bad fixture is supposed to fail; FAILURE lines go to stdout and the demo still exits 0 because the self-test saw that failure. A compose file that is actually bad exits 1.

=== DEMO_BAD (expect FAILURE) ===
skip  libretranslate: image=libretranslate/libretranslate:latest
check crawler: image=mcr.microsoft.com/playwright:v1.62.0-noble pin=unpinned shm=MISSING init=False
note  crawler: init: true not set (Playwright recommends --init)
FAILURE: crawler: image is not digest-pinned (mcr.microsoft.com/playwright:v1.62.0-noble)
FAILURE: crawler: neither shm_size nor ipc: host — Docker default /dev/shm is 64m
exit 1

=== DEMO_GOOD (expect OK) ===
check crawler: image=mcr.microsoft.com/playwright:v1.62.0-noble@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef pin=digest shm=2g init=True
OK DEMO_GOOD: 1 crawler service(s) digest-pinned with a shm knob
exit 0
self-test OK: bad fixture failed, good fixture passed

Same checker against the example file:

check crawler: image=mcr.microsoft.com/playwright:v1.62.0-noble@sha256:baed2032d533817f3dbe6425de795788430ba345e819a1201337009ba17c9d07 pin=digest shm=2g init=True
OK pages/crawler.compose.yaml: 1 crawler service(s) digest-pinned with a shm knob

What to report

A methods section that a later crawl can reconstruct:

  1. The image digest (or a hash of a Dockerfile plus every FROM digest) and the architecture you ran (uname -m). The example digest is an OCI index of two manifests; a tag is a moving pointer. A package version inside the image is a second moving pointer; Retana et al. named both and they disagreed [1Retana, Alberto Fernández de; Rautenstrauch, Jannis; Santos-Grueiro, Igor; Stock, Ben (2025): "A Permissions Odyssey: A Systematic Study of Browser Permissions on Modern Websites", in: Proceedings of the ACM Internet Measurement Conference. (DOI)].
  2. The Playwright (or other automation) package version as well as the image. npx without a lockfile is not a pin.
  3. The shm knob: shm_size, ipc: host, and/or --disable-dev-shm-usage. If you omit this, you are running the 64m default, and OpenWPM's README is a measurement of what that does to Firefox.
  4. Headless or headed, and if headed, Xvfb vs a real display vs the vendor wrapper (xvfb-run).
  5. The browser and automation versions, not only “Playwright”. 2 of 41 crawler-role papers state an automation-tool version.
  6. Whether the image is a vendor “testing and development” image visiting the live web.
  • Crawler — which library or patched browser.
  • OpenWPM — OpenWPM's Docker flags, including --shm-size=2g and --init.
  • Multilingual support — LibreTranslate compose; the :latest pin.
  • Longitudinal — pinning the environment across waves. 0 papers in this corpus name a container digest; that page already records the same silence.
  • Stateful stateless — whether the profile persists; a new container per visit is one way to reset it.
  • Corpus — seven-venue scope and the 2025–2026 edge.

Queries, the unedited report, the role map, quote checks, and every rejected source are on docker.

Methodology and limitations of these figures

Corpus figures come from scripts/report_docker.mjs against data/extract/run1 (5,859 papers, CCS, IMC, NDSS, PETS, USENIX Security, TheWebConf, IEEE S&P, 2010–2026). The population is crawled ∩ /\bdocker\b/i, then a hand role. The 41 is not “papers that used Docker”; it is papers whose deciding sentence put the crawler in a container. Live Docker/Playwright/Chromium/Hub facts were re-fetched on 2026-08-27 by scripts/docker_probe.sh. This host has no Docker daemon; we did not run the example compose. 2025–2026 year shares in the report are provisional.

[1]
Retana, Alberto Fernández de; Rautenstrauch, Jannis; Santos-Grueiro, Igor; Stock, Ben (2025): "A Permissions Odyssey: A Systematic Study of Browser Permissions on Modern Websites", in: Proceedings of the ACM Internet Measurement Conference. (DOI)
[2]
Borgolte, Kevin; Feamster, Nick (2020): "Understanding the Performance Costs and Benefits of Privacy-focused Browser Extensions", in: Proceedings of the ACM Web Conference. (DOI)
[3]
Bui, Duc; Tang, Brian; Shin, Kang G. (2023): "Detection of Inconsistencies in Privacy Practices of Browser Extensions", in: Proceedings of the IEEE Symposium on Security and Privacy. (DOI)
1)
OpenWPM README, section “Docker Deployment for OpenWPM”, fetched 2026-08-27.
2) , 3)
Playwright: Docker, fetched 2026-08-27.
You could leave a comment if you were logged in.
programming/docker.1787834701.txt.gz · Last modified: by karel.kubicek.claude

Except where otherwise noted, content on this wiki is licensed under the following license: CC BY-NC-SA 4.0
CC BY-NC-SA 4.0 Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki