In Part 1, we learned that the Dockerfile is the essential, reproducible "source code" for your Docker image. Now, we dive into the docker build command and explore advanced instructions that help you create production-ready, highly efficient images.
1. The Build Process: The docker build Command
The docker build command is what executes the instructions in your Dockerfile and creates the image.
| Command Part | Description | Example |
|---|---|---|
docker build |
The command to start the image creation process. | docker build . |
. (Context) |
The build context. This is the directory containing the Dockerfile and all files needed for the build (e.g., application code). | docker build . |
-t (Tag) |
Assigns a name and optional tag to the final image. Essential for identification and pushing to registries. | docker build -t myapp/backend:v1.0 . |
-f <file> |
Specifies an alternative Dockerfile name or path. Useful if you have multiple Dockerfiles in one directory. | docker build -f Dockerfile.dev . |
--no-cache |
Forces Docker to ignore the build cache and run every instruction. | docker build --no-cache . |
2. The Power of the Build Context
The build context (the directory you specify, often .) is critical.
When you run docker build, Docker first bundles the entire contents of the context directory and sends it to the Docker Daemon.
The COPY and ADD instructions can only reference files within this context. They cannot access files outside of it.
Using .dockerignore
To prevent sending large, unnecessary files (like node_modules, .git folders, or local environment files) to the Docker Daemon, you use a .dockerignore file.
This file works exactly like .gitignore. It lists files and directories that should be excluded from the build context sent to the daemon.
Benefit: Smaller build context means faster builds and avoids accidentally copying sensitive local files into your image.
3. Advanced Dockerfile Instructions
We covered the basics (FROM, RUN, CMD) in Part 1. These instructions provide more control over image size and container execution:
| Instruction | Purpose | Creates Layer? | Example |
|---|---|---|---|
ENTRYPOINT |
Configures a container to run as an executable. It sets the main program that will always run when the container starts. | Yes | ENTRYPOINT ["/usr/bin/python3"] |
ADD |
Similar to COPY, but it can automatically extract compressed archives (e.g., .tar, .zip) from the source URL or local path into the destination. |
Yes | ADD https://example.com/app.tar.gz /tmp/ |
ARG |
Defines build-time variables that users can pass during the build process using the --build-arg flag. |
No | ARG VERSION=1.0 |
ENV |
Sets persistent environment variables inside the resulting image (and container). These variables persist after the build and during runtime. | Yes | ENV PORT=8080 |
USER |
Sets the username or UID to be used when running the container and for subsequent instructions like CMD or RUN. |
No | USER appuser |
Understanding CMD vs. ENTRYPOINT
This is a common point of confusion:
- CMD: Defines the default arguments for the ENTRYPOINT. If no ENTRYPOINT is defined, CMD sets the executable. It is easily overridden by arguments passed to docker run.
- ENTRYPOINT: Defines the main executable. Arguments passed to docker run are appended to the ENTRYPOINT command.
| Configuration | ENTRYPOINT |
CMD |
Effective Command on Run |
|---|---|---|---|
| Shell App | Not set | ["/bin/bash"] |
/bin/bash |
| Server App | ["java", "-jar", "app.jar"] |
Not set | java -jar app.jar |
| Executable/Arguments | ["nginx"] |
["-g", "daemon off;"] |
nginx -g daemon off; |
| Override Example | ["echo"] |
["Hello"] |
echo Hello |
Override with docker run world |
["echo"] |
["Hello"] |
echo world |
4. Best Practice: Multi-Stage Builds
The biggest challenge in image building is keeping the final image small. Tools needed for building (compilers, SDKs, development dependencies) often result in large image sizes.
Multi-Stage Builds solve this by using multiple FROM statements in a single Dockerfile.
Stage 1 (Builder): Uses a large base image (e.g., golang:latest) to compile the application. This stage contains all the unnecessary build tools.
Stage 2 (Final): Uses a small, minimal base image (e.g., scratch or alpine). It only copies the final, compiled executable artifact from the Builder stage.
| Stage | Base Image Example | Contents | Purpose |
|---|---|---|---|
| Builder (Stage 1) | FROM node:20 as build |
All source code, NPM, Webpack, etc. | To Compile the application. |
| Final (Stage 2) | FROM nginx:alpine |
Only the static HTML/CSS/JS files. | To Serve the application. |
The Result: The final image size is drastically reduced because the entire build environment is discarded.
5. Pushing Your Image to a Registry
Once your image is built, the final step is sharing it. A Container Registry (like Docker Hub, AWS ECR, or GitHub Packages) stores your images securely.
-
Authenticate: Log into your registry using your CLI credentials.
docker login -
Tag the Image: Ensure your image is tagged with the full registry path (e.g., username/repository:tag).
docker tag myapp/backend:v1.0 myregistry.com/myusername/myapp:v1.0 -
Push the Image: Upload the image and its layers to the remote registry.
docker push myregistry.com/myusername/myapp:v1.0
You have now completed the entire image lifecycle!
Example Dockerfile:
# ----------------------------------
# STAGE 1: THE BUILDER STAGE
# This stage compiles, bundles, and installs dependencies.
# We use a large, full Node image (node:20) for all the necessary tools.
# ----------------------------------
FROM node:20-alpine AS build
# Set build arguments (can be passed with --build-arg during 'docker build')
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
# Set the working directory inside the container for all subsequent commands
WORKDIR /app
# Copy package.json and package-lock.json first.
# This leverages Docker's cache: if dependencies haven't changed,
# Docker won't re-run 'npm install'.
COPY package*.json ./
# Install application dependencies
RUN npm install
# Copy the rest of the application source code
COPY . .
# Run the build process (e.g., if you have a React or Angular front-end)
# RUN npm run build
# ----------------------------------
# STAGE 2: THE FINAL PRODUCTION STAGE
# This stage takes only the necessary runtime files from the build stage.
# We use a tiny base image (alpine) for a small final size.
# ----------------------------------
FROM alpine:latest
# Define environment variables (runtime settings)
ENV HOST=0.0.0.0
ENV PORT=3000
# Set the user to a non-root user (good security practice)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# Set the final working directory
WORKDIR /usr/src/app
# Copy ONLY the node executable and the necessary files from the 'build' stage
# Note the '--from=build' flag
COPY --from=build /usr/local/bin/node /usr/local/bin/
COPY --from=build /usr/local/lib/node_modules /usr/local/lib/node_modules
COPY --from=build /app ./
# The above copies could be replaced by just copying the final executable files or artifacts
# For a simple Node app, we copy the app files and hope the Node runtime is available.
# The EXPOSE instruction documents the port the application listens on.
EXPOSE 3000
# ENTRYPOINT is the main executable command (the application runner)
ENTRYPOINT ["node"]
# CMD provides the default arguments for the ENTRYPOINT (the app file to run)
CMD ["index.js"]
Key points and why we used it:
| Instruction | Purpose in this Demo | Concept Highlighted |
|---|---|---|
FROM node:20-alpine AS build |
Starts the build stage and names it build. |
Multi-Stage Build (Stage Naming) |
ARG/ENV |
Defines environment settings for build and runtime. | Build-Time vs. Runtime variables |
WORKDIR /app |
Ensures consistency for subsequent file operations. | Filesystem Context |
COPY package*.json ./ |
Separates dependency files for Cache Optimization. | Layer Caching |
RUN npm install |
Executes shell commands to install software (creates a layer). | Image Layering |
COPY --from=build /app ./ |
Transfers only the necessary files between stages. | Multi-Stage Build (Efficiency) |
EXPOSE 3000 |
Documents the port required by the application. | Port Documentation (Not actual mapping) |
USER appuser |
Drops root privileges for better security. | Security Best Practice |
ENTRYPOINT ["node"] |
Sets the application's main executable. | Container Execution |
CMD ["index.js"] |
Sets the default file/arguments for the executable. | Overridable Defaults |
Top comments (0)