Dockerfile 构建镜像
前两篇跑的都是别人造好的盒子(nginx、hello-world)。自己的项目怎么变成镜像?答案就是 Dockerfile——一份"造盒子说明书",docker build 照着它一步步把你的代码+依赖+启动方式封进镜像。01 篇的盒子比喻到这里闭环:之前是买盒子,现在自己造盒子。
一、第一个 Dockerfile:把 http-server 装进盒子
01 篇里本地跑网页的方式是 python -m http.server 8080,现在把"一个静态网页 + 这个服务"打成镜像。项目目录长这样:
my-site/
├── Dockerfile
├── index.html
└── style.css
Dockerfile 内容:
FROM python:3.12-slim
WORKDIR /app
COPY . .
EXPOSE 8080
CMD ["python", "-m", "http.server", "8080"]
五行,每行一个动作:
| 指令 | 作用 | 备注 |
|---|---|---|
FROM |
基础镜像,一切从它出发 | slim 是精简版(约 50MB),完整版 1GB——基础镜像直接决定成品体积 |
WORKDIR |
设定工作目录 | 后续指令都在这个目录里执行 |
COPY . . |
把宿主机当前目录拷进镜像 | 第一个 . 是宿主机,第二个是容器内 |
EXPOSE 8080 |
声明容器监听的端口 | 只是"说明书注释",真正开放端口还要靠 run 时的 -p |
CMD |
容器启动时执行的命令 | 一个 Dockerfile 只有一条生效的 CMD |
构建并运行:
docker build -t my-site:1.0 .
docker run -d -p 8080:8080 my-site:1.0
访问 http://localhost:8080——你的网页已经在盒子里了,拷到任何装 Docker 的机器上 run 一下就活。01 篇开头那个"环境不同 依赖不同"的部署难题,到此终结。
二、常用指令全家桶
FROM ubuntu:22.04 # 基础镜像(起点)
ENV TZ=Asia/Shanghai # 环境变量(构建期+运行期都在)
ARG VER=1.0 # 仅构建期的变量
RUN apt-get update && apt-get install -y curl # 构建时执行命令(装依赖)
COPY app/ /app/ # 拷贝文件/目录
ADD dist.tar.gz /opt/ # 类似 COPY,但会自动解压 tar(少用,语义混)
WORKDIR /app # 切换工作目录
USER appuser # 切换运行用户(安全加分项)
EXPOSE 8000 # 声明端口
ENTRYPOINT ["python"] # 固定的主命令
CMD ["-m", "http.server"] # 默认参数(可被 docker run 后面的参数覆盖)
CMD vs ENTRYPOINT
初学阶段记一条:写 CMD 就够用了。两者区别是 ENTRYPOINT 写死主程序、CMD 只给默认参数(docker run myimg --port 9000 能覆盖 CMD,覆盖不了 ENTRYPOINT)。细节等踩到再加。
三、分层与缓存:为什么指令顺序很重要
Docker 镜像是一层层叠出来的:Dockerfile 每条指令生成一层,且每层有缓存——指令和上下文没变就直接复用上次的层,没变的部分不用重跑。
这就是为什么讲究的 Dockerfile 都把"不容易变的放前面,经常变的放最后":
# 推荐写法:装依赖(不常变)在前,拷代码(天天变)在后
COPY requirements.txt .
RUN pip install -r requirements.txt # ← 依赖没变时这层永远走缓存
COPY . . # ← 只有代码变了这个层才重建
反着写的话,改一行代码就重装一遍依赖,构建从 3 秒变 3 分钟。
配套的 .dockerignore 让 COPY . . 别把没用的东西拷进去(作用同 .gitignore):
node_modules
.git
*.log
四、镜像瘦身两板斧
- 换小基础镜像:
python:3.12(1GB 级)→python:3.12-slim(百 MB 级)→python:3.12-alpine(几十 MB 级); - RUN 合并+清理:
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*一行写完——每条 RUN 是独立一层,中间产生的缓存不会被下一条清掉,必须在同一条里删。
到这一步,单个盒子从造到跑已经全通。但真实服务往往不止一个盒子——网站 + 数据库 + 缓存,总不能一条条 docker run 手敲吧?先补一块拼图(数据怎么留住、盒子之间怎么通信):数据卷与网络,然后 Compose 编排实操 把多盒子一键拉起来。
💬 评论