#3626·beef

fix(docker): healthcheck passes literal `$UI_PORT` to curl

Author: coygeekCreated Aug 6, 2026Updated Aug 6, 2026

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

  1. Check out BeEF revision 7f4d40432f84b82098433008d5dc6d9be64053df.

  2. Inspect the relevant Dockerfile instructions:

    dockerfile
    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" ]
  3. Build the image, optionally using a non-default value to make the distinction explicit:

    bash
    docker build --build-arg UI_PORT=4321 -t beef-healthcheck-test .
  4. Inspect the stored healthcheck command:

    bash
    docker image inspect beef-healthcheck-test \
      --format '{{json .Config.Healthcheck.Test}}'
  5. Observe that the result still contains the literal variable reference:

    json
    ["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 65535

Expected 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_PORT is declared as a build-time ARG in 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_PORT remains 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 Dockerfile healthcheck 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:

json
["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 healthy state.

Blocked by

None — can start immediately