Docker 第五天学习指南:初识 Dockerfile
一、学习目标
-
理解 Dockerfile 是什么以及它的作用
-
掌握 Dockerfile 核心指令:
FROM、WORKDIR、COPY、RUN、CMD -
编写第一个 Dockerfile(基于 Python Flask 应用)
-
使用
docker build构建镜像
二、什么是 Dockerfile?
Dockerfile 是一个纯文本文件,里面包含了一系列构建镜像的指令。Docker 可以读取这些指令并自动构建出一个镜像。
Dockerfile 的作用
-
自动化构建:将应用及其运行环境固化为代码(Infrastructure as Code)
-
可重复性:任何人只要拿到 Dockerfile,就能构建出完全相同的镜像
-
版本控制:Dockerfile 可以像源代码一样提交到 Git 仓库
一个简单的例子
# 使用 Python 官方镜像作为基础
FROM python:3.9-slim
# 设置工作目录
WORKDIR /app
# 复制当前目录下的所有文件到容器内的 /app
COPY . /app
# 安装 Python 依赖
RUN pip install -r requirements.txt
# 声明容器启动时运行的命令
CMD ["python", "app.py"]
三、核心指令详解
1. FROM —— 指定基础镜像
作用:每个 Dockerfile 的第一条指令必须是 FROM,它指定了构建所使用的“地基”镜像。
# 语法
FROM <镜像名>[:<标签>]
# 示例
FROM ubuntu:22.04 # 基于 Ubuntu 22.04
FROM python:3.9-slim # 基于精简版 Python 3.9
FROM alpine:latest # 基于 Alpine Linux(极小体积)
FROM scratch # 从零开始(用于静态二进制文件)
最佳实践:尽量选择官方镜像,并指定具体标签(如 python:3.9-slim),不要使用 latest(不可复现)。
2. WORKDIR —— 设置工作目录
作用:为后续的 RUN、CMD、COPY 等指令设置当前工作目录。如果目录不存在,会自动创建。
# 语法
WORKDIR <路径>
# 示例
WORKDIR /app # 绝对路径
WORKDIR /usr/local/src
WORKDIR app # 相对路径(相对于上一个 WORKDIR)
# 可以多次使用,支持相对路径
WORKDIR /home
WORKDIR myapp # 最终路径 = /home/myapp
最佳实践:使用绝对路径,避免混淆。先 WORKDIR 再 COPY,可以简化复制路径。
3. COPY —— 复制文件
作用:将构建上下文(通常是 Dockerfile 所在目录)中的文件/目录复制到镜像中。
# 语法
COPY <源路径>... <目标路径>
# 示例
COPY . /app # 将当前目录所有文件复制到 /app
COPY requirements.txt /app/requirements.txt
COPY --chown=www-data:www-data index.html /var/www/
COPY --from=builder /output /app # 多阶段构建中从上一阶段复制
# 通配符
COPY hom* /mydir/
COPY abc?.txt /mydir/
注意:
-
源路径必须在构建上下文中(即不能
COPY ../xxx) -
目标路径可以是绝对路径,也可以是相对
WORKDIR的相对路径 -
如果目标路径不存在,Docker 会自动创建
4. RUN —— 执行命令
作用:在构建过程中执行命令,通常用于安装软件、配置环境。每个 RUN 都会在镜像中创建一个新层。
# 两种语法形式
# shell 形式(默认使用 /bin/sh -c)
RUN apt-get update && apt-get install -y curl
# exec 形式(推荐,不会启动 shell 解析)
RUN ["apt-get", "update"]
# 示例
RUN pip install flask redis
RUN npm install --production
RUN mkdir -p /app/data && chown 1000:1000 /app/data
# 多行命令(使用 \ 换行,提高可读性)
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
curl \
git && \
rm -rf /var/lib/apt/lists/*
最佳实践:
-
将多个
RUN合并为一个(减少层数,缩小镜像体积) -
安装完成后清理临时文件(如
apt-get clean) -
使用
&&确保命令失败时构建失败
5. CMD —— 容器启动默认命令
作用:指定容器启动时执行的默认命令。每个 Dockerfile 只能有一个 CMD,最后一个生效。CMD 可以被 docker run 后面的命令覆盖。
# 三种形式
# 1. exec 形式(推荐)
CMD ["python", "app.py"]
# 2. 作为 ENTRYPOINT 的默认参数
CMD ["--port", "8080"]
# 3. shell 形式
CMD python app.py
# 示例
CMD ["nginx", "-g", "daemon off;"]
CMD ["flask", "run", "--host=0.0.0.0"]
与 RUN 的区别:
-
RUN在 构建时 执行,结果保存到镜像中 -
CMD在 运行时 执行,只是默认命令,可以被覆盖
补充指令(了解即可,后面会深入学习)
| 指令 | 作用 |
|---|---|
ADD | 类似 COPY,但支持 URL 下载和自动解压 tar(不推荐优先使用) |
ENV | 设置环境变量,构建和运行时都存在 |
ARG | 构建参数,只在构建时有效 |
EXPOSE | 声明容器监听端口(只是文档,不会真正暴露) |
ENTRYPOINT | 与 CMD 类似,但不会被覆盖,常与 CMD 配合使用 |
四、实战:为一个 Flask 应用编写 Dockerfile
1. 准备 Flask 应用
首先创建一个新目录,并在其中创建以下文件:
目录结构:
my-flask-app/
├── app.py
├── requirements.txt
└── Dockerfile
app.py(简单的 Flask Web 应用):
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello from Docker! I am running inside a container.'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
requirements.txt:
flask==2.3.2
2. 编写 Dockerfile
在同一目录下创建 Dockerfile(无扩展名):
# 1. 指定基础镜像
FROM python:3.9-slim
# 2. 设置工作目录
WORKDIR /app
# 3. 复制依赖文件(先复制 requirements.txt,利用缓存)
COPY requirements.txt .
# 4. 安装 Python 依赖
RUN pip install --no-cache-dir -r requirements.txt
# 5. 复制应用源代码
COPY app.py .
# 6. 声明容器运行时监听的端口(仅文档作用)
EXPOSE 5000
# 7. 定义容器启动时运行的命令
CMD ["python", "app.py"]
逐行解释:
-
FROM python:3.9-slim:使用官方精简版 Python 镜像,体积小,适合生产。 -
WORKDIR /app:所有后续命令都在/app目录下执行。 -
COPY requirements.txt .:只复制依赖文件,这样在代码修改时,只要requirements.txt没变,Docker 就会使用缓存的依赖安装层,加速构建。 -
RUN pip install --no-cache-dir -r requirements.txt:安装依赖,--no-cache-dir可以减小镜像体积。 -
COPY app.py .:复制应用代码。 -
EXPOSE 5000:告诉使用者这个容器会监听 5000 端口。 -
CMD ["python", "app.py"]:启动 Flask 应用。
3. 构建镜像
在 my-flask-app 目录下执行:
docker build -t my-first-flask-app .
参数说明:
-
-t my-first-flask-app:给镜像起一个名字(tag) -
.:构建上下文路径(当前目录),Docker 会把这个目录下的所有文件发送给守护进程
构建过程输出示例:
[+] Building 15.2s (10/10) FINISHED
=> [1/5] FROM python:3.9-slim
=> [2/5] WORKDIR /app
=> [3/5] COPY requirements.txt .
=> [4/5] RUN pip install --no-cache-dir -r requirements.txt
=> [5/5] COPY app.py .
=> exporting to image
=> => naming to docker.io/library/my-first-flask-app
4. 运行镜像并测试
# 运行容器,映射宿主机 5000 端口到容器 5000 端口
docker run -d --name my-flask-container -p 5000:5000 my-first-flask-app
# 检查容器是否运行
docker ps
# 访问应用(使用 curl 或浏览器)
curl http://localhost:5000
# 应该输出:Hello from Docker! I am running inside a container.
5. 查看日志和停止容器
# 查看容器日志
docker logs my-flask-container
# 停止并删除容器
docker stop my-flask-container
docker rm my-flask-container
五、Dockerfile 编写最佳实践
1. 利用构建缓存
将不经常变化的指令放在前面,经常变化的代码放在后面。
# ✅ 好:依赖放前面,代码放后面
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
# ❌ 不好:复制所有代码在前,导致每次代码修改都要重装依赖
COPY . .
RUN pip install -r requirements.txt
2. 合并 RUN 命令减少层数
# ❌ 不好:多个 RUN,产生多个层
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean
# ✅ 好:合并为一个 RUN
RUN apt-get update && \
apt-get install -y curl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
3. 使用 .dockerignore 文件
创建 .dockerignore 文件,排除不需要的文件(如 __pycache__、.git、venv),加快构建速度并减小镜像体积。
# .dockerignore 内容
__pycache__
*.pyc
.git
.venv
.env
README.md
4. 选择合适的基础镜像
-
生产环境优先选择
-alpine或-slim版本,体积小 -
开发环境可以使用完整镜像(如
python:3.9)
5. 固定版本号
避免使用 latest,指定具体版本(如 python:3.9-slim),确保环境一致性。
6. 以非 root 用户运行(安全性)
默认容器内是 root 用户,最佳实践是创建普通用户。
FROM python:3.9-slim
RUN useradd -m -u 1000 appuser
WORKDIR /app
COPY . .
RUN chown -R appuser:appuser /app
USER appuser
CMD ["python", "app.py"]
六、常用构建命令
# 基础构建
docker build -t 镜像名:标签 .
# 不使用缓存(强制重新构建所有层)
docker build --no-cache -t my-image .
# 指定 Dockerfile 名称(默认是 'Dockerfile')
docker build -f Dockerfile.prod -t my-image .
# 构建时传递构建参数(ARG)
docker build --build-arg VERSION=1.2.3 -t my-image .
# 查看镜像构建历史
docker history my-image
# 查看镜像详情
docker inspect my-image
七、完整示例:多阶段构建(进阶)
对于一个需要编译的应用(如 Go、Java),可以使用多阶段构建来减小最终镜像体积。
# 第一阶段:构建(使用完整编译环境)
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp main.go
# 第二阶段:运行(使用极简基础镜像)
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/myapp .
CMD ["./myapp"]
八、常见问题排查
Q1:构建时提示 COPY failed: file not found
原因:源文件路径错误或文件不存在。
解决:检查源文件是否在构建上下文内,路径是否正确。
Q2:容器启动后立即退出
原因:CMD 指定的命令不是持续运行的(比如直接运行 bash)。
解决:对于前台应用(如 Flask),确保 CMD 是阻塞命令;对于调试,可以先运行 sleep infinity。
Q3:镜像体积过大
解决:
-
使用 Alpine 或 slim 基础镜像
-
合并 RUN 指令并清理临时文件
-
使用多阶段构建
-
检查是否有不必要的文件(使用
.dockerignore)
Q4:pip install 速度慢
解决:使用国内镜像源(在 Dockerfile 中配置):
RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
九、课后练习
练习 1:构建一个静态网站镜像
# 使用 nginx:alpine 作为基础镜像
# 创建 index.html 文件,内容任意
# 复制到 /usr/share/nginx/html
# 构建并运行,通过浏览器访问
解析:
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
练习 2:修改 Flask 应用
-
修改
app.py,增加一个新路由/health,返回OK -
重新构建镜像(无需修改 Dockerfile)
-
重启容器,测试
/health端点
练习 3:添加环境变量
修改 Dockerfile,使用 ENV 指令设置 FLASK_ENV=production,并在 app.py 中打印该环境变量。
十、小结
今天你学会了:
-
Dockerfile 的核心指令及其含义
-
如何为一个 Flask 应用编写 Dockerfile
-
使用
docker build构建镜像 -
运行自己的第一个定制镜像
今日检查清单:
-
理解 Dockerfile 的作用
-
能独立写出一个包含
FROM、WORKDIR、COPY、RUN、CMD的 Dockerfile -
成功构建镜像并运行
-
能通过浏览器或 curl 访问到 Flask 应用的输出
恭喜你完成了第五天的学习!明天我们将深入数据持久化(Volume),解决容器删除后数据丢失的问题。
更多推荐

所有评论(0)