Which ZAP container image tag should a CI job pull, and what does the `bare` tag leave out?
answer
- read the file, not the name
- the small one is a different Dockerfile
- no browser and no interpreter
- one architecture, and no start command
basics
~20 sPull 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.
solid answer
~40 sThe project publishes to `ghcr.io/zaproxy/zaproxy`. The `stable` and `latest` names are pushed by the same build step, so they are one image; `weekly` and `nightly` are separate builds of newer material. `bare` is built from its own Dockerfile: the final layer is a Temurin JRE on Alpine with `bash` and `curl` added, it copies in the extracted release and the shipped policies and nothing else, and it is published for `amd64` only while the others are multi-architecture. None of the four files declares a start command, so what you type after the image name is the whole of the mode decision. The old `owasp/zap2docker-*` names are dead.
code
bash · 6 lines# default tag: browser and interpreter present, multi-architecture
docker run --rm -v "$(pwd):/zap/wrk/:rw" ghcr.io/zaproxy/zaproxy:stable \
zap.sh -cmd -autorun /zap/wrk/plan.yaml
# bare tag: the program and a JRE, nothing else - and amd64 only
docker run --rm ghcr.io/zaproxy/zaproxy:bare zap.sh -cmd -suppinfogo deeper
Know where the images are published, that the default name and the moving alias are the same image, and that the small one drops the browser and the interpreter. Pin whichever tag you use.
Explain what is actually missing from the small image and why that matters for a given run, and note that the images declare no start command at all, so the mode comes entirely from the command you supply.
Treat the tag as part of the pipeline contract. A moving tag changes what the job can do without any change of yours, and a single-architecture image quietly turns into emulation the day someone moves the runner fleet.
Decide whether your organisation consumes the published images at all or rebuilds from them, and who owns the refresh. A pinned tag that nobody refreshes is a supply-chain decision made by neglect.
## What is published, and under which names The images live at `ghcr.io/zaproxy/zaproxy` and differ by tag rather than by repository. Four Dockerfiles produce them, and the release workflow pushes several names per build — a dated name and a version-stamped name alongside the moving one. For a pipeline the moving names are what matter: | tag | built from | what is in it | architectures | |---|---|---|---| | `stable` and `latest` | the stable file, off the released distribution | a Debian base, a JDK, a browser, Xvfb, a window manager, a VNC server, Python and the helper scripts | `amd64` and `arm64` | | `weekly` | the weekly file, off the weekly distribution | the same desktop-and-interpreter set, on a newer base | `amd64` and `arm64` | | `nightly` | the live file, built from source in the image | the same set again, plus build tooling | `amd64` and `arm64` | | `bare` | its own file | a Temurin JRE on Alpine, plus `bash` and `curl` | `amd64` only | `stable` and `latest` are not two builds. One step in the release workflow pushes both names, so they point at the same image. ## What `bare` actually contains Read the file rather than the name. Its builder stage downloads and expands the release and refreshes the add-ons; its final stage starts from a Temurin JRE on Alpine, adds `bash` and `curl`, copies the expanded release in, copies the shipped policy files in, creates the unprivileged `zap` user, and stops. Concretely, compared with the default image it has: - **no browser**, so anything that needs to drive one cannot run there; - **no X server and no window manager**, so the windowed session cannot start; - **no Python interpreter**, and it does not copy the repository's helper scripts in at all — the default images do that in a separate step that `bare` simply does not have; - **one architecture**, so on an `arm64` runner it either fails to pull or runs under emulation. What it does have is the program itself and its add-ons, which is exactly enough to run the one-shot or service lifetime against a target you reach over the network. That is the trade: a much smaller image that can still scan, as long as nothing in the run needs a browser or an interpreter. ## No start command is baked in None of the four Dockerfiles declares a start command. The images set a working directory, a non-root `zap` user, a few environment variables and a health check that curls the local listener — and then stop. So the command you write after the image name is not overriding a default; it **is** the mode decision. Run the program with the one-shot switch and the container ends when the run ends. Run it with the daemon switch and the container stays up until something stops it. Run it with no switch and, in an image with no display, it fails immediately. ## The container knows it is a container, twice Each image writes a small marker file into the install directory and sets an environment variable saying it is containerised. The program itself reads the marker file to decide it is in a container and to name which one; the shipped Python helpers instead look for the runtime's own marker files or that environment variable. The two checks share no signal. It rarely bites, but if you build your own image around the distribution you can easily satisfy one and not the other. ## Picking one Start from the default name unless you have a reason. Move to `bare` when the run genuinely does not need a browser or an interpreter and the image size or the attack surface matters, and confirm your runners are the architecture it is published for. Reach for `weekly` or `nightly` only when you need something that has not reached a release yet, and expect them to move under you. Whichever you choose, **pin it in the pipeline rather than tracking a moving name**, and remember that the `owasp/zap2docker-*` names you will still find in old instructions are dead — only the marker file inside the image still carries that spelling.
- Why does a job that worked on the default image fail on `bare`?Usually because the run needed something the image does not have. There is no browser, so browser-driven work cannot start; there is no Python interpreter and none of the shipped helper scripts, so anything invoking those is missing; and on an `arm64` runner the single-architecture image will not pull natively.
- What is the difference between the `stable` and `latest` tags?None. One step of the release workflow pushes both names, along with a dated one and a version-stamped one, from the same build. Treat them as aliases and pin whichever of them you use.
saying these in an interview costs you the question
- Pull owasp/zap2docker-stable, it is the official image
- The bare tag is just the default image with fewer add-ons
- The images start the program for you if you pass no command
- latest is the newest build and stable is an older one
- Every published tag runs on any runner architecture