Back to Blog
Deployment

Docker Browser Automation: Deployment and Scaling Guide

Deploy browser automation in Docker containers with Dockerfile examples, Compose scaling, volume mounts, and production best practices.

Introduction

Docker provides the reproducibility, isolation, and scaling primitives that production BotBrowser deployments need. Each container starts from a known state, runs independently of other containers, and can be replicated across any infrastructure that supports Docker. No more "it works on my machine" problems.

This guide covers the complete Docker deployment workflow: building images, managing profiles and scripts with volume mounts, configuring shared memory, scaling with Docker Compose, and applying production hardening. Whether you are running a single container locally or orchestrating dozens of workers in the cloud, the patterns here apply.

Why Docker Deployment Matters

BotBrowser depends on specific system libraries, a virtual display server, and careful environment configuration. On bare metal, every new server requires the same manual setup: installing dependencies, configuring Xvfb, setting environment variables, and verifying the installation. Docker packages all of this into a single image that deploys identically everywhere.

Docker also provides process isolation. Each container runs its own BotBrowser instance with its own filesystem, network namespace, and resource limits. A memory leak in one container does not affect others. A crashed worker can be automatically restarted by Docker's restart policy without human intervention.

For teams running multiple BotBrowser instances with different profiles and proxies, Docker Compose or container orchestration platforms make it straightforward to define, scale, and manage the entire fleet from a single configuration file.

Technical Background

Chrome and Shared Memory

Chrome uses /dev/shm (shared memory) extensively for inter-process communication between the browser process, GPU process, and renderer processes. Docker's default /dev/shm allocation is 64 MB, which is far too small. Chrome will crash or behave erratically without at least 1-2 GB of shared memory.

The fix is simple: add --shm-size=2g to your docker run command or set shm_size: '2g' in your Docker Compose file.

Display Server in Containers

Just like on a bare-metal server, BotBrowser in Docker needs Xvfb for display initialization. The container must start Xvfb before launching Chrome, and the DISPLAY environment variable must be set.

User Data Directories

Each BotBrowser instance needs its own --user-data-dir. In Docker, this directory lives inside the container by default and is lost when the container stops. For persistent sessions, mount a host volume to preserve browser state between container restarts.

Image Size Considerations

A BotBrowser Docker image with all dependencies is typically 500-800 MB. Using multi-stage builds or minimizing the package list can reduce this. The trade-off is that every missing library is a potential runtime failure.

<svg viewBox="0 0 700 280" xmlns="http://www.w3.org/2000/svg" style={{maxWidth: '100%', height: 'auto'}}> Docker Container Xvfb :10 Virtual Display BotBrowser Chrome + Profile Node.js Automation Script /dev/shm 2 GB Shared Mem Volume: /opt/profiles (host mounted) Volume: /data/sessions (persistent)

Common Approaches and Limitations

Single Monolithic Container

Running everything in one container (Xvfb, BotBrowser, automation script, and monitoring) is the simplest approach but limits scalability. You cannot scale the automation script independently of the browser, and a problem in any component takes down the entire container.

Container-per-Browser

Running one container per BotBrowser instance provides the best isolation. Each container has its own profile, proxy, and user data directory. The overhead is higher (each container includes a full set of system libraries), but the isolation makes debugging and scaling straightforward.

Sidecar Pattern

Some deployments use a sidecar container running Xvfb that multiple browser containers share. This reduces resource usage but adds networking complexity and creates a single point of failure.

Building from Chromium Base Images

Using Google's official Chromium Docker images as a base can reduce setup effort, but these images may not include all the libraries BotBrowser needs. Building from Ubuntu provides more control over the dependency list.

BotBrowser's Approach

BotBrowser's cross-platform profile system is particularly well-suited for Docker deployments. The same profile produces the same fingerprint regardless of the container's base image, kernel version, or host machine. This eliminates a common source of inconsistency in containerized browser deployments.

The containerization strategy recommended here uses Ubuntu 22.04 as the base, installs all required dependencies, copies the BotBrowser binary and profiles into the image, and starts Xvfb before the automation script. This approach is tested, reliable, and produces consistent results across cloud providers.

Configuration and Usage

Base Dockerfile

