Run Vibium with Docker Compose
Run Vibium with Docker Compose: orchestrate a headless test service, an app-under-test, mounted artifact volumes, healthchecks, and parallel workers.
To run Vibium with Docker Compose, define a runner service that builds a Vibium image, mount an artifacts volume for screenshots and traces, launch Chrome with headless=True, and add your app-under-test as a second service gated by a healthcheck. Docker Compose is the right tool when a single Dockerfile is not enough — when you need to boot your web app, maybe a database, and the Vibium runner together as one reproducible stack. Vibium ships as a single Go binary that auto-downloads its own Chrome for Testing, so no service ever needs a system Chrome or a matching driver; you only add Chrome's shared libraries to the runner image. Compose gives you a shared network so Vibium reaches the app by service name, depends_on with condition: service_healthy so tests start only after the app is up, a shm_size setting to stop Chrome crashing, and --scale to fan tests across parallel workers. The result is docker compose up bringing your whole test environment to life with one command.
What does the Vibium Docker Compose pipeline look like?
The flow from source to a green run is short: build the Vibium image, start the app-under-test, wait for its healthcheck, run the headless suite, then collect artifacts from a mounted volume. Each stage maps to one part of the docker-compose.yml below.
Compose orchestrates these stages for you: it builds images, starts services in dependency order, blocks the runner until the app reports healthy, then tears everything down when the run exits. You describe the desired state once and Compose reconciles it — no shell script juggling container IDs.
Why use Docker Compose instead of a single Vibium Dockerfile?
Docker Compose orchestrates multiple services as one unit, whereas a single Dockerfile only builds one image. If all you need is to package one headless script, a plain Dockerfile is simpler — that path is covered in how to run Vibium in Docker. Compose earns its place the moment a second moving part appears.
The concrete reasons to reach for Compose:
- You test a real app, not
example.com. Boot your web app (and its database, cache, or mock APIs) as sibling services so tests run against the actual stack. - Startup order matters.
depends_onwith a healthcheck makes Vibium wait until the app is genuinely serving, not just until its container exists. - Networking is automatic. Every service shares a network and is reachable by name — no fragile
host.docker.internalor published-port gymnastics. - Parallelism is one flag.
--scalespins up N isolated runner replicas, each with its own Chrome. - The environment is reproducible. One
docker-compose.ymlin the repo means every teammate and every CI runner gets the identical stack.
If Vibium itself is new to you, skim what is Vibium first for the mental model, then come back — the Compose parts assume you know that find() auto-waits and launch() takes a headless flag.
What does a minimal Vibium Docker Compose setup look like?
A minimal setup is two files: a Dockerfile for the runner image and a docker-compose.yml that wires the runner to an app service. Start with the runner image — it is a slim base plus Chrome's shared libraries plus a pre-fetched browser:
# Dockerfile
FROM python:3.12-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
libnss3 libatk-bridge2.0-0 libgbm1 libasound2 \
libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 \
libxrandr2 libdrm2 libpango-1.0-0 libcairo2 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN pip install --no-cache-dir vibium==26.2 pytest && vibium install
COPY . /app
CMD ["pytest", "-v"]There is no system Chrome line — Vibium downloads Chrome for Testing itself, and vibium install bakes it into an image layer so the container never re-fetches the browser on start. Pinning vibium==26.2 keeps a rebuild from silently pulling a newer Chrome; see pin the Chrome version.
Now the Compose file that turns that image into an orchestrated stack:
# docker-compose.yml
services:
web:
image: my-org/my-web-app:latest
ports:
- "3000:3000"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 5s
timeout: 3s
retries: 10
tests:
build: .
shm_size: "1gb"
environment:
HEADLESS: "true"
BASE_URL: "http://web:3000"
volumes:
- ./artifacts:/app/artifacts
depends_on:
web:
condition: service_healthyBring the whole stack up with one command:
docker compose up --build --abort-on-container-exit --exit-code-from tests--abort-on-container-exit stops the web service as soon as tests finishes, and --exit-code-from tests makes Compose return the test process's exit code — so a failing suite fails your CI job. Everything below is a closer look at the pieces that make this reliable.
How does the Vibium container reach the app service?
The Vibium container reaches the app by its service name, not localhost. Compose places every service on a shared network and registers each service name as a DNS hostname, so the tests container resolves web to the app container's address.
This trips up almost everyone the first time. Inside the tests container, localhost is the test container itself — the app is not there. The app lives in the web container, reachable at http://web:3000. That is why the Compose file passes BASE_URL: "http://web:3000" and the script reads it:
import os
from vibium import browser_sync as browser
base_url = os.environ["BASE_URL"] # http://web:3000 in Compose
headless = os.getenv("HEADLESS", "false") == "true"
vibe = browser.launch(headless=headless)
try:
vibe.go(f"{base_url}/login")
vibe.find("#email").type("jane@acme.com")
vibe.find("#password").type("secret")
vibe.find("button[type=submit]").click()
result = vibe.check("the dashboard is visible")
assert result.passed, result.reason
finally:
vibe.quit()Reading BASE_URL from the environment means the identical script points at http://web:3000 under Compose and http://localhost:3000 on your laptop — one code path, no branching. The published ports: ["3000:3000"] on web is only for your browser to reach the app from the host; container-to-container traffic uses the service name and does not need a published port at all.
Why do I need shm_size for Chrome in Docker Compose?
Chrome writes shared memory to /dev/shm, and Docker's default /dev/shm is only 64 MB, which is too small for heavy pages — Chrome tabs crash with obscure errors. Setting shm_size: "1gb" on the Vibium service fixes the single most common container failure for headless Chrome.
You have two equivalent options. The simple one is shm_size:
tests:
build: .
shm_size: "1gb"The alternative mounts /dev/shm as a tmpfs, which some teams prefer for finer control:
tests:
build: .
tmpfs:
- /dev/shm:size=1gEither way, the symptom you are preventing is a page that renders fine on your laptop but throws a tab crash or a blank result inside the container. If you hit exactly that, this setting is almost always the cure — see headless Chrome crashing on Linux for the deeper diagnosis. Vibium's Chrome for Testing is a normal Chrome build, so it has the same /dev/shm appetite as any other headless Chrome.
How do I wait for the app with depends_on and healthchecks?
Use depends_on with condition: service_healthy so Compose starts the Vibium runner only after the app's healthcheck passes. Plain depends_on waits for the container to start, which is not the same as the app being ready to serve — a web server can take seconds to boot after its process launches.
The healthcheck lives on the app service and polls an endpoint until it succeeds:
web:
image: my-org/my-web-app:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 5s
timeout: 3s
retries: 10
start_period: 10sHere localhost is correct because the healthcheck runs inside the web container, testing itself. start_period gives the app a grace window before failures count against it, and retries sets how many polls it gets.
Even with a healthcheck gating startup, keep your Vibium code free of manual sleeps. Vibium's find() auto-waits for actionability — it polls until the element is present, visible, and interactable — so once the page loads, you never add time.sleep() to "let the button appear". The healthcheck handles service readiness; Vibium handles element readiness. Layering both gives you a stack that is stable without a single hardcoded delay.
How do I get screenshots and traces out of a Compose run?
Mount a host directory into the Vibium service and write artifacts to that path — after the run, the files are on your host. Containers are ephemeral, so anything written only to the container's filesystem vanishes when it exits; a bind-mounted volume is the bridge back to your machine.
The Compose file already mounts ./artifacts:/app/artifacts. Point your capture code at /app/artifacts:
import os
from vibium import browser_sync as browser
ART = "/app/artifacts"
os.makedirs(ART, exist_ok=True)
vibe = browser.launch(headless=True)
try:
vibe.go(os.environ["BASE_URL"])
# ... interactions ...
except Exception:
png = vibe.screenshot(full_page=True) # PNG bytes
with open(f"{ART}/failure.png", "wb") as f:
f.write(png)
raise
finally:
vibe.quit()After docker compose up exits, ./artifacts/failure.png is sitting on your host, ready to open or upload. screenshot() returns raw PNG bytes, so saving it is one write; see the screenshot command for full_page and clipping options. For richer post-mortems, record a Vibium trace to the same folder and scrub it frame by frame in the Trace viewer. In CI, add an upload step that reads from ./artifacts and the whole loop closes.
How do I run Vibium tests in parallel with Docker Compose?
Run parallel workers by scaling the runner service — docker compose up --scale tests=4 starts four isolated tests containers, each with its own Chrome. Because every replica is a separate container, workers never share browser state, cookies, or /dev/shm; isolation is total by construction.
The pattern that scales cleanly is sharding: split your suite into N slices and give each worker one slice via an index. Pass the total and the index through the environment, and derive the index from Compose's replica ordinal:
tests:
build: .
shm_size: "1gb"
environment:
HEADLESS: "true"
BASE_URL: "http://web:3000"
SHARD_TOTAL: "4"
depends_on:
web:
condition: service_healthyWith pytest, a plugin like pytest-xdist parallelizes within one container (pytest -n auto), while --scale parallelizes across containers — you can combine both, but start with one axis. Each replica getting its own Chrome means a crash in one worker cannot corrupt another. For the framework-level detail on sharding and worker isolation, read how to parallelize Vibium tests, and for structuring the suite so it shards cleanly, how to structure a Vibium test suite.
A quick decision on which parallelism axis to reach for:
| Approach | How | Best for | Isolation |
|---|---|---|---|
In-container (pytest -n) | pytest-xdist inside one service | Fast local runs, moderate suites | Process-level, shared image |
Across-container (--scale) | docker compose up --scale tests=N | CI fan-out, heavy suites | Full container isolation |
| Multiple named services | tests-shard-1, tests-shard-2, ... | Different shard configs per worker | Full, explicit per shard |
Docker Compose vs a single Dockerfile vs bare CI: which fits?
All three run Vibium headless; they differ in how much orchestration you need. This table is honest about when the extra Compose machinery pays off and when it is overkill.
| Consideration | Single Dockerfile | Docker Compose | Bare CI (no container) |
|---|---|---|---|
| Services managed | One image | Many services as a stack | None (host tools) |
| App-under-test | Point at an external URL | Boot it as a sibling service | Point at an external URL |
| Startup gating | Manual | depends_on + healthcheck | Manual |
| Networking to app | Published port / external | Service-name DNS, automatic | Host network |
| Parallel workers | Run N containers by hand | --scale, one flag | Runner-native matrix |
| Reproducibility | High | Highest (whole env in one file) | Depends on runner setup |
| Setup weight | Low | Medium | Lowest |
When to choose Docker Compose: you test against an app you also build, need startup ordering, want service-name networking, or want a single file that reproduces the full environment for every teammate and CI job. When a single Dockerfile is enough: you package one headless script that hits an external URL — start with run Vibium in Docker. When bare CI wins: your suite is small, hits a deployed staging URL, and you would rather use the runner's native steps — see running Vibium in CI/CD with GitHub Actions and, for a plain server without containers, run Vibium headless on a server.
Be fair about the trade: Compose adds a layer of concepts (services, networks, healthchecks) that a one-off script does not need. Reach for it when you have a genuine multi-service test, not to containerize a five-line scraper.
How do I run the Compose stack in CI/CD?
In CI, the same docker compose up command runs your whole stack on the runner — most CI providers ship Docker, so there is nothing extra to install. The key flags are --build to rebuild the image, --abort-on-container-exit to stop siblings when tests finish, and --exit-code-from tests so the job's pass/fail follows the test result.
A minimal GitHub Actions job:
name: vibium-compose
on: [push, pull_request]
jobs:
browser-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Vibium suite via Compose
run: >
docker compose up --build
--abort-on-container-exit
--exit-code-from tests
- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: vibium-artifacts
path: artifacts/if: always() uploads screenshots and traces even when the suite fails — which is exactly when you want them. Because the whole environment is described in docker-compose.yml, the run on the CI runner is byte-for-byte the run on your laptop: same base image, same Chrome for Testing, same healthcheck gating. That reproducibility is the payoff for the extra Compose layer. For the non-Compose GitHub Actions recipe (install Vibium directly on the runner), see running Vibium in CI/CD with GitHub Actions.
Can I mount local test code for a fast dev loop?
Yes — bind-mount your test directory into the runner so you edit on the host and re-run without rebuilding the image. During development this turns a slow build-run cycle into an instant one. Add the mount and override the command to keep the container alive:
tests:
build: .
shm_size: "1gb"
environment:
HEADLESS: "true"
BASE_URL: "http://web:3000"
volumes:
- ./tests:/app/tests # live-edit test code
- ./artifacts:/app/artifacts # pull artifacts out
depends_on:
web:
condition: service_healthyNow run a one-off suite against the already-running stack without rebuilding:
docker compose run --rm tests pytest -v tests/test_login.pydocker compose run executes a single command in a fresh runner container that still joins the shared network, so http://web:3000 resolves exactly as before. The bind-mounted ./tests means the container sees your latest edits immediately. Keep the image build for CI (where reproducibility matters) and use the bind mount locally (where iteration speed matters) — the same Compose file supports both.
What are the common Vibium Docker Compose gotchas?
Most Compose problems are environment or wiring, not Vibium. This table maps the symptom to the fix so you can unstick fast.
| Symptom | Likely cause | Fix |
|---|---|---|
| Chrome tab crashes on heavy pages | Default 64 MB /dev/shm | Set shm_size: "1gb" on the service |
ECONNREFUSED hitting the app | Using localhost from the test container | Navigate to http://<service-name>:<port> |
| Tests start before the app is ready | Plain depends_on | Add a healthcheck + condition: service_healthy |
| Screenshots vanish after the run | Writing to container-only path | Bind-mount a host volume and write there |
| CI job passes despite failing tests | Exit code not propagated | Add --exit-code-from tests |
| Missing-library error at launch | Slim base lacks Chrome's libs | Add libnss3, libgbm1, libasound2, etc. |
| Slow cold start every run | Chrome downloaded at runtime | vibium install at build time to bake it in |
A missing-library error at launch is worth calling out: the fix is always to add the library to the runner image, never to install a different browser — Vibium manages Chrome itself. For the architecture behind that single-binary, self-downloading design, see how Vibium works.
How does this pair with AI agents and Vibium MCP?
The same containerized Chrome that runs your scripted suite can also be driven by an LLM through Vibium's built-in MCP server. Vibium exposes an MCP server so an agent in Claude Code can issue the exact find/click/check commands your Compose-run tests use — identical code paths, one engine.
The practical pattern inside a Compose stack: keep your stable, high-value journeys as deterministic scripts that run headless in the tests service, and use check()/do() (or a full agent over MCP) for exploratory or fast-changing flows. Both share the same browser engine and can run against the same web service. To wire an agent to Vibium, see Vibium MCP in Claude Code.
Next steps
- How to run Vibium in Docker — the single-image foundation this builds on.
- Running Vibium in CI/CD with GitHub Actions — the non-Compose CI recipe.
- How to parallelize Vibium tests — sharding and worker isolation in depth.
- How to structure a Vibium test suite — organize a suite that shards cleanly.
- The screenshot command — capture options for failure artifacts.
- How Vibium works — the single Go binary and Chrome for Testing.
- Course and Roadmap — go from basics to production-grade containers.
Frequently asked questions
How do I run Vibium with Docker Compose?
Define a service that builds a Vibium image, mount an artifacts volume, and launch with headless=True. Add your app-under-test as a second service and use depends_on with a healthcheck so Vibium waits until the app is ready. Then run docker compose up --build to bring the stack up and run the tests.
Why use Docker Compose instead of a single Vibium Dockerfile?
A single Dockerfile builds one image; Docker Compose orchestrates several services together. With Compose you can boot your app-under-test, a database, and the Vibium runner as one stack, wire them on a shared network, gate startup with healthchecks, and scale workers — all from one docker-compose.yml file.
Do I need to set shm_size for Vibium in Docker Compose?
Often yes. Chrome uses /dev/shm for shared memory and the Docker default of 64MB can cause tab crashes on heavy pages. In your Compose service set shm_size: '1gb' (or mount /dev/shm as a tmpfs). This is the single most common fix for headless Chrome crashing inside containers.
How does the Vibium container reach my app service in Docker Compose?
Compose puts every service on a shared network and registers each by its service name as a DNS hostname. If your app service is named web on port 3000, the Vibium container navigates to http://web:3000 — not localhost, because localhost inside the container is the container itself, not the app.
How do I get screenshots and traces out of a Compose test run?
Mount a host volume into the Vibium service, for example ./artifacts:/app/artifacts, and write screenshots and traces to that path in your script. After docker compose up exits, the PNGs and trace files are on your host in ./artifacts, ready to upload as CI artifacts or inspect locally.
How do I run Vibium tests in parallel with Docker Compose?
Split your suite into shards and run the runner service with docker compose up --scale tests=4, or define multiple runner services each handling one shard. Each replica is its own isolated container with its own Chrome, so workers never share browser state. Pair this with a shard index environment variable per worker.
Vibium is created by Jason Huggins. This is an independent tutorial — see the official Vibium site and GitHub repo for canonical docs.
Related guides
Vibium Best Practices: The Complete Guide
Vibium best practices for reliable browser automation: semantic locators, actionability waits, page objects, isolation, CI, and AI checks.
13 min read→Best PracticesA Complete Vibium CI/CD Pipeline
Build a complete Vibium CI/CD pipeline: install, headless run, parallel shards, artifact capture, and quality gates that block bad merges on every push.
12 min read→Best PracticesData-Driven Testing with Vibium
Data-driven testing with Vibium: feed one browser test many rows from arrays, CSV, or JSON, loop over cases, and keep the automation logic in one place.
15 min read→Best PracticesRun Vibium Tests on Kubernetes
Run Vibium tests on Kubernetes: build a headless image, ship it as a Job, scale parallel Pods, and pull screenshots and traces as artifacts.
15 min read→