TL;DR
A Dockerfile you write FROM a base image does not add to that image's ENTRYPOINT — it replaces it. If the base image already exits after running its own entrypoint binary, your second command never gets a turn. The fix from the original Stack Overflow thread is to set ENTRYPOINT ["/usr/bin/env"] and put your real multi-step command in CMD, so Docker execs your script instead of the base image's fixed binary.
The problem
FROM denvazh/gatling:2.2.3
RUN apk update \
&& apk add -U bash \
&& apk add nodejs=6.7.0-r0
COPY simulations /opt/gatling/user-files/simulations
COPY trigger-test-and-parser.sh /opt/gatling/
RUN chmod +x /opt/gatling/trigger-test-and-parser.sh
ENTRYPOINT ["bash", "/opt/gatling/trigger-test-and-parser.sh"]
The base image denvazh/gatling already ships ENTRYPOINT ["gatling.sh"] so the container runs the load test, then exits — there is never a moment where a second command, such as a Node.js script that parses the results, gets to run in the same container lifecycle. Overriding ENTRYPOINT with a shell script that chains both commands looks correct on paper, but the underlying question is more general: what actually happens to a base image's ENTRYPOINT (and its CMD) when a child Dockerfile declares its own?
Why it happens
Per the official Dockerfile reference on ENTRYPOINT, only the last ENTRYPOINT instruction that applies to the image has any effect. A child Dockerfile's FROM base-image followed by its own ENTRYPOINT line means your instruction is the one Docker keeps — the base image's ENTRYPOINT is not merged with yours, appended to it, or run first. It is discarded entirely once your image declares a new one.
The same reference calls out an easy-to-miss side effect: setting a new ENTRYPOINT resets any CMD inherited from the base image back to an empty value. So a base image that shipped both an ENTRYPOINT and a default CMD loses both the instant your Dockerfile overrides the entrypoint — you are not just replacing one instruction, you are clearing two.
The other half of the confusion is exec form versus shell form. In exec form (ENTRYPOINT ["executable", "param1"]), CMD values are appended as extra arguments to your ENTRYPOINT executable — this is the documented pairing for "CMD provides default arguments for ENTRYPOINT." In shell form (ENTRYPOINT command param1), Docker wraps the whole thing in /bin/sh -c and, per the same page, shell-form ENTRYPOINT ignores any CMD or docker run command-line arguments outright. That distinction is exactly what trips people up when they mix a shell-form ENTRYPOINT with a CMD they expect to be appended: it silently never runs.
None of this is Gatling-specific or Node-specific — it's how any layered image behaves once a second Dockerfile declares its own ENTRYPOINT on top of a base image that already had one.
Fix
The pattern from the accepted answer works because /usr/bin/env is a real executable whose entire job is to exec whatever command line follows it — so it becomes a transparent stand-in for the base image's original entrypoint binary.
- Point
ENTRYPOINTat/usr/bin/envin exec form, soCMDis appended to it as arguments:
FROM denvazh/gatling:2.2.3
RUN apk update \
&& apk add -U bash \
&& apk add nodejs=6.7.0-r0
COPY simulations /opt/gatling/user-files/simulations
COPY trigger-test-and-parser.sh /opt/gatling/
RUN chmod +x /opt/gatling/trigger-test-and-parser.sh
ENTRYPOINT ["/usr/bin/env"]
- Set
CMDto the script that runs both commands in sequence, also in exec form so it combines correctly with the exec-formENTRYPOINT:
CMD ["bash", "/opt/gatling/trigger-test-and-parser.sh"]
- The script itself just runs both commands one after another — nothing special is required inside it because the container's lifecycle now follows the script, not the base image's original binary:
#!/bin/bash
gatling.sh -s MicroserviceHPSPubSubRatePerfTest.scala
node publish-rate-to-team-city.js
- Rebuild and run as usual:
docker build --no-cache -t gatling-nodejs:v8 .
docker run -i -v "$PWD/results":/opt/gatling/results gatling-nodejs:v8
Alternative: skip the Dockerfile change entirely
If you cannot rebuild the image right now, or you only need this for one run, override the entrypoint from the command line instead. The docker run CLI reference documents --entrypoint as overwriting the default ENTRYPOINT of the image for that container:
docker run --entrypoint /opt/gatling/trigger-test-and-parser.sh gatling-nodejs:v8
--entrypoint takes a single executable path, not a full command line with arguments, so any script it points to needs to be executable and contain the full sequence of commands itself — the same script from step 3 works here unchanged.
Verify the fix
Confirm which entrypoint actually shipped in the built image, rather than trusting the Dockerfile alone:
docker inspect --format='{{.Config.Entrypoint}} {{.Config.Cmd}}' gatling-nodejs:v8
You should see [/usr/bin/env] as the entrypoint and your script command as the Cmd array — not the base image's original [gatling.sh]. Then run the container and confirm both processes execute in order, in the container logs:
docker run -i gatling-nodejs:v8
If the output stops after the first command, the most common cause is a shell-form ENTRYPOINT further up the Dockerfile that is silently dropping CMD — check every ENTRYPOINT line in the file, not just the last one you added, since a leftover shell-form line earlier in a multi-stage build can still be the one that takes effect on the final stage.
Variants and edge cases
-
Base image
CMDyou actually wanted to keep. Because settingENTRYPOINTclears the inheritedCMD, if the base image's defaultCMDsupplied arguments you relied on, you must re-declare them explicitly in your ownCMD— they will not survive the override implicitly. -
docker-compose.ymlservices. The same override applies through Compose'sentrypoint:andcommand:keys, which map onto the sameENTRYPOINT/CMDmechanism at the container level; settingentrypoint:in a compose file has the identical resetting effect on inheritedcommand:behaviour as the Dockerfile instruction does. -
Multi-stage builds. Each
FROMstarts a fresh stage; only theENTRYPOINT/CMDdeclared in (or inherited into) the final stage that produces the image ships in the result — anENTRYPOINTset in an earlier build stage that is not the final stage has no effect on the built image at all. -
Signal handling. Wrapping your real process in a shell script run as
CMDmeans the shell — not your Node.js process or Gatling — is PID 1 and receivesSIGTERMfirst. For long-running services (not one-shot scripts like this one), that matters for graceful shutdown; a one-shot script that runs to completion and exits, as in this fix, is not affected.
Overriding an inherited ENTRYPOINT is a full replacement, not a merge, and it quietly resets the base image's CMD at the same time — that combination, not any bug in Docker, is what stops a second command from ever running after a base image's fixed entrypoint. Once the override is explicit in exec form, and CMD carries the real multi-step script, the container's lifecycle is fully under the Dockerfile's control instead of the base image's. If your base image was already correct and the failure only shows up once you add your own tooling on top — for example bumping a pinned Node.js version inside the image — check that the new entrypoint script's shebang and permissions survived the COPY before assuming the entrypoint logic itself is at fault. The same "read the exact command Docker is actually trying to run" habit applies to nearby failures too: a connection-refused error in a local Compose setup talking to Postgres that traces back to the wrong hostname rather than a stuck database, a wrong-architecture exec format error in production, or an OCI runtime exec error that turns out to be a binary the image never shipped rather than anything wrong with the runtime itself. For a from-scratch reference Dockerfile with a working entrypoint and Compose setup to compare against, see the Docker Compose dev environment guide.
Originally published at https://www.iloveblogs.blog
Top comments (0)