Skip to content

Docker Compose

Docker Compose runs the whole stack on one machine: the API, test workers, a scheduler, three headless browsers, Postgres, Redis and the web app behind one reverse proxy. It’s the default way to run Plainwright, on a laptop for evaluation or development and on a single server for a team. Every other deployment target runs the same images, wired differently.

Use this page to install, to look up what a service or port is for, and for everyday commands. To put the stack on the internet with a domain and HTTPS, follow Deploy to a VPS after this page.

  • Docker with Compose v2.24 or newer. Docker Desktop on Windows or macOS, or Docker Engine with the Compose plugin on Linux. Check with docker compose version. The compose file marks .env as optional, which older Compose versions reject.
  • Memory. The stack starts three browser services and two workers. Plan on about 8 GB of RAM and 4 CPU cores for two or three browser tests at once; see Scaling workers for sizing.
  • Free ports on the host: 8080, plus 3000, 8000, 5433, 6379 and 8931 on 127.0.0.1.
  • An AI model. Either a provider key that you add in the app after signing in, or a server-wide Anthropic key in .env. A local Ollama model works too. Every model must support tool calling.
  1. Get the code.

    Terminal window
    git clone https://github.com/SaiBolisetty/Plainwright.git plainwright
    cd plainwright
  2. Create .env from the example file.

    Terminal window
    cp .env.example .env

    Then edit two values. Every other value has a working default; the full list is in Configuration.

    .env
    # Signs sign-ins and encrypts stored API keys. Random, at least 32 characters:
    # python3 -c "import secrets; print(secrets.token_urlsafe(48))"
    SECRET_KEY=replace-with-a-long-random-value
    # Optional: a server-wide fallback model, used until users add their own provider.
    ANTHROPIC_API_KEY=

    If you leave SECRET_KEY empty, the stack still starts, with a publicly known built-in key. The backend then logs a CRITICAL warning and the app shows owners and admins a banner until you rotate the key. That’s fine for a quick look on your own machine; for anything else, set the key now, when there’s nothing to rotate yet.

  3. Build and start everything.

    Terminal window
    docker compose up -d --build

    The first build takes several minutes. On the first start, each browser service also downloads its browser with npm and Playwright, which takes a few more.

  4. Wait for the browser services. Each shows (health: starting) in docker compose ps while it installs, and (healthy) once its MCP server answers on port 8931. The first start is allowed up to 5 minutes; Firefox and WebKit take a little longer than Chromium. To watch progress:

    Terminal window
    docker compose ps
    docker compose logs -f playwright-mcp

    When it’s ready, backend, postgres, redis and the three playwright-mcp* services are (healthy), and the rest are Up. Workers start as soon as the backend is healthy, without waiting for the browsers, so a browser test started earlier ends in error; run it again.

  5. Open http://localhost:8080 and create an account. The guided setup walks you through adding an AI provider, creating a project and running a first test.

Docker Compose services: nginx in front of the frontend and backend; workers take jobs from Redis and drive browsers through playwright-mcpBrowser · CIpeople and pipelinesnginx:8080 · the only entryfrontendReact buildbackendAPI · migrationsworker × 2AI agent · judgeschedulercron runs · stuck-run reaperpostgres · redisdata · job queueplaywright-mcpchromium · firefox · webkit//apienqueueMCPDocker Compose services: nginx in front of the frontend and backend; workers take jobs from Redis and drive browsers through playwright-mcpBrowser · CIpeople and pipelinesnginx:8080 · the only entryfrontendReact buildbackendAPI · migrationsschedulercron · reaperpostgres · redisdata · job queueworker × 2AI agent · judgeplaywright-mcp3 browser engines
nginx sends /api and /screenshots to the backend and everything else to the frontend. Workers take jobs from Redis and drive a browser through the playwright-mcp services.
Service Host port What it does
nginx 8080 The entry point: nginx:alpine with nginx/nginx.conf. Proxies /api/ and /screenshots/ to the backend, unbuffered for live run streams, and everything else to the frontend. Adds security headers and answers /metrics with 404.
frontend 127.0.0.1:3000 The React build from ./frontend, served by its own nginx. You normally reach it through nginx.
backend 127.0.0.1:8000 The FastAPI REST API and sign-in, built from backend/Dockerfile. Runs database migrations on start, serves /health and /metrics, and runs the AI Crawler.
worker — RQ workers on the test_runs queue, built from backend/Dockerfile.worker. Each replica runs one test at a time; WORKER_REPLICAS sets how many (default 2). Starts once the backend is healthy, i.e. migrated.
scheduler — python -m app.services.scheduler on the backend image. Every 60 seconds it starts tests whose cron schedule is due, and marks runs stuck past their time limit as errors. Run exactly one. Starts once the backend is healthy.
playwright-mcp 127.0.0.1:8931 Headless Chromium behind a Playwright MCP server. The default browser for UI tests and the AI Crawler.
playwright-mcp-firefox — Headless Firefox, for runs with browser firefox.
playwright-mcp-webkit — Headless WebKit, for runs with browser webkit.
postgres 127.0.0.1:5433 postgres:15-alpine. All data: users, projects, tests, runs, results. Set the host port with POSTGRES_HOST_PORT.
redis 127.0.0.1:6379 redis:7-alpine. The job queue and live run events. It has no password, so it must stay private.