FROM ubuntu:22.04

# Install system dependencies
RUN apt-get update && apt-get install -y \
    wget ca-certificates fonts-liberation \
    libasound2 libatk-bridge2.0-0 libatk1.0-0 \
    libcups2 libdbus-1-3 libdrm2 libgbm1 \
    libgtk-3-0 libnspr4 libnss3 \
    libxcomposite1 libxdamage1 libxrandr2 \
    xdg-utils xvfb curl \
    && rm -rf /var/lib/apt/lists/*

# Install Node.js
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
    && apt-get install -y nodejs \
    && rm -rf /var/lib/apt/lists/*

# Set display environment
ENV DISPLAY=:10.0

# Copy BotBrowser binary
COPY botbrowser/ /opt/botbrowser/
RUN chmod +x /opt/botbrowser/chrome

# Copy profiles
COPY profiles/ /opt/profiles/

# Copy automation scripts
WORKDIR /opt/app
COPY package.json package-lock.json ./
RUN npm ci --production
COPY scripts/ ./scripts/

# Health check
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
  CMD pgrep -x Xvfb && pgrep -f "chrome" || exit 1

# Start Xvfb and run the automation script
CMD Xvfb :10 -screen 0 1920x1080x24 -ac & \
    sleep 1 && \
    node scripts/main.js

Build and Run

docker build -t botbrowser-worker .
docker run --rm --shm-size=2g botbrowser-worker

The --shm-size=2g flag is required. Without it, Chrome crashes or behaves unpredictably due to insufficient shared memory.

Automation Script

// scripts/main.js
const { chromium } = require('playwright-core');

const PROFILE = process.env.PROFILE || '/opt/profiles/profile.enc';
const PROXY = process.env.PROXY || '';

async function main() {
  const args = [
    '--disable-setuid-sandbox',
    `--bot-profile=${PROFILE}`,
  ];

  if (PROXY) {
    args.push(`--proxy-server=${PROXY}`);
  }

  const browser = await chromium.launch({
    executablePath: '/opt/botbrowser/chrome',
    args: args,
    headless: true,
  });

  try {
    const context = await browser.newContext();
    const page = await context.newPage();
    await page.goto('https://example.com');
    console.log('Title:', await page.title());

    // Your automation logic here

  } finally {
    await browser.close();
  }
}

// Handle graceful shutdown
process.on('SIGTERM', async () => {
  console.log('Received SIGTERM, shutting down...');
  process.exit(0);
});

process.on('SIGINT', async () => {
  console.log('Received SIGINT, shutting down...');
  process.exit(0);
});

main().catch(console.error);

Docker Compose for Multiple Workers

version: '3.8'

services:
  worker-us:
    build: .
    shm_size: '2g'
    environment:
      - PROFILE=/opt/profiles/windows-chrome-131.enc
      - PROXY=socks5://user:pass@us-proxy.example.com:1080
    volumes:
      - ./profiles:/opt/profiles:ro
      - session-us:/data/session
    deploy:
      resources:
        limits:
          memory: 4g
          cpus: '2'
    restart: unless-stopped

  worker-eu:
    build: .
    shm_size: '2g'
    environment:
      - PROFILE=/opt/profiles/windows-chrome-132.enc
      - PROXY=socks5://user:pass@eu-proxy.example.com:1080
    volumes:
      - ./profiles:/opt/profiles:ro
      - session-eu:/data/session
    deploy:
      resources:
        limits:
          memory: 4g
          cpus: '2'
    restart: unless-stopped

volumes:
  session-us:
  session-eu:

Start the fleet:

docker compose up -d
docker compose logs -f

Scale a specific worker:

docker compose up -d --scale worker-us=3

Volume Mounts

Mount profiles as read-only volumes so you can update them without rebuilding the image:

docker run --rm --shm-size=2g \
  -v /host/profiles:/opt/profiles:ro \
  -v /host/sessions/worker-1:/data/session \
  -e PROFILE=/opt/profiles/profile.enc \
  -e PROXY=socks5://user:pass@proxy:1080 \
  botbrowser-worker

Environment Variables

Use environment variables for per-container configuration:

VariableDescriptionExample
PROFILEPath to profile file inside container/opt/profiles/win-chrome-131.enc
PROXYProxy URL with credentialssocks5://user:pass@proxy:1080
DISPLAYX display (set in Dockerfile):10.0

Entrypoint Script

For more complex startup logic, use an entrypoint script:

#!/bin/bash
# entrypoint.sh

# Start Xvfb
Xvfb :10 -screen 0 1920x1080x24 -ac &
XVFB_PID=$!

# Wait for Xvfb to be ready
for i in $(seq 1 10); do
  if xdpyinfo -display :10 > /dev/null 2>&1; then
    break
  fi
  sleep 0.5
done

# Run the main script
exec node /opt/app/scripts/main.js

Update the Dockerfile:

COPY entrypoint.sh /opt/app/
RUN chmod +x /opt/app/entrypoint.sh
ENTRYPOINT ["/opt/app/entrypoint.sh"]

Verification

Verify your Docker setup:

# Build the image
docker build -t botbrowser-worker .

# Run with interactive shell for debugging
docker run --rm --shm-size=2g -it botbrowser-worker bash

# Inside the container:
Xvfb :10 -screen 0 1920x1080x24 &
export DISPLAY=:10.0
/opt/botbrowser/chrome --version
ldd /opt/botbrowser/chrome | grep "not found"

No "not found" lines should appear. Run a test navigation:

docker run --rm --shm-size=2g botbrowser-worker

Check the output for the expected page title and no error messages.

Best Practices

Always set --shm-size=2g or higher. This is the most common Docker + Chrome issue. The default 64 MB is insufficient.

Use --disable-setuid-sandbox in containers. Docker containers typically run without the kernel capabilities Chrome's sandbox requires.

Mount profiles as read-only volumes. This separates profile updates from image builds and prevents accidental modification.

Set resource limits. Use Docker's --memory and --cpus flags to prevent any container from consuming all host resources.

Handle SIGTERM for graceful shutdown. Docker sends SIGTERM when stopping containers. Your script should close the browser and clean up resources.

Use .dockerignore. Exclude node_modules, .git, and local development files from the build context to reduce image size and build time.

Tag images with version numbers. Use botbrowser-worker:1.2.0 instead of :latest for reproducible deployments.

Frequently Asked Questions

Why does Chrome crash with "out of memory" in Docker?

The most common cause is insufficient /dev/shm size. Add --shm-size=2g to your docker run command. If the container itself is memory-limited, increase the memory limit with --memory=4g.

Can I use Alpine Linux instead of Ubuntu?

Chrome's dependencies are glibc-based. Alpine uses musl libc, which is incompatible. Stick with Debian or Ubuntu-based images.

How do I update profiles without rebuilding the image?

Mount your profiles directory as a volume: -v /host/profiles:/opt/profiles:ro. Update the files on the host and restart the container.

Can I expose the remote debugging port?

Yes. Use -p 9222:9222 and add --remote-debugging-port=9222 to Chrome's launch flags. Be cautious with this in production since the debugging port provides full control over the browser.

How do I view container logs?

Use docker logs <container-id> or docker compose logs <service-name>. Your script's console.log output appears here.

What base image should I use?

Ubuntu 22.04 LTS is the most tested and reliable choice. Ubuntu 24.04 works with adjusted package names. Debian 12 (Bookworm) is also a solid option.

Can I run multiple BotBrowser instances in one container?

Yes, but separate containers provide better isolation and resource management. If you do run multiple instances in one container, ensure each has its own --user-data-dir and --remote-debugging-port.

How do I handle Chrome zombie processes?

Add --init to your docker run command to use Docker's init process, which reaps orphaned child processes. Alternatively, use dumb-init or tini in your Dockerfile.

Summary

Docker provides the most reliable and repeatable way to deploy BotBrowser in production. Build your image once with all dependencies, use volume mounts for profiles and persistent data, and scale with Docker Compose or orchestration tools. The key configuration requirement is adequate shared memory (--shm-size=2g).

For bare-metal setup, see Headless Server Setup. For performance tuning, see Optimizing BotBrowser Performance. For CLI flag reference, see CLI Recipes.

#docker#deployment#server#automation#devops