Topic 2.5
ENTRYPOINT vs CMD, and ARG vs ENV
In one line
Four instructions that look interchangeable until an interview (or a 2am incident) asks you to explain the exact difference — CMD is a default you can override, ENTRYPOINT usually isn't, ARG only exists at build time, ENV lives on into the running container.
Key ideas
- 01
Real-life example for CMD vs ENTRYPOINT: CMD is like a restaurant's 'suggested side dish' — the kitchen serves it by default, but you can ask for a different one instead. ENTRYPOINT is like the restaurant's actual cooking process — you can add ingredients (arguments) to it, but you can't tell the kitchen to stop cooking and do something completely unrelated without a special request.
- 02
CMD ["java", "-jar", "app.jar"]sets the DEFAULT command —docker run myimageruns exactly that, butdocker run myimage --helpREPLACES the entire CMD with--help, which is almost never what you want for an app image (it's ideal for a general-purpose image likepythonorubuntuwhere the whole point IS letting the caller choose what to run). - 03
ENTRYPOINT ["java", "-jar", "app.jar"]sets the process that ALWAYS runs —docker run myimage --spring.profiles.active=proddoes NOT replace it; the extra text is APPENDED as arguments to the entrypoint instead, becomingjava -jar app.jar --spring.profiles.active=prod. This is the right choice for an image that has exactly one job. - 04
The genuinely powerful pattern combines both: ENTRYPOINT fixes the unchangeable command, CMD supplies DEFAULT arguments to it that a caller can still override —
ENTRYPOINT ["java", "-jar", "app.jar"]plusCMD ["--spring.profiles.active=default"]meansdocker run myimageuses the default profile, whiledocker run myimage --spring.profiles.active=prodswaps in a different profile, and the underlyingjava -jar app.jarinvocation itself can never be accidentally dropped. - 05
Overriding a container's ENTRYPOINT entirely (rather than just its CMD arguments) IS still possible, deliberately, with the explicit
--entrypointflag — e.g.docker run --entrypoint bash myimageto get a shell inside an image that would normally always launch your app. This is the standard trick for debugging an image whose ENTRYPOINT would otherwise never let you in. - 06
ARGdefines a variable available ONLY during the image BUILD (inside the Dockerfile, resolved bydocker build --build-arg KEY=value) — it does not exist in the final running container at all unless you deliberately copy its value into an ENV instruction. Use it for things like choosing a base image variant or a version number at build time. - 07
ENVdefines a variable that's baked into the image AND remains set in every container run from it — visible to your application at runtime via normal environment variable lookups (System.getenv("KEY")in Java). Use it for actual runtime configuration, likeSPRING_PROFILES_ACTIVE. - 08
A common, useful combination:
ARG APP_VERSION(supplied at build time by your CI pipeline) followed byENV APP_VERSION=$APP_VERSION— this takes a BUILD-time-only value and deliberately promotes it into a RUNTIME-visible one, so your running app can report its own version, something a bare ARG could never do.
In your stack
- →
A Spring Boot image built with ENTRYPOINT + a default-args CMD gives you the best of both:
docker run myimageboots normally, whiledocker run myimage --debugordocker run myimage --spring.profiles.active=staginglets whoever's running it override JUST the arguments, without ever risking accidentally replacing the entirejava -jar app.jarlaunch command the way a bare CMD-only image would allow.
Code & diagrams
ENTRYPOINT can't be silently dropped by a stray docker run argument; CMD's defaults still can be overridden on purpose.
ARG JAVA_VERSION=17
FROM eclipse-temurin:${JAVA_VERSION}-jre
ARG APP_VERSION=dev
ENV APP_VERSION=${APP_VERSION}
WORKDIR /app
COPY target/myapp.jar app.jar
# The command that ALWAYS runs — cannot be accidentally replaced
ENTRYPOINT ["java", "-jar", "app.jar"]
# Default ARGUMENTS to the entrypoint — CAN be overridden
CMD ["--spring.profiles.active=default"]Same image, three different results — because of exactly how ENTRYPOINT and CMD interact.
# Uses the default CMD args -> java -jar app.jar --spring.profiles.active=default
docker run myimage
# Overrides ONLY the CMD args -> java -jar app.jar --spring.profiles.active=prod
docker run myimage --spring.profiles.active=prod
# Builds with a different ARG value baked in at build time
docker build --build-arg APP_VERSION=1.4.2 -t myimage .
# Bypasses ENTRYPOINT entirely, on purpose, to get a debugging shell
docker run --entrypoint bash myimageExplain it without notes
Why does docker run myimage --help behave completely differently depending on whether the image used CMD alone versus ENTRYPOINT alone for its launch command?
Why can't you read an ARG's value from inside your running application, even though it was clearly used during the build?
Practice
Build one image using ENTRYPOINT and a separate one using CMD for the exact same launch command, then run both with an extra trailing argument and compare the actual behavior.
Add an ARG for a version number to a Dockerfile, build it with --build-arg, and try (and fail) to read it from inside the running container — then fix it by promoting it to an ENV instead.
Trade-offs
- ↔
ENTRYPOINT-only images are safer for single-purpose app images (nobody can accidentally 'run' something unrelated), but are less flexible for general-purpose tool images (like a base
ubuntuorpythonimage) where CMD-only, letting the caller specify absolutely anything, is exactly the intended design.
Done when you can
I can predict exactly what
docker run myimage <extra args>does for an image using ENTRYPOINT+CMD together.I know --entrypoint overrides an ENTRYPOINT deliberately when I need to debug inside an otherwise-fixed image.
I understand ARG values vanish after build unless explicitly promoted into an ENV.