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.
Requirements
Section titled “Requirements”- 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.envas 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, plus3000,8000,5433,6379and8931on127.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.
Start the stack
Section titled “Start the stack”-
Get the code.
Terminal window git clone https://github.com/SaiBolisetty/Plainwright.git plainwrightcd plainwright -
Create
.envfrom the example file.Terminal window cp .env.example .envTerminal window Copy-Item .env.example .envThen 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_KEYempty, the stack still starts, with a publicly known built-in key. The backend then logs aCRITICALwarning 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. -
Build and start everything.
Terminal window docker compose up -d --buildThe 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.
-
Wait for the browser services. Each shows
(health: starting)indocker compose pswhile 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 psdocker compose logs -f playwright-mcpWhen it’s ready,
backend,postgres,redisand the threeplaywright-mcp*services are(healthy), and the rest areUp. 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. -
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.
How the services fit together
Section titled “How the services fit together”/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.Services
Section titled “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 browser services
Section titled “The browser services”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.
Volumes and folders
Section titled “Volumes and folders”| 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.
Everyday commands
Section titled “Everyday commands”| 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.
Using a local Ollama model
Section titled “Using a local Ollama model”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.