Skip to main content

Docker Images

Local development only. Both the Orchestrator and Playwright runner run as Docker images that must be built on your local machine before use.


Why Docker?

The Orchestrator and Playwright runner are isolated in Docker for two reasons:

  1. Isolation — test runs cannot affect the host system or each other.
  2. Version control — multiple Playwright versions can coexist as tagged images.

The images are built locally (not pulled from a registry) so changes to the runner code are reflected immediately after a rebuild.

Build-Time Integration Boundary

Marketplace integration code is never installed while a workflow is running. The Orchestrator build installs the dependencies declared in its lockfile and scans its installed direct production dependencies for package-owned playrunner.integration.orchestrator metadata. It validates those declarations, generates static imports, and bundles the contributions into dist/index.js. Jira and Slack currently declare this surface; host-managed Environment, GitHub, Playwright, and Schedule nodes remain part of the Orchestrator host.

Runtime configuration only selects an executor that is already in that image and passes it provider-scoped credentials, node configuration, templates, logging, cancellation, and other host capabilities. Adding, removing, or upgrading an executable integration requires updating the build inputs, rebuilding the Orchestrator image, and replacing or redeploying the running image.


Building the Images

start-local.sh builds all images automatically. You can also build them manually:

Orchestrator

Use the repo helper from the workspace root. It builds with the repository as the Docker context, then removes the existing local Orchestrator container so a new one will be created from the rebuilt image:

./infra/scripts/rebuild-orchestrator.sh

The equivalent image-only command is:

docker build \
--build-arg BASE_PATH=. \
-f apps/runners/orchestrator/Dockerfile \
-t playrunner-orchestrator \
.

Do not use apps/runners/orchestrator as the build context. The image needs the repository-root context so its local file: package dependencies and static integration-composition generator are available during the build.

Base image: node:24-alpine
Extra packages installed: docker-cli (so the container can run docker commands against the host socket)
Entry point: node dist/index.js

Playwright Runner

The Playwright runner supports multiple Playwright versions via a shared config file and Docker build argument:

# Inspect configured versions
node infra/scripts/playwright-runner-config.mjs json

# Build one configured version explicitly
docker build \
--platform linux/amd64 \
-f ./apps/runners/playwright/Dockerfile.typescript \
--build-arg PLAYWRIGHT_VERSION=v1.59.0-jammy \
-t playrunner-playwright-runner-typescript:latest \
-t playrunner-playwright-runner-typescript:v1.59.0-jammy \
./apps/runners/playwright

docker build \
--platform linux/amd64 \
-f ./apps/runners/playwright/Dockerfile.python \
--build-arg PLAYWRIGHT_VERSION=v1.59.0-jammy \
-t playrunner-playwright-runner-python:latest \
-t playrunner-playwright-runner-python:v1.59.0-jammy \
./apps/runners/playwright

start-local.sh, infra/gcp/scripts/push-runners.sh, and the Playwright settings modal all read from config/playwright-runner-versions.json.

Base image: mcr.microsoft.com/playwright:${PLAYWRIGHT_VERSION}
Entry point: node dist/index.js


Image Tags

TagPlaywright VersionNotes
playrunner-playwright-runner-typescript:latestTag with publishAsLatest: trueAlias for the current latest configured TypeScript runner
playrunner-playwright-runner-typescript:<configured-tag>Matching configured tagOne TypeScript image per entry in config/playwright-runner-versions.json
playrunner-playwright-runner-python:latestTag with publishAsLatest: trueAlias for the current latest configured Python runner
playrunner-playwright-runner-python:<configured-tag>Matching configured tagOne Python image per entry in config/playwright-runner-versions.json
playrunner-orchestratorThe orchestrator image (no version tagging)

The Playwright node in the editor exposes a playwrightVersion config field and a runtime selector. The Orchestrator uses both values to choose the Docker image tag (for example playrunner-playwright-runner-python:v1.59.0-jammy).


How the Orchestrator Prepares Playwright Containers

When the Orchestrator receives a workflow, it scans the whole graph for Playwright nodes and starts their containers in preparation mode before the DAG reaches those nodes. Locally, each prepared runner starts with the equivalent of:

docker run --rm \
-e GCP_PROJECT=local-dev \
-e PUBSUB_EMULATOR_HOST=host.docker.internal:8084 \
-e MY_ENV_VAR=value \ # user-defined env vars from the Environment node
-e PAYLOAD='{"data":{...},...}' \ # full JSON config
playrunner-playwright-runner-typescript:<configured-tag>

The --rm flag ensures the container is deleted after it exits. No Docker volumes are mounted for the Playwright runner — local output archives are uploaded to the API via HTTP before the container exits, while logs, states, runner_control, runner_status, and output events go through Pub/Sub.


Docker Socket Access

The Orchestrator container receives the host Docker socket mounted as a volume:

-v /var/run/docker.sock:/var/run/docker.sock

This is what allows the Orchestrator (running inside Docker) to spawn Playwright containers on the host's Docker daemon. The containers it spawns appear as siblings, not children, in docker ps.


Rebuilding After Code Changes

If you modify Orchestrator source, an integration's manifest or Orchestrator contribution, the provider-agnostic integration resolver, the selected dependencies, or Playwright runner source, rebuild the affected image before the change can take effect:

