Dockerfile 基础语法入门:写你的第一个镜像
这篇文章把 Dockerfile 拆开来讲,从零写出一个能跑的真实镜像。
先说 Dockerfile 是什么
Dockerfile 就是一个配置文件,用来说明"这个镜像要怎么组装"。它告诉 Docker:用什么基础系统、安装什么依赖、复制什么文件、启动时执行什么命令。
写好 Dockerfile,执行 docker build,就得到一个自定义镜像。这个镜像可以在任何装好 Docker 的机器上一键跑起来,环境完全一致。
说起来简单,但里面有些坑,新手容易踩进去。看完这篇,至少能避开大部分。
最小示例:一个 Python 镜像
先看个最简单的例子,感受一下结构:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "main.py"]
这就是一个完整可用的 Dockerfile。每一行都是一条指令,Docker 按顺序执行。
接下来逐条拆解。
核心指令
FROM:起点,决定基础镜像
FROM python:3.11-slim
FROM 是第一条指令,也是唯一必须有的。它指定用什么镜像作为起点。
写法是 镜像名:标签。python:3.11-slim 是 Python 官方提供的镜像,slim 表示精简版,体积比完整版小很多。
常见的基础镜像:
| 镜像 | 适用场景 |
|---|---|
python:3.11-slim | Python 应用 |
node:20-alpine | Node.js 应用 |
openjdk:17-slim | Java 应用 |
nginx:alpine | 静态网站、Web 服务 |
ubuntu:22.04 | 通用 Linux 环境 |
alpine:3.19 | 追求极小体积的基础镜像 |
关于 Alpine:Alpine 是一个超轻量的 Linux 发行版,用它做基础镜像可以让最终镜像体积小很多。但有些软件在 Alpine 上需要额外配置兼容层,新手建议先用 Debian/Ubuntu 系。
RUN:执行命令,安装东西
RUN pip install --no-cache-dir -r requirements.txt
RUN 在镜像构建过程中执行命令。可以安装系统包、下载文件、创建目录等。
这里 pip install 装了 Python 依赖。--no-cache-dir 是不缓存下载文件,能让镜像小一点——这个习惯挺好。
多条 RUN 指令会分层执行,每条 RUN 都会生成一层。所以能用一条 RUN 搞定的事就别拆成多条,减少镜像体积:
# 分开写(不好)
RUN apt-get update
RUN apt-get install -y nginx
RUN apt-get clean
# 合并写(好)
RUN apt-get update && apt-get install -y nginx && rm -rf /var/lib/apt/lists/*
COPY:把文件复制进去
COPY requirements.txt .
COPY . .
COPY 把宿主机的文件复制到镜像里。
第一个 COPY requirements.txt . 把 requirements.txt 复制到当前工作目录。第二个 COPY . . 把当前目录的所有内容都复制进去(. 在 Docker 语境里表示 WORKDIR)。
两个 COPY 的顺序是有讲究的。把不常变化的文件(如依赖文件)放在前面,频繁变化的文件(如源代码)放在后面。这样可以利用 Docker 的缓存层:依赖没变的情况下,pip install 那一步会被复用,不会每次重新安装。
WORKDIR:设置工作目录
WORKDIR /app
WORKDIR 切换当前工作目录,后续的 COPY、RUN、CMD 等指令都在这个目录下执行。
路径不存在会自动创建。建议每个 Dockerfile 都显式设置 WORKDIR,别用绝对路径裸跑。
CMD:容器启动时干什么
CMD ["python", "main.py"]
CMD 指定容器启动时执行的命令。注意写法是 JSON 数组,不是字符串。
一个 Dockerfile 只能有一条 CMD,写多条的话只有最后一条生效。
这里启动 Python 脚本。如果你的应用是 Web 服务,可以写成:
CMD ["python", "-m", "http.server", "8000"]
ENV:设置环境变量
ENV NODE_ENV=production
ENV PORT=8080
ENV 设置环境变量,容器运行期间一直生效。
建议在 Dockerfile 里把环境的默认值设好,不用依赖启动时的 -e 参数传入。
EXPOSE:声明端口
EXPOSE 8080
EXPOSE 只是文档作用,告诉使用者这个镜像会监听哪个端口。实际映射还是要靠 docker run -p 参数。它不实际打开端口,只是元数据。
ARG:构建参数
ARG APP_VERSION=1.0.0
ARG 定义构建时变量,用户可以在 docker build 时传入:
docker build --build-arg APP_VERSION=2.0.0 -t myapp .
和 ENV 不同,ARG 只在构建阶段有效,运行时的容器看不到。
实战:写一个完整的 Dockerfile
做个实际的东西感受一下。假设你的项目结构是这样的:
myapp/
├── Dockerfile
├── requirements.txt
├── main.py
└── static/
└── style.css
main.py 是个简单的 Flask Web 应用:
from flask import Flask, send_from_directory
import os
app = Flask(__name__)
@app.route('/')
def index():
return send_from_directory('static', 'style.css')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 5000)))
对应的 Dockerfile:
FROM python:3.11-slim
# 设置工作目录
WORKDIR /app
# 先复制依赖文件,安装完再复制代码(利用缓存)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制所有代码
COPY . .
# 设置环境变量
ENV PORT=5000
ENV FLASK_APP=main.py
# 暴露端口
EXPOSE 5000
# 启动命令
CMD ["python", "main.py"]
构建镜像:
docker build -t my-flask-app .
运行容器:
docker run -d -p 5000:5000 --name flask-demo my-flask-app
访问 http://localhost:5000 看看效果。
多阶段构建:给镜像瘦身的技巧
写到这里,有个问题:最终镜像里包含了 Python 编译环境、pip 缓存这些运行时不需要的东西。Python 官方镜像 slim 版已经有 150MB+,加上依赖轻轻松松上 300MB。
用多阶段构建可以解决:
# 第一阶段:构建
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --prefix=/install --no-cache-dir -r requirements.txt
# 第二阶段:运行时
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /install /usr/local
COPY . .
ENV PORT=5000
EXPOSE 5000
CMD ["python", "main.py"]
第一阶段装了依赖,第二阶段只复制了依赖目录和代码。最终镜像只有 180MB 左右,比单阶段小很多。
关于多阶段构建更详细的用法,后面会有专门的文章来讲。
几个新手容易踩的坑
坑一:COPY 路径写错
. 和 / 是两个东西。. 是相对于 WORKDIR 的当前目录,/ 是根目录。新手经常把 COPY . . 写成 COPY . /,结果文件全跑根目录去了。
坑二:CMD 和 ENTRYPOINT 混淆
简单理解:CMD 是给 docker run 用的默认参数,ENTRYPOINT 是固定命令,不容易被覆盖。
如果你的容器主要当命令用(比如一个 CLI 工具),用 ENTRYPOINT;如果主要当服务用,用 CMD。
# CMD:覆盖容易
CMD ["python", "app.py"]
# ENTRYPOINT:更像可执行文件
ENTRYPOINT ["python", "app.py"]
坑三:忽视 .dockerignore
和 .gitignore 类似,.dockerignore 可以在 COPY 时排除不需要的文件:
.git
node_modules
__pycache__
*.pyc
.env
*.log
不排除的话,COPY . . 会把整个目录打包进去,包括 .git、node_modules 这些没用的东西,不仅拖慢构建,还可能把敏感信息打包进镜像。
坑四:RUN 命令不清理
apt-get install 之后不清理缓存、pip install 不加 --no-cache-dir,会让镜像膨胀很多。上文提到过,养成好习惯。
构建进阶:给镜像加标签和版本
# 打标签
docker build -t myapp:latest -t myapp:1.0.0 .
# 带构建参数
docker build --build-arg VERSION=1.0.0 -t myapp:1.0.0 .
latest 只是个约定,没有实际含义。建议用语义化版本号管理。
最后
Dockerfile 本身语法不多,多用几次就熟练了。关键是理解每条指令在构建阶段还是运行阶段生效,知道缓存是怎么工作的,知道镜像体积可以怎么优化。
关注不迷路 👆
更多推荐

所有评论(0)