skip to content

How do you run ZAP's windowed session in a container with no display, and what still differs?

level: seniorimportance: nice to knowfreq 28%

answer

  1. two different tests, not one
  2. a display is not a view
  3. Xvfb goes in front of the program
  4. the shipped launcher forwards the exit code

basics

~20 s

Put a virtual display in front of it: the shipped zap-x.sh starts Xvfb, runs zap.sh with your arguments and forwards the exit code. What still differs is that the program branches on whether a view is attached.

solid answer

~40 s

With no lifetime switch the program picks `GuiBootstrap`, which asks the runtime whether the graphics environment is headless and, if so, logs a fatal message telling you to run inline or in daemon mode and returns a failure. The shipped `zap-x.sh` fixes that by bringing up an `Xvfb` virtual display first, then running `zap.sh` with whatever arguments you passed, killing the display afterwards and propagating the exit code. `zap-webswing.sh` runs the same Swing desktop through Webswing so it is reachable over HTTP. Both ship in the default images and neither is in the small one. The distinction that survives is `hasView()`: a virtual display does not attach a view, so behaviour gated on the view still only fires when you are actually running the windowed session.

code

bash · 9 lines
bash
# no lifetime switch on a container with no display:
# logs "ZAP GUI is not supported on a headless environment." and fails
zap.sh

# zap-x.sh starts Xvfb first, runs zap.sh with your args, forwards the code
zap-x.sh -newsession /zap/wrk/demo.session

# a virtual display does NOT attach a view - this is still headless
zap-x.sh -cmd -autorun /zap/wrk/plan.yaml

go deeper

for a junior

Remember that with no lifetime switch the program wants a real display and gives up without one, and that the images ship a small script that provides a virtual one before launching it.

for a middle

Explain the two layers: whether the graphics environment is headless decides if the window can start, and whether a view is attached decides how the program behaves. They are not the same test and they do not move together.

for a senior

Use it to diagnose. When a container dies at start-up, read the fatal line before anything else; when behaviour differs between a workstation and a runner, look for the view check rather than for configuration drift.

for a principal

Decide whether a human ever drives this tool inside your infrastructure at all. Serving a desktop from a container is a supported path with real cost, and leaving it possible but unowned is how a debugging convenience becomes a standing exposure.

## With no switch at all, the program wants a display The GUI flag is only cleared by a switch, so `zap.sh` with no lifetime argument selects `GuiBootstrap`. The first thing that bootstrap does after logging its start-up line is ask the Java runtime whether the graphics environment is headless. If it is, it logs and prints a fatal message — "ZAP GUI is not supported on a headless environment", with a pointer at the help argument — and returns a failure code. In a container with no display that is the entire run: the process is gone in seconds, and the pipeline sees a start-up failure rather than a scan. That is the problem the shipped launcher scripts exist to solve, and they solve it in two different ways. ## `zap-x.sh`: a virtual display in front of the real program The script exports a display name, starts `Xvfb` on it if a lock file is not already there, and then runs `/zap/zap.sh` with **whatever arguments it was given**. When the program returns it captures the exit code, kills the virtual display, removes the lock and exits with the captured code. Two properties are worth noticing: - it is a **wrapper around the whole command line**, not a mode of its own, so it works the same whether you hand it the windowed session or a headless lifetime; - it **preserves the exit code**, so putting it in front of a one-shot run does not cost you the verdict. ## Webswing: the desktop over HTTP The second script is heavier. It starts a Webswing server against a virtual display and serves the real Swing desktop to a browser, so a person can drive the windowed session inside a container they have no screen on. The default images carry everything it needs — the X server, a window manager, the VNC server, the Webswing distribution itself. The program notices: when it detects its container it also notices a Webswing home directory and appends that fact to the container name it records about itself. Neither script is present in the small published image, which has no X server and no interpreter, so "run the desktop in a container" is a property of the default images specifically. ## `hasView()` is the branch that actually matters Here is the part that catches people. A virtual display makes the *graphics environment* non-headless, which is what lets `GuiBootstrap` proceed. It does not attach a view. The program's own behaviour is gated on `hasView()` — whether the view singleton was initialised — and the headless lifetimes deliberately never initialise it; the daemon bootstrap goes further and marks the view as never-to-be-initialised at all. So: | you run | graphics environment | view attached | view-gated behaviour | |---|---|---|---| | `zap.sh` on a desktop | not headless | yes | fires | | `zap-x.sh` with no lifetime switch | not headless | yes | fires | | `zap-x.sh` with a headless lifetime switch | not headless | no | does not fire | | a headless lifetime with no display at all | headless | no | does not fire | So a virtual display does not move the program's own behaviour across that branch: put `Xvfb` in front of a one-shot run and every view-gated path stays exactly where it was. What a display **does** buy you is anything in the run that needs one for itself — a real browser being the obvious case, which is why the default images carry an X server, a window manager and a browser together. Distinguish the two: the display is for the things the run drives, the view is for the program's own behaviour. ## Why that distinction has teeth A measurable example lives in the local-server start-up. When the main listener cannot be bound — the address is wrong, the port is taken — the code checks whether a view is attached. With one, it offers a dialog and lets you pick another port, and the program survives. With none, it logs, prints to standard error if the lifetime is the one-shot one, and terminates the program outright. Same failure, two completely different outcomes, decided by a branch that has nothing to do with whether a display exists. That shape recurs. More than one of this program's guard rails and recovery paths is written as "if there is a view", which is another way of saying it is a desktop behaviour and not a pipeline one; and a couple of its self-termination paths are written as "if there is *no* view", firing in automation and not on a human's screen. When you read this codebase, treat a view check as a statement about *who is watching*, and ask what happens on the side where nobody is. ## Practical advice If you want a human to look at a run inside a container, use the shipped launchers on a default image and accept that you are running the desktop. If you want a scan, use a headless lifetime, and add a virtual display only because something in the run needs one — never in the hope of changing how the program itself behaves, because it will not. And when you are debugging "it worked on my laptop", check which side of the view branch each machine was on before you look at anything else.

  • Does putting `Xvfb` in front of a `-cmd` run change how the run behaves?
    Not the program's own behaviour. The display only decides whether the windowed session can start; a lifetime switch initialises the control singleton without a view, so every view-gated path stays on the side it was already on. A display is still needed by anything in the run that drives something graphical itself, which is a different question from how the program branches.
  • Why does the same listener bind failure kill one run and merely warn another?
    Because the recovery is gated on a view being attached. With one, the code offers a dialog to choose another port and carries on; with none, it logs, writes to standard error on the one-shot lifetime, and terminates the program. It is the view check, not the error, that differs.

saying these in an interview costs you the question

  • Starting Xvfb makes a headless run behave like the desktop
  • The windowed session falls back to headless when there is no display
  • Every published image can run the windowed session
  • A view is attached whenever a display is available
  • The shipped launcher script swallows the program's exit code