Why a browser in CI or Docker looks nothing like a real machine
A browser started inside a CI job or a bare Docker image runs on hardware and a filesystem that do not resemble anyone's laptop, and every default in that environment leaks something you would never notice locally. None of it is exotic. It is five ordinary defaults stacked on top of each other.
No GPU, so WebGL names the fallback
Sites read the WebGL renderer and vendor strings to see what graphics hardware is drawing the page. A container with no GPU has nothing to report, so Chromium falls back to a software rasterizer, and that rasterizer identifies itself as exactly what it is: SwiftShader or, on Linux, Mesa's llvmpipe, rather than a real GPU model. A real desktop or laptop almost never returns a software renderer string. There is no cheap fix here; passing a real or virtual GPU through is possible but real engineering cost, and most teams should treat this as a hard signal the run will not pass as a consumer machine, and plan around it instead of fighting it.
A bare image has almost no fonts
Slim base images ship a small fraction of the fonts a real desktop has installed. Font enumeration, whether by direct API or by measuring rendered text width against a candidate list, returns a short list where a real Windows or macOS machine returns dozens to hundreds of names. This one is fixable: install font packages explicitly, for example the common Debian packages for core web fonts and a broader Noto set, rather than relying on the base image's default. It will not reproduce one specific machine's exact font manifest, but a deliberately populated list beats a bare one.
No audio device
Audio fingerprinting reads the output of an AudioContext graph, oscillator and compressor included, and that output depends on a real audio backend. A container commonly has no ALSA or PulseAudio device, so the browser falls back to a stub, throws where a real machine would not, or returns a flat result unlike any real sound card. A virtual or null audio device closes the "no device present" gap, though it cannot manufacture a real card's analog quirks. Treat this signal as unrealistic unless you have deliberately addressed it.
A datacenter IP, and everything that has to agree with it
The job's host sits on cloud provider address space, trivial to separate from residential or mobile ranges and scored accordingly. Worse than the IP alone is what it forces out of step: the container clock reporting UTC or whatever the base image sets, while the IP geolocates elsewhere, a contradiction cheaper to catch than the datacenter range itself. Binding a proxy and deriving timezone, locale and geolocation from its exit point closes that gap. See residential versus datacenter proxies for what the IP class costs on its own.
A clean profile every run
Containers are rebuilt or discarded between runs by design, so cookies, local storage and profile history vanish with them. A brand-new device for a returning account, every run, is itself an unusual pattern. The fix is ordinary: mount the profile directory as a volume outside the container's lifecycle, so a restart reuses the same on-disk profile instead of starting cold. See backing up and migrating a profile for treating that directory as the thing that survives.
Headless mode is not the same thing as Xvfb
A dedicated headless mode runs the browser with no display server at all. Xvfb runs an actual virtual X display and lets the ordinary headed build render into it, so the browser draws to a real window on a real, if virtual, display instead of a stripped-down mode. On Linux, Xvfb with the full headed binary is generally the closer match to a real desktop session. Headless versus headful detection covers what differs between the two.
Where none of this is worth doing
If the job is a test suite asserting your own app's behavior, or a scrape against a target that does not fingerprint aggressively, skip all of the above. A default CI container is close to the worst environment for looking like a real machine, but that only costs you something if the other end actually scores you as a device, and most CI jobs never are. Spend the engineering time on the tests, and reserve the hardening for runs that touch real accounts; see scraping versus account automation for where that line sits. For account-facing work, a persistent machine that keeps its GPU, fonts, audio stack and disk between runs is often a smaller lift than tuning a container to fake all five.
What this SDK does and does not fix here
Binding a proxy to a profile makes timezone, locale and geolocation follow the exit IP automatically, and profiles export to a portable archive, covering the datacenter-IP and clean-profile problems above. It does not give a container a GPU, fonts or an audio device; those are still yours to install. One gap worth knowing: AntiBrow's headless option is a no-op on macOS, and Linux still needs Xvfb for the full headed build.
Check it yourself
Run your CI image, load a page that reports WebGL renderer, font count and an audio fingerprint, and compare those numbers to the same page on your own laptop. The gap will be obvious, and it tells you which fixes above are worth your time.
Try it on the free tier.
Unlimited local profiles, no credit card. Check it against the detectors yourself.