# Rebuild the Orchestrator, including all generated package contribution imports,
# and remove the currently running local Orchestrator container
./infra/scripts/rebuild-orchestrator.sh

# Rebuild playwright runners from shared config
./start-local.sh

After the Orchestrator helper finishes, reopen the Editor tab. That triggers POST /api/runners/start and starts a fresh container from the new image.

Publishing a package or changing marketplace metadata alone does not change a running Orchestrator. The deployment must use an image rebuilt from the updated dependency, lockfile, and package-owned manifest inputs. Package authors never add their provider to a common source registry.


Publishing to GCP

For the end-to-end infrastructure and GCP integration runbook, see GCP Setup. This section documents the image publishing helper itself.

Running workflows against the GCP execution path requires the Orchestrator and Playwright images to be available in a registry that Cloud Run can pull from. The repo ships a helper that builds and pushes them to Google Artifact Registry, redeploys the Orchestrator Cloud Run service, and clears stale Playwright Cloud Run Jobs so they pick up the new image:

./infra/gcp/scripts/push-runners.sh

When only the Orchestrator or its bundled integration executors changed, use:

./infra/gcp/scripts/push-runners.sh --target orchestrator --yes

Prerequisites

  1. Connect GCP in the Integrations modal. The script reads the GCP project, region, Cloud Run service name, service defaults, and generated image URI templates from the CloudCredential row that the modal writes to Postgres.
  2. Generate Terraform variables and apply Terraform at least once so the required GCP APIs are enabled and the orchestrator and playwright-runner Artifact Registry repositories and shared workflow-events Pub/Sub topic exist in your project. Run ./infra/gcp/scripts/setup-terraform.sh, review infra/gcp/terraform.tfvars, then run terraform -chdir=infra/gcp init, terraform -chdir=infra/gcp plan, and terraform -chdir=infra/gcp apply. Repeat this before publishing if the saved project, region, repository path, or workflow-events topic changed.
  3. Local tooling on PATH: docker, gcloud, and node. apps/api/.env must contain a working DATABASE_URL (the script reuses Prisma from apps/api/node_modules to read the credential).
  4. GCloud authentication: run gcloud auth login before publishing. The push script configures Docker's Artifact Registry credential helper for the target registry host automatically.

Reading the stored settings

If you want to confirm what the script will use before running it:

node infra/gcp/scripts/gcp-settings.mjs json

This only emits the non-secret fields (selectedProject, cloudRunLocation, orchestratorServiceName, orchestratorMinInstanceCount, orchestratorMaxInstanceCount, orchestratorCpuIdle, orchestratorImageUriTemplate, playwrightImageUriTemplate, schedulerServiceAccountEmail). Each value is also available as its own subcommand: project-id, region, orchestrator-service-name, orchestrator-min-instance-count, orchestrator-max-instance-count, orchestrator-cpu-idle, orchestrator-image-uri-template, playwright-image-uri-template, scheduler-service-account-email. Run ./infra/gcp/scripts/push-runners.sh --help for the wrapper script's options.

If these saved values changed, rerun the push script after saving:

./infra/gcp/scripts/push-runners.sh --target both --yes

Run Terraform first when the changed values point at a project, region, Artifact Registry repository, or Pub/Sub topic that does not already exist.

Usage

Interactive (confirmation prompt, then menu for orchestrator / playwright / both):

./infra/gcp/scripts/push-runners.sh

Non-interactive, e.g. for CI:

./infra/gcp/scripts/push-runners.sh --target both --yes

One-off override (push to a project other than the one saved in the modal):

./infra/gcp/scripts/push-runners.sh --project-id my-other-project --target orchestrator --yes

All flags:

FlagEffect
--project-id <id>Override the stored GCP project ID
--region <region>Override the stored Cloud Run region
--orchestrator-service-name <n>Override the stored Cloud Run service name
--orchestrator-min-instances <n>Override the stored Orchestrator minimum instance count; 0 is allowed
--orchestrator-max-instances <n>Override the stored Orchestrator maximum instance count
--orchestrator-cpu-idle <true|false>Override the stored Orchestrator CPU idle policy
--user-id <id>Filter the Postgres lookup when multiple users have GCP credentials
--target orchestrator|playwright|bothSkip the interactive menu
--base-path <path>BASE_PATH build arg for the Orchestrator image (default .)
--yes, -ySkip the confirmation prompt

What it does

  • Docker auth: configures Docker for the target Artifact Registry host with gcloud auth configure-docker.
  • Orchestrator: builds the Orchestrator and the package executors selected as direct production dependencies from the repository context as linux/amd64. The build generates their static imports, tags the image using orchestratorImageUriTemplate (with {projectId} substituted), pushes it, and runs gcloud run deploy <orchestratorServiceName> --image ... --min-instances ... --max-instances ... --cpu-boost --[no-]cpu-throttling to roll out the new image and service settings.
  • Playwright: for both typescript and python, builds every tag in config/playwright-runner-versions.json. The tag flagged publishAsLatest: true is additionally pushed as :latest. Tags use playwrightImageUriTemplate with {projectId}, {runtime}, and {version} substituted.
  • Job cleanup: deletes any existing Cloud Run Jobs named playrunner-* in the configured region so the next workflow execution recreates them with the new image.