Dockerfile Basics

A Dockerfile is the "source code" of an image—by writing a Dockerfile, you can build a reproducible application image with a single command.

1. What You'll Learn


2. A True Story of a Python Developer

(1) Pain Point: Having to manually set up the environment with every deployment

Alice wrote a Python web application. Every time she deploys it, she has to manually install Python, set up a virtual environment, install dependencies, and copy the code. She has to repeat this process three times—once for the test server, once for the pre-production environment, and once for the production server—which amounts to a total of three repetitions, each with slight variations.

(2) Solutions for Dockerfile Automation

Bob said, "Write a Dockerfile, 'pack' your application into an image, and then you'll be able to run it anywhere with a single command."

DOCKERFILE
# A simple Dockerfile for a Flask application
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]

(3) Benefits: Build Once, Run Anywhere

After Alice built the image using docker build -t myapp:1.0 ., all three environments ran on the same image, reducing deployment time from 30 minutes to 3 minutes and eliminating environment inconsistencies.


3. Basic Structure of a Dockerfile

A Dockerfile is a plain-text file that contains a sequence of instructions for building an image. Each instruction builds an image layer.

100%
graph LR
    DF["Dockerfile<br/>Sequence of Instructions"] -->|docker build| IMG["Image<br/>Read-Only Overlay"]
    IMG -->|docker run| CTN["Container<br/>Writable layer + Read-Only Layer"]

(1) Timing of Instruction Execution

Timing Command Description
During build FROM / RUN / COPY / ADD / ARG Generate image layer
Runtime CMD / ENTRYPOINT / ENV / EXPOSE / USER Defines container behavior

(2) Basic Dockerfile Template

DOCKERFILE
# 1. Base image
FROM python:3.12-slim

# 2. Set working directory
WORKDIR /app

# 3. Copy dependency file first (cache optimization)
COPY requirements.txt .

# 4. Install dependencies
RUN pip install --no-cache-dir -r requirements.txt

# 5. Copy application code
COPY . .

# 6. Define the default command
CMD ["python", "app.py"]

4. FROM: Select a base image

FROM is the first instruction in a Dockerfile; it specifies the base image for the build.

(1) Base Image Selection Strategy

Strategy Image Size Use Case
Official Language Mirror python:3.12-slim 155 MB Python Project
Alpine variant python:3.12-alpine 50 MB Disk space extremely low
Multi-stage build golang:1.22alpine 12 MB Go/Rust compiled languages
Minimalist OS debian:bookworm-slim 74 MB Requires a custom environment

▶ Example: The Simplest Dockerfile (Difficulty: ⭐)

DOCKERFILE
# Minimal Dockerfile: just prints hello
FROM alpine:3.19
CMD ["echo", "Hello from Docker!"]
BASH
# Build and run
docker build -t hello:1.0 .
docker run --rm hello:1.0
💻 Output:

TEXT
Hello from Docker!

5. RUN: Execute the build command

RUN Executes a command during the build, and writes the result to a new image layer.

(1) Two Formats

Format Syntax Features
Shell format RUN apt-get install nginx Executes /bin/sh -c by default; supports pipes
Exec Mode RUN ["apt-get", "install", "nginx"] Execute directly without launching a shell

▶ Example: RUN to install dependencies (Difficulty: ⭐⭐)

DOCKERFILE
# Best practice: combine RUN commands to reduce layers
FROM debian:bookworm-slim

RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        curl \
        nginx && \
    rm -rf /var/lib/apt/lists/*
📌 Key Point: Combine multiple apt-get commands into a single RUN to reduce the number of image layers. rm -rf /var/lib/apt/lists/* clears the APT cache to reduce the image size.

(2) Principles of RUN Chain Merging

Method Result Notes
Multiple RUN Each entry creates a layer Many layers, large file size
Merge RUN && One layer per track Few layers, compact size
Clear Cache rm -rf apt/lists Clears cache within the same level
Clean separately Next RUN rm Invalid—the previous layer has cured
DOCKERFILE
# Bad: creates 2 layers, apt cache is baked into layer 1
RUN apt-get update
RUN apt-get install -y nginx

# Good: creates 1 layer, cache cleaned in the same layer
RUN apt-get update && \
    apt-get install -y nginx && \
    rm -rf /var/lib/apt/lists/*

6. CMD and ENTRYPOINT

Both CMD and ENTRYPOINT define the commands to be executed when the container starts, but they behave differently.

(1) Comparison of Three Startup Commands

Dimension CMD ENTRYPOINT
Purpose Provide default commands Define a fixed entry point
Can be overridden docker run Parameter overrides directly Requires --entrypoint to override
Combination Can be used with ENTRYPOINT Can be used with CMD command-line arguments
Multiple Only the last one takes effect Only the last one takes effect

(2) The Three Formats of CMD

Format Syntax Recommendation Level Description
Exec Format CMD ["python", "app.py"] ⭐⭐⭐ Executes directly; signals are passed correctly
Shell format CMD python app.py As a child process of /bin/sh -c, SIGTERM is not passed
Parameter Format CMD ["--port", "8080"] ⭐⭐ Use with ENTRYPOINT

▶ Example: The Difference Between CMD and ENTRYPOINT (Difficulty: ⭐⭐)

DOCKERFILE
# Dockerfile with CMD: command can be easily overridden
FROM alpine:3.19
CMD ["echo", "Hello default"]
BASH
# Default: runs CMD
docker run --rm test-cmd
# Output: Hello default

# Override CMD with custom command
docker run --rm test-cmd echo "Custom message"
# Output: Custom message
DOCKERFILE
# Dockerfile with ENTRYPOINT: command stays, args are appended
FROM alpine:3.19
ENTRYPOINT ["echo"]
CMD ["Hello default"]
BASH
# Default: runs ENTRYPOINT + CMD
docker run --rm test-entry
# Output: Hello default

# Append arguments (don't override ENTRYPOINT)
docker run --rm test-entry "Custom message"
# Output: Custom message

# Override ENTRYPOINT (rarely needed)
docker run --rm --entrypoint sh test-entry -c "ls /"

▶ Example: ENTRYPOINT + CMD Combination (Difficulty: ⭐⭐⭐)

This is the best practice approach—ENTRYPOINT specifies the program to run, and CMD provides the default arguments:

DOCKERFILE
# Entrypoint + CMD pattern
FROM python:3.12-slim
WORKDIR /app
COPY app.py .
ENTRYPOINT ["python", "app.py"]
CMD ["--host", "0.0.0.0", "--port", "5000"]
BASH
# Default: uses CMD arguments
docker run --rm myapp
# Equivalent to: python app.py --host 0.0.0.0 --port 5000

# Override just the arguments
docker run --rm myapp --port 8080
# Equivalent to: python app.py --port 8080

7. Setting Up the Context and .dockerignore

(1) Setting the Context

The last parameter of docker build, ., does not refer to the "current directory," but rather to the build context—the Docker Client sends all files in this directory to the Daemon.

100%
graph LR
    CTX["Build Context<br/>(. Table of Contents)"] -->|Send File| D["Docker Daemon"]
    D -->|Dockerfile in COPY/ADD| IMG["Image Layers"]
⚠️ Note: If there is 1 GB of node_modules in the directory, 1 GB will be sent to the daemon during the build (even if the Dockerfile does not COPY it). .dockerignore can exclude unnecessary files.

▶ Example: How .dockerignore Works (Difficulty: ⭐⭐)

TEXT
# .dockerignore - exclude files from build context
node_modules
.git
__pycache__
*.pyc
.env
Dockerfile
docker-compose*.yml
README.md
.vscode

(2) Why should the COPY directive for dependency files come before the COPY directive for source code?

DOCKERFILE
# Good: dependency change rate < code change rate
# When code changes, dependency layer uses cache
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .

# Bad: any file change invalidates the pip install layer
COPY . .
RUN pip install -r requirements.txt
Change Scenario Good Version Bad Version
Modify code only ✅ Use pip cache (seconds) ❌ Rebuild pip (minutes)
Change Dependencies ✅ Rebuild pip layer (required) ❌ Rebuild pip layer (as above)

8. Common Parameters for docker build

Parameter Function Example
-t Image Name: Tag -t myapp:1.0
-f Specify Dockerfile path -f Dockerfile.prod .
--build-arg Pass build parameters --build-arg VERSION=2.0
--no-cache Do not use cache --no-cache
--target Build to a specified stage --target builder
--platform Specify Target Platform --platform linux/arm64

▶ Example: Build an image and view the layer history (Difficulty: ⭐⭐)

BASH
# Build with tag
docker build -t myapp:1.0 .

# View image layers
docker history myapp:1.0

9. Complete Example: Writing a Dockerfile for a Flask Application

DOCKERFILE
# ============================================
# Dockerfile for a Flask web application
# Demonstrates: FROM, WORKDIR, COPY, RUN, CMD
# ============================================

# Use official Python slim image
FROM python:3.12-slim

# Set working directory inside container
WORKDIR /app

# Copy dependency file first (cache optimization)
COPY requirements.txt .

# Install dependencies (clean cache in same layer)
RUN pip install --no-cache-dir -r requirements.txt

# Copy application source code
COPY . .

# Expose the application port (documentation only)
EXPOSE 5000

# Run the Flask application
CMD ["python", "app.py"]
BASH
# Build the image
docker build -t flask-app:1.0 .

# Run the container
docker run -d -p 5000:5000 --name my-flask flask-app:1.0

# Test the application
curl http://localhost:5000

# View image size and layers
docker images flask-app
docker history flask-app:1.0
💻 Output (excerpt):

TEXT
# docker images flask-app
REPOSITORY   TAG   IMAGE ID       SIZE
flask-app    1.0   a1b2c3d4e5f6   180MB

# docker history flask-app:1.0
IMAGE          CREATED        CREATED BY                          SIZE
a1b2c3d4e5f6   5 seconds ago  CMD ["python" "app.py"]             0B
<missing>      5 seconds ago  COPY . .                            2.5kB
<missing>      5 seconds ago  RUN pip install --no-cache-dir...   45MB
<missing>      5 seconds ago  COPY requirements.txt .             58B
<missing>      5 seconds ago  WORKDIR /app                        0B

❓ FAQ

Q Why should we COPY dependency files first and COPY source code later?
A To take advantage of the layer caching mechanism. Dependencies change much less frequently than code. By COPYing the dependency files and installing them first, this layer is cached; when only the code is modified later, the dependency layer directly uses the cache, reducing build time from minutes to seconds. If all files were COPYed first, any file change would invalidate the pip install layer cache.
Q Can CMD and ENTRYPOINT be used together?
A Yes, this is the recommended approach. ENTRYPOINT defines a fixed executable (such as python app.py), while CMD provides default arguments (such as --port 5000). Arguments passed to docker run override CMD but not ENTRYPOINT, enabling a "fixed executable + flexible arguments" setup.
Q What does “build context” mean?
A The . at the end of the docker build command specifies the build context directory. The Docker client packages all files in this directory and sends them to the daemon. The COPY and ADD commands in the Dockerfile can only reference files within the build context. Use .dockerignore to exclude unnecessary files, which speeds up the build and reduces the size of the build context.
Q How do I troubleshoot a build failure?
A Follow these three steps: ① Review the error message—Docker will indicate the failed command and line number; ② Check the context—verify that the file specified by COPY exists; ③ Interactively debug—docker run -it <last-successful-layer> bash enter the last successful image layer to manually troubleshoot.
Q What should I do if a line in RUN is too long?
A Use \ to continue the line and && to chain commands. This is the standard way to write a Dockerfile, which reduces the number of layers while maintaining readability. For example: RUN apt-get update && \ + apt-get install -y nginx && \ + rm -rf /var/lib/apt/lists/*.

📖 Summary


📝 Exercises

  1. Basic Problem (Difficulty ⭐): Write a Dockerfile for a Node.js application, using node:20-alpine as the base image, npm install to install dependencies, and node server.js to start the application.
  2. Advanced Problem (Difficulty ⭐⭐): Build an image and run a container. Use docker history to analyze the size of each layer in the image, identify the largest layer, and explain why.
  3. Challenge (Difficulty: ⭐⭐⭐): Create a .dockerignore file to exclude node_modules and .git, and compare the difference in build context size with and without the .dockerignore file (Hint: Look for the line "Sending build context to Docker daemon" in the docker build output).
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