What are the Main Instructions Used in Dockerfile
Instructions in a Dockerfile are domain-specific language (DSL) commands executed sequentially by the Docker engine / BuildKit to produce immutable image layers and metadata.
🟢 Junior Level
30-Second Interview Answer
Instructions in a Dockerfile are domain-specific language (DSL) commands executed sequentially by the Docker engine / BuildKit to produce immutable image layers and metadata.
All primary instructions fall into four functional categories:
- Environment and Context:
FROM(defines base OS/runtime),WORKDIR(sets working directory),ENV(runtime environment variables),ARG(build-time variables). - Filesystem Operations:
COPY(transfers local files),ADD(copies files with auto-extraction of tar archives). - Execution Commands:
RUN(executes during image build to create layers),ENTRYPOINTandCMD(execute at container startup). - Configuration and Security:
USER(switches to an unprivileged UID),EXPOSE(documents network ports),VOLUME(declares external mount points),HEALTHCHECK(monitors container liveness).
Clean Java 21 / Spring Boot 3 Example
# 1. Base runtime image
FROM eclipse-temurin:21-jre-jammy
# 2. Establish working directory inside container (auto-created if missing)
WORKDIR /app
# 3. Inject JVM ergonomics for container cgroups
ENV JAVA_TOOL_OPTIONS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
# 4. Copy pre-compiled JAR artifact
COPY target/service.jar app.jar
# 5. Create and switch to non-root user for security
RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser
USER appuser
# 6. Document application port (Metadata only)
EXPOSE 8080
# 7. Fixed container entrypoint in Exec Form
ENTRYPOINT ["java", "-jar", "app.jar"]
🟡 Middle Level
Key Instruction Deep Dive
1. RUN vs CMD vs ENTRYPOINT
RUN: Executed once during image creation (docker build). Installs OS packages (apt-get), compiles code, creates users, and commits a permanent read-only layer to the image.ENTRYPOINT: Defines the fixed executable binary launched whenever the container starts (docker run). Overriding it requires an explicit--entrypointCLI flag.CMD: Supplies default arguments to theENTRYPOINT(or defines a default command if noENTRYPOINTis present). It is easily overridden by passing arguments at the end ofdocker run.
2. COPY vs ADD
COPY: Transparent, safe, and recommended for 95% of use cases. Directly transfers files or directories from the local build context into the image.ADD: An extended copy command with extra automation:- Automatically extracts compressed local tarballs (
.tar,.tar.gz,.tar.bz2) into the target directory. - Can fetch files from remote URLs (not recommended for production because it cannot clean up downloaded archive files within the same layer).
- Automatically extracts compressed local tarballs (
3. ARG vs ENV
| Feature | ARG (Build Argument) | ENV (Environment Variable) |
|—|—|—|
| Lifecycle | Available only during build (docker build) | Available both during build and in running container |
| Override Mechanism | Passed via --build-arg KEY=value | Passed via -e KEY=value or --env-file |
| Security Implication | Baked in plaintext in docker history (Do NOT use for secrets!) | Visible via docker inspect and to all container processes |
| Common Use Case | Specifying package versions, compiler flags | Database URLs, active Spring profiles, heap tuning |
4. EXPOSE and VOLUME
EXPOSE 8080: Acts purely as documentation between the image author and system operators. It does not publish or bind the port to the host interface. To expose traffic outside the Docker host, you must explicitly bind ports at runtime usingdocker run -p 8080:8080.VOLUME ["/data"]: Declares a mount point to decouple persistent or heavy I/O data from the ephemeral container Overlay2 filesystem. Writes to this path bypass the container’s copy-on-write layer.
🔴 Senior Level
Atomic Layer Chaining in RUN
Every distinct RUN instruction commits an immutable Overlay2 layer. Cleaning temporary caches in a subsequent RUN step creates a whiteout marker but permanently traps downloaded bytes in parent layers:
# ❌ ANTI-PATTERN: Traps 150 MB of apt cache files in intermediate layer!
RUN apt-get update
RUN apt-get install -y --no-install-recommends curl jq
RUN rm -rf /var/lib/apt/lists/*
# ✅ PRODUCTION PATTERN: Atomic single layer with zero leftover build debris
RUN apt-get update && \
apt-get install -y --no-install-recommends curl jq && \
rm -rf /var/lib/apt/lists/*
Exec Form vs Shell Form & The PID 1 Graceful Shutdown Problem
Any process-starting directive (RUN, CMD, ENTRYPOINT) can be written in two distinct syntactic notations:
+-------------------------------------------------------------------+
| Exec Form vs Shell Form Process Tree |
| |
| 1. Exec Form: ENTRYPOINT ["java", "-jar", "app.jar"] |
| Host Kernel |
| └── Container PID 1: java (JVM) |
| * Directly receives SIGTERM from Docker/Kubernetes |
| * Executes Spring Boot shutdown hooks & flushes buffers |
| |
| 2. Shell Form: ENTRYPOINT java -jar app.jar |
| Host Kernel |
| └── Container PID 1: /bin/sh -c "java -jar app.jar" |
| └── Container PID 2: java (JVM) |
| * /bin/sh drops SIGTERM; JVM never receives signal |
| * Killed after grace period by SIGKILL (Exit Code 137) |
+-------------------------------------------------------------------+
- Shell Form (
ENTRYPOINT command param): Docker implicitly invokes/bin/sh -c "command param". The shell process becomes PID 1. Standard POSIX shells do not forward signals (SIGTERM) to child processes. When Kubernetes stops the container, Java never receivesSIGTERM. Shutdown hooks, active database transaction rollbacks, and Kafka commit offsets are abandoned. After 30 seconds, the kernel issuesSIGKILL(Exit 137). - Exec Form (
ENTRYPOINT ["executable", "param1", "param2"]): Docker callsexecve(2)directly without spawning a subshell. The JVM process becomes PID 1, interceptingSIGTERMgracefully.
The VOLUME Declaration Ordering Trap
A critical edge case that trips up many engineers is declaring VOLUME before configuring directory ownership or initial files:
- Once an instruction
VOLUME ["/app/data"]is encountered, Docker creates a temporary anonymous volume mount for that path during all subsequent build steps. - If a later step runs
RUN chown -R appuser:appgroup /app/dataorRUN touch /app/data/init.db, those mutations are written into the temporary build-time volume. - When the build step finishes, the temporary volume is discarded. The changes are never saved into the final image layers!
- At runtime,
/app/datawill revert toroot:rootownership, causing the non-root application to fail withPermission denied. - Golden Rule: Always create directories, assign file ownership with
chown, and seed initial data before declaring theVOLUMEinstruction.
Container Orchestration Health Checks (HEALTHCHECK)
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1
- Instructs the Docker Engine to actively monitor application health.
- Containers transition from
startingtohealthyorunhealthy. --start-period=60s: Crucial for enterprise Java/Spring Boot applications to prevent false-positive kills during JIT compilation and cold JVM boot phases.
4 Tricky Questions with In-Depth Answers
1. What hidden danger occurs if RUN chown -R appuser /app/data is placed AFTER VOLUME ["/app/data"] in a Dockerfile?
Answer:
The VOLUME instruction explicitly tells Docker that the specified directory is an external mount point rather than part of the union filesystem.
For every subsequent instruction during the build, Docker mounts a temporary anonymous volume over /app/data.
The RUN chown -R appuser /app/data command changes ownership inside that temporary volume. Once the step finishes, Docker commits the container layer diff—which contains nothing because the changes were written to the volume—and discards the volume.
As a result, in the final image, /app/data remains owned by root. When the container is launched with USER appuser, the process crashes with java.io.IOException: Permission denied when attempting to write to /app/data.
2. What is the difference in variable scope between ARG declared BEFORE the first FROM instruction vs ARG declared AFTER FROM?
Answer:
- An
ARGdeclared before the initialFROMline lives in the global build scope. Its only purpose is to parameterizeFROMstatements (for example, choosing a base image version:ARG JAVA_VER=21$\to$FROM eclipse-temurin:${JAVA_VER}). - Once
FROMexecutes, Docker enters an isolated build stage. The globalARGgoes out of scope and becomes completely inaccessible to subsequentRUN,ENV, orCOPYinstructions. - To use that variable inside a build stage, you must re-declare the
ARGinside that stage without specifying a default value:ARG JAVA_VER=21 FROM eclipse-temurin:${JAVA_VER} ARG JAVA_VER # Re-declared to bring into the stage scope RUN echo "Compiling on Java $JAVA_VER"
3. How do ENTRYPOINT and CMD interact when both are declared in Exec Form? Show an example with Spring Boot profiles and ports.
Answer:
When both are declared in Exec Form (JSON array), ENTRYPOINT defines the executable binary and its immutable parameters, while CMD appends default, overridable arguments:
ENTRYPOINT ["java", "-jar", "app.jar"]
CMD ["--server.port=8080", "--spring.profiles.active=prod"]
- Running
docker run order-serviceexecutes:java -jar app.jar --server.port=8080 --spring.profiles.active=prod. - Running
docker run order-service --server.port=9090overridesCMDentirely, executing:java -jar app.jar --server.port=9090. This allows image creators to enforce a strict binary entrypoint while allowing operators to pass dynamic CLI arguments effortlessly.
4. Why is RUN curl -fSL ... | tar -xz && rm preferred over ADD http://... for fetching remote archives?
Answer:
- No Auto-Extraction for URLs:
ADDonly automatically unpacks.tar.gzfiles from the local build context. When pointed to an HTTP URL, it simply downloads the raw archive without extracting it. - Layer Bloat:
ADDwrites the downloaded archive into an immutable layer. Even if you runRUN rm archive.tar.gzin the next instruction, the compressed archive remains permanently inside the image history. - Integrity & Verification:
ADDlacks support for HTTP headers, basic authentication, retry logic, and cryptographic SHA-256 checksum verification. UsingRUN curl ... | tar -xzstreams the archive directly into the tar extraction utility and cleans up in a single execution step, leaving no lingering archive files in the layer history.
🎯 Interview Cheat Sheet
Summary of Core Dockerfile Instructions
| Instruction | Phase | Primary Purpose | Best Practice / Pro-Tip |
|—|—|—|—|
| FROM | Build | Sets base OS and runtime | Always pin specific version tags; avoid :latest |
| WORKDIR | Build/Run | Sets working directory | Replaces cd; creates directories automatically |
| RUN | Build | Executes commands to create layers | Chain with && \ and clean caches in the same command |
| COPY | Build | Copies files from build context | Always prefer over ADD; use --chown flag |
| ADD | Build | Copies with auto-tar extraction | Use only for local .tar.gz unpacking |
| ARG | Build | Build-time parameterization | Scoped per stage; never use for passwords |
| ENV | Build/Run | Injects runtime environment vars | Persists into container runtime environment |
| USER | Run | Sets non-root execution UID | Enforce least-privilege security (USER 10001) |
| EXPOSE | Metadata | Documents container listening ports | Documentation only; does not publish ports to host |
| VOLUME | Metadata/Run| Declares external mount points | Declare after directory creation and permissions setup |
| ENTRYPOINT| Run | Fixed primary executable | Always use Exec Form (["java", "-jar", ...]) for PID 1 |
| CMD | Run | Default overridable arguments | Appends default arguments to ENTRYPOINT |
| HEALTHCHECK| Run | Declares container health probe | Set --start-period for slow-starting Java services |
Red Flags (DO NOT Say)
- ❌ “
EXPOSE 8080opens port 8080 to the internet.” (It is purely documentary metadata; runtime binding requiresdocker run -p 8080:8080). - ❌ “
RUN cd /appsets the current folder for subsequent steps.” (EachRUNexecutes in a fresh subshell; useWORKDIR /app). - ❌ “I pass passwords using
ARGbecause args disappear in the running container.” (ARGvalues are permanently stored in cleartext insidedocker history). - ❌ “Shell form
ENTRYPOINT java -jar app.jaris fine in Kubernetes.” (Wraps Java in/bin/shas PID 1, preventing gracefulSIGTERMpropagation and corrupting transactions upon Pod eviction).
Related Questions
- What is Dockerfile — Fundamental syntax and build mechanics
- What is the Difference Between CMD and ENTRYPOINT — In-depth analysis of startup directives
- What is Multi-Stage Build — Splitting build and runtime stages
- Why are Health Checks Needed — Container probes and availability