skip to content

Desktop, Daemon and Container

The same program starts as a windowed session, as a run that finishes and exits, or as a long-lived background service. Interviewers ask because the image you pick silently limits it.

on this pageshow

questions

5

In ZAP, what is the real difference between starting the program with `-cmd` and with `-daemon`?

level: middleimportance: must knowfreq 62%

answer

  1. neither switch is about the window
  2. one run ends, one run waits
  3. two bootstraps, one shared base
  4. the loop is the whole difference

basics

~20 s

Lifetime, not the interface. Both switches turn the window off and both bring up the local listener. -cmd runs the registered work inline and then shuts down; -daemon runs it on a background thread and loops.

solid answer

~30 s

Both switches are headless: the argument parser sets `setGUI(false)` for each of them, and it is `setDaemon(true)` that separates them. `ZAP.createZapBootstrap` picks `GuiBootstrap` when the window is wanted, `DaemonBootstrap` when the daemon flag is set, and `CommandLineBootstrap` otherwise — all three share a base that initialises the model and the control singleton. `CommandLineBootstrap` calls `control.runCommandLine()` inline, shuts the program down in a `finally`, and returns `control.getExitStatus()`. `DaemonBootstrap` marks the view as never-to-be-initialised, hooks the same command-line listeners on a thread named `ZAP-daemon`, and then sleeps in a loop forever. Passing both switches is rejected in the `CommandLine` constructor before anything starts.

code

bash · 8 lines
bash
# -cmd: headless, runs the registered work inline, shuts down, returns a status
zap.sh -cmd -autorun /zap/wrk/plan.yaml; echo "rc=$?"

# -daemon: headless too, same work on the "ZAP-daemon" thread, then loops
zap.sh -daemon

# both together: rejected in the CommandLine constructor, nothing starts
zap.sh -cmd -daemon

go deeper

for a junior

Remember the shape: no switch means a window, and the two switches are both windowless. Say which one finishes and which one stays up, and you have the answer.

for a middle

Explain the mechanism: the argument parser clears the GUI flag for both switches, and only the daemon flag differs. Then name the two bootstrap classes and say that one runs the work inline and one runs it on a background thread that never returns.

for a senior

Show the operational consequence. A service lifetime has no natural place to report a verdict and will sit in a pipeline forever unless something shuts it down; a one-shot lifetime tears the listener down the moment its work is finished, which breaks any step that expected to talk to it.

for a principal

Own the standard. Decide once whether jobs in your organisation drive this tool as a one-shot process or as a service, because the two need different failure handling, different timeouts and different answers to who may reach the listener.

