Writing Dockerfile
Basically, a Dockerfile is just a simple text file. It contains a list of instructions that the Docker engine reads to build a Docker image automatically. You don’t need to memorize complex commands; you just need to understand the flow. It’s like writing a script to set up a new computer, but for a container.
Think of a Dockerfile as a Recipe for a Dish.
- The Base Image (FROM) is your main ingredient (like rice or dough).
- The RUN commands are the cooking steps (chopping, boiling, frying).
- The COPY command is adding your secret spices (your source code).
- The CMD/ENTRYPOINT is how you serve the dish on the table (starting the app). If you miss a step in the recipe, the dish tastes bad. If you mess up the Dockerfile, the app won’t run!
If you write the recipe clearly, anyone in your kitchen (Team) can cook the exact same dish (App) without mistakes.
Technical Analogy: Think of a Dockerfile as “Infrastructure as Code” at the micro-level. Just like Terraform builds your servers, a Dockerfile builds the internal environment of your application server.
Key Characteristics to Remember
- Top-Down Execution: Docker reads instructions from top to bottom, step-by-step.
- Layer Cake: Every command creates a new “layer.” Fewer layers mean a smaller, faster image.
- Cache is King: Docker is smart; if you haven’t changed a step, it reuses the old result to save time.
- Ephemeral: Containers are meant to be stopped and destroyed easily; don’t store permanent data inside them without a volume.
Logical Order of Dockerfile Instructions
| Instruction | Description | Best Practice Note |
| ARG | Defines variables that users can pass at build-time. | Can be placed before FROM to declare variables used in the FROM line. |
| FROM | Initializes a new build stage and sets the Base Image. | Must be the first non-comment instruction (unless preceded by ARG). |
| LABEL | Adds metadata (version, description, maintainer) to an image. | Replaces MAINTAINER. Use this to define the author/maintainer now. |
| ENV | Sets persistent environment variables. | Available during the build and when the container runs. |
| SHELL | Overrides the default shell used for the shell form of commands. | Useful if you need to use powershell or a specific bash configuration. |
| WORKDIR | Sets the working directory for any subsequent instructions. | Prefer absolute paths. Always use this instead of RUN cd .... |
| COPY | Copies files/folders from your local machine to the image. | Preferred over ADD for transparency unless you need ADD‘s specific features. |
| ADD | Copies files; also supports remote URLs and auto-extracting tar files. | Use only if you need to extract a tarball or download a remote URL. |
| RUN | Executes commands in a new layer on top of the current image. | Combine commands with && to reduce image layers (e.g., apt-get update && install). |
| USER | Sets the user name (or UID) and group to use for running the image. | Switch to a non-root user here for security. |
| VOLUME | Creates a mount point for externally mounted volumes. | Used for persistent data or sharing data between containers. |
| EXPOSE | Documents which ports the application is intended to listen on. | Does not actually publish the port; it is for documentation and inter-container communication. |
| ENTRYPOINT | Configures a container that will run as an executable. | The main process. Arguments passed to docker run are appended to this. |
| CMD | Provides defaults for an executing container. | Can be overridden by arguments passed to docker run. Often used to pass default flags to ENTRYPOINT. |
| HEALTHCHECK | Tells Docker how to test a container to check that it is still working. | Essential for orchestration tools (like Kubernetes or Swarm) to know if a restart is needed. |
| STOPSIGNAL | Sets the system call signal that will be sent to the container to exit. | Default is usually SIGTERM. |
| ONBUILD | Adds a trigger instruction to be executed when the image is used as a base for another build. | Useful for creating “builder” images (e.g., a Maven image that compiles code). |
| MAINTAINER | Deprecated instruction to specify the author. | Do not use. Use LABEL maintainer="name" instead. |
Key Differences to Remember
- COPY vs. ADD:
COPYis simple (local file to container).ADDis magic (can extract zips and download URLs). UseCOPYunless you strictly need the magic. - CMD vs. ENTRYPOINT:
- Use ENTRYPOINT for the command (e.g.,
python script.py). - Use CMD for the default arguments (e.g.,
--helpor--port 8080). - Note: If you run
docker run myimage <args>, it overwritesCMDbut appends toENTRYPOINT.
- Use ENTRYPOINT for the command (e.g.,
–
“All-In-One” Dockerfile
# 1. ARG: Define a variable for the base image version
ARG ALPINE_VERSION=3.18
# 2. FROM: Initialize the build from a base image
FROM alpine:${ALPINE_VERSION}
# 3. MAINTAINER: (Deprecated) Specify the author.
# Note: This is included per your request but is deprecated. Use LABEL instead.
MAINTAINER "admin@example.com"
# 4. LABEL: Add metadata to the image (Modern replacement for MAINTAINER)
LABEL org.opencontainers.image.authors="admin@example.com" \
version="1.0" \
description="A demo image showing all Dockerfile instructions"
# 5. SHELL: Change the default shell for RUN commands
SHELL ["/bin/sh", "-c"]
# 6. ENV: Set environment variables (Available in build AND runtime)
ENV APP_HOME=/usr/src/app \
PORT=8080 \
ENVIRONMENT=production
# 7. WORKDIR: Set the working directory
WORKDIR $APP_HOME
# 8. RUN: Execute build commands (Installing dependencies)
# We install curl for the Healthcheck later
RUN apk add --no-cache curl nginx && \
mkdir -p /run/nginx
# 9. ONBUILD: Trigger instruction
# This command will ONLY run if someone else writes "FROM this-image"
ONBUILD RUN echo "This image was used as a base!"
# 10. ADD: Download remote file or extract tar
# Here we download a sample text file from the web
ADD https://www.google.com/robots.txt ./sample_download.txt
# 11. COPY: Copy local files to the container
# Assuming you have a file named 'start.sh' in your local folder
COPY start.sh ./start.sh
# 12. USER: Set the user ID (Security best practice)
# Create a non-root user and switch to it
RUN adduser -D myuser
USER myuser
# 13. VOLUME: Create a mount point for external storage
# Data written here persists even if the container stops
VOLUME ["/tmp/data"]
# 14. EXPOSE: Document the port the app listens on
EXPOSE $PORT
# 15. STOPSIGNAL: Define the system call signal for exiting
STOPSIGNAL SIGTERM
# 16. HEALTHCHECK: Check container health on startup
# Docker will run this every 30s to see if the server is up
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:$PORT/ || exit 1
# 17. ENTRYPOINT: The main executable
# This script will receive the CMD arguments
ENTRYPOINT ["./start.sh"]
# 18. CMD: Default arguments
# These are passed to the ENTRYPOINT script
CMD ["--mode", "default"]
if you want to try building this Dockerfile follow below steps
1: Create a dummy start.sh file by running below commands at the same place where Dockerfile created.
# Create a dummy start.sh file
echo '#!/bin/sh' > start.sh
echo 'echo "Starting server with args: $@"' >> start.sh
echo 'tail -f /dev/null' >> start.sh
chmod +x start.sh
2: run below command to build the Docker image
docker build -t all-instructions-demo .
1. .dockerignore File
Just like .gitignore, this file tells Docker what NOT to copy. You don’t want to copy your local node_modules or massive .git history folder into the container. It bloats the image and slows down the build.
2. Layer Caching Strategy
Docker builds cache layers. If you change a file in Layer 4, Docker must rebuild Layer 4, 5, 6, and so on.
- Strategy: Copy your dependency manifest (like
package.jsonorrequirements.txt) first, run the install, and then copy your source code. - Why? Because you change your code 100 times a day, but you change dependencies rarely. This makes builds super fast.
3. Linting
Use a linter to check your syntax before you build.
- Tool: Hadolint is the industry standard for checking Dockerfile best practices.
This is where we move from “it works” to “it is enterprise-grade”.
Multi-Stage Builds
This is the gold standard. You use one image to build the app (compile code) and a completely different, tiny image to run it. This discards all the heavy “building tools” (compilers, SDKs) that you don’t need in production.
Example Logic:
- Stage 1 (Builder): Use a heavy image (like
golang:1.19). Compile the code into a binary. - Stage 2 (Runner): Use a tiny image (like
alpineordistroless). Copy only the binary from Stage 1. - Result: Your image size drops from 800MB to 20MB.
- Link: Multi-stage builds docs
# --- STAGE 1: Build Stage (The "Builder") ---
# We use a large image (Golang) that has compilers and build tools
FROM golang:1.21-alpine AS builder
# Set working directory inside the builder
WORKDIR /app
# Copy source code
COPY main.go .
# Compile the binary (named 'myapp')
# This creates a standalone executable
RUN go build -o myapp main.go
# --- STAGE 2: Production Stage (The "Runner") ---
# We switch to a tiny image (Alpine) which is only ~5MB
FROM alpine:latest
# Set working directory for the final image
WORKDIR /root/
# COPY --from=builder: This is the magic step.
# We pull ONLY the compiled binary from the previous stage.
# We discard the source code and the Go compiler.
COPY --from=builder /app/myapp .
# Command to run the binary
CMD ["./myapp"]
Distroless Images
Architects often use “Distroless” images from Google. These images contain only your application and its runtime dependencies. They do not contain package managers or shells.
- Benefit: If a hacker gets in, they cannot run shell commands because there is no shell!
- Link: Google Container Tools – Distroless
3. Least Privilege (Non-Root User)
By default, Docker containers run as root. This is dangerous. If a hacker escapes the container, they are root on your host machine.
- Fix: Create a user and switch to it.Dockerfile
RUN addgroup -S appgroup && adduser -S appuser -G appgroup USER appuser
4. Supply Chain Security
Always scan your base images for vulnerabilities (CVEs).
- Tools:
- Snyk (Developer-first security)
- Clair (Static analysis)
- Hadolint: A linter to check Dockerfile best practices.
- Trivy: Scans your images for vulnerabilities.
- Docker Slim: Automatically minimizes your container images.
–
- ARG vs ENV: Many people confuse these.
ARGis only available during the build (like a build version).ENVis available inside the running container (like a database URL). - PID 1 Issue: In a container, your app is Process ID 1. If it crashes, it might not handle signals (like Ctrl+C) correctly. Using a tool like Tini helps manage this.
- Timezones: Containers default to UTC. If your Indian app needs IST, you must explicitly set the timezone in the Dockerfile.
Key Components
- Base Image: The OS layer.
- Instructions: Commands like RUN, COPY.
- Layers: The intermediate images created by each command.
- Context: The files available to the Docker daemon during build.
Key Characteristics
- Immutable: Once built, it doesn’t change.
- Portable: Runs anywhere Docker is installed.
- versioned: Can be stored in Git and version controlled.
Use Cases
- Microservices: Isolating small services.
- CI/CD: Creating consistent testing environments.
- Legacy Apps: “Containerizing” old apps to run on modern servers.
Benefits vs Limitations
| Benefits | Limitations/Challenges |
| Consistency: “It works on my machine” is solved. | Learning Curve: Understanding layers and networking can be tough initially. |
| Speed: Fast deployment and startup. | Security: Defaults are often insecure (running as root). |
| Isolation: One app crashing doesn’t kill others. | Persistence: Data inside a container is lost when it stops (unless using Volumes). |
Common Issues & Solutions
| Problem | Cause | Solution |
| Huge Image Size | Including build tools in final image | Use Multi-stage builds and Alpine/Distroless images. |
| Slow Builds | Not utilizing cache effectively | Copy dependency files (package.json) before copying source code. |
| Security Risk | Running as Root | Create a specific user: RUN useradd -m myuser && USER myuser. |
| Sensitive Data | Hardcoding API keys | Use Docker Secrets or Environment Variables. |
- Dockerfile Reference (The Bible of Dockerfiles)
- Best Practices for Writing Dockerfiles
- Docker Hub (Official Image Registry)