fix(docker): healthcheck passes literal `$UI_PORT` to curl
Summary
The repository Dockerfile defines its healthcheck in JSON exec form and passes
localhost:$UI_PORT as a literal argv element. Exec-form healthchecks do not
run through a shell, so $UI_PORT is not expanded. The built image consequently
contains a healthcheck URL with a non-numeric port and cannot report healthy,
even when BeEF is listening on its default port.
Steps to reproduce
Check out BeEF revision
7f4d40432f84b82098433008d5dc6d9be64053df.Inspect the relevant Dockerfile instructions:
ARG UI_PORT=3000 EXPOSE $UI_PORT $PROXY_PORT $WEBSOCKET_PORT $WEBSOCKET_SECURE_PORT HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD [ "curl", "-fS", "localhost:$UI_PORT" ]Build the image, optionally using a non-default value to make the distinction explicit:
docker build --build-arg UI_PORT=4321 -t beef-healthcheck-test .Inspect the stored healthcheck command:
docker image inspect beef-healthcheck-test \ --format '{{json .Config.Healthcheck.Test}}'Observe that the result still contains the literal variable reference:
["CMD","curl","-fS","localhost:$UI_PORT"]
A direct invocation demonstrates why the stored argv cannot probe the service:
$ curl -fS 'localhost:$UI_PORT'
curl: (3) URL rejected: Port number was not a decimal number between 0 and 65535Expected behavior
The image healthcheck probes a numeric in-container HTTP port that matches the
BeEF listener, defaulting to port 3000. If UI_PORT is intended to be a
configurable image input, the Docker metadata and the application configuration
should use the same resolved value.
Actual behavior
UI_PORTis declared as a build-timeARGin the final image stage.- Docker expands it for Dockerfile instructions such as
EXPOSE, but the JSON healthcheck stores each argument literally. - No shell processes the healthcheck argv, so
$UI_PORTremains unexpanded. - The image therefore invokes curl with
localhost:$UI_PORT, which curl rejects as an invalid port before making an HTTP request.
Publishing UI_PORT as an ENV by itself would not fix this JSON exec-form
command: environment variables are not interpolated inside an argv element.
The healthcheck must use a resolved numeric value or explicitly invoke a shell
with a runtime environment variable.
Affected area
- Root
Dockerfilehealthcheck definition - Docker and Compose workflows that wait for the image's Docker health status
- Orchestrators that consume the Docker image healthcheck rather than defining an independent probe
Runtime or environment
- BeEF:
0.6.0.0 - Revision:
7f4d40432f84b82098433008d5dc6d9be64053df - Dockerfile base:
ruby:3.4.7-slim-bookworm - Verified with Docker Engine
29.5.3 - Verified: 2026-08-06
Evidence
A minimal image using the same Dockerfile construct and
--build-arg UI_PORT=4321 produced:
["CMD","curl","-fS","localhost:$UI_PORT"]The image environment did not contain UI_PORT. This independently confirms
the Dockerfile behavior without requiring BeEF's Ruby dependencies or valid
Admin UI credentials.
No open or closed BeEF issue matching HEALTHCHECK plus UI_PORT was found in
the repository issue search on 2026-08-06.
Impact
Containers running BeEF can remain unhealthy even when the HTTP listener is
working. Automation that gates readiness or dependent services on Docker health
can time out, restart the container, or require operators to override the
upstream healthcheck.
The defect affects the in-container probe. Host port publishing (for example,
-p 8080:3000) is separate and does not require the healthcheck to use the host
port.
Additional context
- Target upstream: https://github.com/beefproject/beef
- BeEF still requires non-default credentials before the normal image entrypoint will keep the server running. That startup gate is independent of the invalid healthcheck argv.
- Closure signal: image inspection shows a numeric or shell-expanded healthcheck
target, and a normally configured container listening on the expected internal
port reaches Docker's
healthystate.
Blocked by
None — can start immediately
Source: beefproject/beef