## The switch names a lifetime, not an interface The most common mistake here is reading `-cmd` as "headless" and `-daemon` as "the other one". Both are headless. In `CommandLine.parseSwitchs`, `-cmd` does `setDaemon(false); setGUI(false)` and `-daemon` does `setDaemon(true); setGUI(false)`. The only field that differs is the daemon flag, and that flag decides **how long the process lives**, not whether a window appears. If you supply neither switch, the GUI flag stays on and you get the windowed session. If you supply both, the `CommandLine` constructor throws an `IllegalArgumentException` saying the two "cannot be used at the same time", `ZAP.main` catches it, prints an invalid-parameter message, and exits without starting anything. ## Three bootstraps, one decision `ZAP.createZapBootstrap` reads the parsed arguments and chooses one of three classes, stamping a `ZAP.ProcessType` as it goes: | invocation | bootstrap | `ProcessType` | window | local listener | what `start()` returns | |---|---|---|---|---|---| | no switch | `GuiBootstrap` | `desktop` | yes | yes | zero, immediately — the UI runs on the AWT queue | | `-cmd` | `CommandLineBootstrap` | `cmdline` | no | yes | `control.getExitStatus()` after shutdown | | `-daemon` | `DaemonBootstrap` | `daemon` | no | yes | zero, immediately — a background thread keeps going | The enum carries a fourth value, `zaas`, that this selection never assigns. The two headless bootstraps share `HeadlessBootstrap`, which initialises the control singleton without a view; that shared base is why so much of the behaviour is identical. ## What the one-shot path does differently - It runs `control.runCommandLine()` **on the calling thread**, so the call blocks until the registered work is finished. - It shuts the program down in a `finally` block and then returns `control.getExitStatus()`, which becomes the process exit code. - It owns the terminal argument paths — help, version and support-info printing all end the run right there. - `CommandLine.info(...)` and `CommandLine.error(...)`, the methods command-line listeners are told to report through, **only write to standard output or standard error when the process type is `cmdline`**. Everywhere else they write to the log and nothing else. - The network add-on skips the *additional* configured listeners in this mode; only the main one is started. ## What the service path does differently - It calls `View.setDaemon(true)`, which the source comments as preventing the view from ever being initialised. - It moves the whole sequence — control init, session handling, the command-line hook, `runCommandLine()` — onto a thread it names `ZAP-daemon`, and `start()` returns zero as soon as that thread is running. - It runs an update check that the one-shot path skips — the windowed session runs the same check, so this is a one-shot omission rather than a daemon speciality. - After the work finishes, the thread sleeps in a loop rather than returning. The source comment says this is the only non-daemon thread, so it is what keeps the JVM alive, and that shutting down is something the control API does with an explicit exit. ## Both of them start the listener The local server that carries the control API and the proxy on the same port is brought up by the `network` add-on's command-line handling, which both headless modes reach because both hook command-line listeners. So "I need `-daemon` before anything will listen" is false. What `-daemon` gives you is **time**: the listener is still there after the registered work has finished, which is the whole point of running the program as a service. ## What the switch does not change Almost everything else is shared, and saying so is half of answering the question well: - **Which add-ons load.** That is decided by the build and the install directory, not by the lifetime flag. A one-shot run is not a reduced program. - **Session handling.** `-session` and `-newsession` are processed by the shared headless base, the same way in both modes, and supplying that pair together is rejected just as the lifetime pair is. - **Configuration overrides.** `-config key=value` goes through the same control-overrides object either way. - **The command-line hook.** Both modes call the extension loader's command-line hook, which is how every add-on-registered argument — including the plan flags — reaches its listener at all. So when someone says a flag "only works in daemon mode", be suspicious: usually what they mean is that the *process was still alive* long enough for them to use it. ## Why it matters in a pipeline A job that wants a verdict wants the one-shot lifetime, because that is the lifetime that has somewhere to put an answer. A job that wants to drive the program interactively over its control API — several steps, its own ordering, its own polling — wants the service lifetime, and must then decide separately how the pipeline learns whether the run passed. Choosing the wrong one usually shows up as a job that hangs forever (a service where a one-shot was wanted) or as a job whose steps all race a process that already exited.

  • What actually happens if both `-cmd` and `-daemon` are passed?
    The `CommandLine` constructor throws an `IllegalArgumentException` saying the two cannot be used at the same time. `ZAP.main` catches it, prints a message naming the invalid parameters, and exits non-zero. No model, no control singleton, no listener — the rejection happens during argument parsing.
  • With neither switch, what does the program do on a machine with no display?
    It picks `GuiBootstrap`, because the GUI flag is only cleared by a switch. `GuiBootstrap` then asks the runtime whether the graphics environment is headless; if it is, it logs a fatal message telling you to run inline or in daemon mode and returns a failure code, so the process is gone in seconds.
  • Can an automation plan run under `-daemon`, or only under `-cmd`?
    It runs under both. The daemon's background thread hooks command-line listeners and calls `runCommandLine()` exactly as the one-shot path does. What differs is afterwards: the one-shot path shuts down and returns a status, while the daemon keeps looping and leaves the listener up.

saying these in an interview costs you the question

  • -cmd is the headless one and -daemon shows the window
  • Pass both switches and the later one wins
  • The control API only listens when -daemon is used
  • Leaving both switches off just runs it headless anyway
  • An automation plan can only be run from -cmd
open as a page

Which ZAP container image tag should a CI job pull, and what does the `bare` tag leave out?

level: juniorimportance: should knowfreq 55%

basics

~20 s

Pull the default tag unless you know you do not need what it carries. The bare tag is a JRE-on-Alpine image with bash and curl added: no browser, no X server, no Python, and one architecture.

open as a page

In ZAP, why does a `-cmd` run return a failure exit code where a `-daemon` run returns zero?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Only the one-shot bootstrap returns an exit status; the daemon bootstrap returns zero as soon as its background thread is alive. The automation add-on also refuses to record a status unless the process type is cmdline.

open as a page

Should a pipeline run ZAP as a fresh container per job or as one long-lived daemon?

level: principalimportance: should knowfreq 35%

basics

~20 s

Default to a fresh container per job running the one-shot lifetime: it is the shape whose exit code is a verdict and whose state cannot leak between targets. Share a daemon only when several steps need the control API.

open as a page

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

level: seniorimportance: nice to knowfreq 28%

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.

open as a page