Ports bound to 127.0.0.1 are reachable only from the machine itself, for debugging and host tools. Only nginx’s 8080 is published on every interface. Services talk to each other by service name, for example http://backend:8000, over two networks:

Network Members Why
default postgres, redis, backend, worker, scheduler, frontend, nginx The data plane.
browser the three playwright-mcp* services, backend, worker The backend (for the AI Crawler) and the workers drive the browsers here.

A page open in the browser under test, or a prompt injected into one, therefore can’t reach Postgres, Redis, the scheduler, the frontend or nginx by name. It can still reach the internet, your private network, and the backend and workers on the browser network; see The browser and your network.

The three playwright-mcp* services use the mcr.microsoft.com/playwright:v1.44.0-jammy image and run the same command with a different --browser. On every start they install the latest @playwright/mcp and the matching browser, then start the server. The flags are required:

Flag Why
--headless No display in the container.
--no-sandbox The container runs as root, which Chromium’s sandbox refuses.
--browser chromium (or firefox, webkit) Uses the browser installed for this exact MCP version. The image’s preinstalled browsers don’t match it.
--isolated A fresh in-memory browser profile for every run, so cookies and logins never leak between runs. State still lasts for the whole run.
--allowed-hosts '*' Lets workers address the server by its compose host name. It’s variadic, so it must stay the last flag, or it swallows the flags after it.

Each browser service gets shm_size: 2gb and mounts ./test-files read-only at /test-files, so tests can upload project files. Its health check passes once the MCP server answers HTTP on port 8931, with a 5-minute start period for the first install.

Volume or folder Mounted in Holds
postgres_data (named volume) postgres The database.
screenshots (named volume) backend, worker, nginx at /app/screenshots Screenshots, visual baselines and diffs, and run evidence, while STORAGE_BACKEND=local.
./test-files (host folder) backend (read-write), worker and the browser services (read-only) at /test-files Files uploaded per project in the app, stored as test-files/<project_id>/<file>.
./nginx/nginx.conf (host file) nginx The proxy configuration.

Back up postgres_data, screenshots, ./test-files and your .env; see Upgrades, migrations and backups.

Task Command
See what’s running and healthy docker compose ps
Follow logs docker compose logs -f backend worker
Apply a changed .env docker compose up -d (recreates the changed containers; restart keeps the old environment)
Update to new code git pull, then docker compose up -d --build
Run more tests in parallel set WORKER_REPLICAS=4 in .env, then docker compose up -d
Check the database schema version docker compose exec backend alembic current
Stop everything, keep data docker compose down
Live reload while developing the backend docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build

Rebuild the backend, worker and scheduler together after changing backend code: they share one code base, and a worker on old code can misread what a new backend wrote. up -d --build does that for you.

The backend and worker containers map host.docker.internal to the host, so a provider with base URL http://host.docker.internal:11434 reaches an Ollama server running on your machine (Docker Desktop does this automatically; the compose file adds it for Linux). Ollama runs on the host, not in Compose. On Linux, make Ollama listen on more than loopback, for example with OLLAMA_HOST=0.0.0.0, or containers can’t reach it. Pick a model with tool calling, such as llama3.1 or qwen2.5.