Dockerfile和自定义镜像
太好了!我们继续用“物流系统”的比喻,来深入讲解Dockerfile和自定义镜像的知识。这就像是学习如何自己设计模具,而不是总是用别人做好的。
Dockerfile是什么?
通俗解释:Dockerfile就像是制作模具的“配方”或“施工图纸”。
- 之前我们都是用别人做好的模具(镜像),比如
nginx、ubuntu。 - 现在,我们想做一个专属模具,比如“一个包含我的Python代码、依赖库和配置文件的专属服务器模具”。
- Dockerfile就是这个模具的详细制作说明书,里面一步一步地写着该怎么制作。
Dockerfile核心语法详解
让我们来看这份“说明书”里最重要的几个指令:
1. FROM– 选择基础模具
比喻: 你要做蛋糕,不会从种小麦开始。你会先买一个现成的蛋糕胚作为基础。FROM就是指定这个“基础蛋糕胚”。
- 语法:
FROM <基础镜像> - 例子:
FROM ubuntu:20.04(基于Ubuntu系统这个模具开始加工)FROM python:3.9(基于一个已经装好Python的模具开始加工,更省事!)
- 重要: 每个Dockerfile必须以
FROM指令开头。你必须基于一个已有的镜像开始构建。
2. RUN– 在制作过程中执行命令
比喻: 在蛋糕胚上进行操作步骤。比如:“RUN 涂抹奶油”、“RUN 放上草莓”。
- 语法:
RUN <命令> - 例子:
RUN apt-get update && apt-get install -y python3(在基于Ubuntu的模具里安装Python3)RUN pip install -r requirements.txt(安装Python依赖包)
- 作用: 执行任何需要在容器内部完成的设置和安装命令。
3. COPY/ ADD– 把文件放进模具里
比喻: 把你的独家秘方(代码、配置文件) 放进蛋糕里。
- 语法:
COPY <源路径> <目标路径> - 例子:
COPY . /app(将当前目录下的所有文件,复制到模具内的/app目录下)COPY requirements.txt /code/requirements.txt(只复制依赖列表文件)
COPYvsADD: 对初学者来说,用COPY就够了。ADD有一些额外功能(如自动解压),但原则是:除非你需要解压,否则一律用COPY。
4. WORKDIR– 设置工作目录
比喻: 指定一个工作台。之后的所有操作(比如RUN, COPY)默认都在这个工作台上进行。
- 语法:
WORKDIR <路径> - 例子:
WORKDIR /app - 好处: 避免了在命令中写很长的绝对路径,让“说明书”更清晰。
5. EXPOSE– 声明端口
比喻: 在模具的设计图上标注出“这里有个服务窗口”。
- 语法:
EXPOSE <端口号> - 例子:
EXPOSE 80 - 注意: 这只是一个声明,方便别人看文档知道这个镜像用什么端口。它并不会自动完成端口映射! 真正的端口映射还是在运行容器时用
-p参数来做。
6. CMD– 容器启动时默认要运行的命令
比喻: 给这个货箱(容器)贴上使用说明书的第一条:“启动后,请自动运行这个程序”。
- 语法:
CMD [“可执行文件”, “参数1”, “参数2”](推荐这种格式,称为exec格式) - 例子:
CMD [“python”, “app.py”] - 重要: Dockerfile中只能有一条
CMD指令,如果有多条,则只有最后一条生效。 RUNvsCMD的区别(核心概念!):RUN是构建镜像(制造模具)时执行的命令,比如安装软件。CMD是启动容器(激活货箱)时执行的命令,比如启动服务器。你可以通过在docker run时指定新命令来覆盖CMD。
7. ENV– 设置环境变量
比喻: 在模具里预设一些全局配置参数,比如“甜度=高”。
- 语法:
ENV <key> <value>或ENV <key1>=<value1> <key2>=<value2> - 例子:
ENV MODE=production
一个完整的Dockerfile例子
假设我们有一个简单的Python Flask应用,项目结构如下:
my-app/
├── app.py # 你的Python代码
├── requirements.txt # 依赖列表
└── Dockerfile # 我们正在写的“模具说明书”
Dockerfile 内容:
# 第一行:选择一个已经装好Python的基础模具,省去自己安装Python的麻烦
FROM python:3.9-slim
# 设置工作目录为 /app,后续操作都在这里进行
WORKDIR /app
# 先把依赖列表文件复制到工作目录
# (这是一个优化技巧,利用Docker的缓存层,如果requirements.txt没变,则不需要重新pip install)
COPY requirements.txt .
# 在构建镜像时执行命令:安装依赖包
RUN pip install --no-cache-dir -r requirements.txt
# 再把当前目录的所有文件(你的代码)都复制到工作目录
COPY . .
# 声明容器运行时提供服务的端口是5000(只是声明,不是映射)
EXPOSE 5000
# 设置一个环境变量
ENV FLASK_APP=app.py
# 指定容器启动时默认运行的命令:启动Flask应用
CMD ["flask", "run", "--host", "0.0.0.0"]

如何“施工”:使用Dockerfile构建自定义镜像
现在,我们有了“施工图纸”(Dockerfile),可以开始制造我们自己的模具了! 命令:docker build -t <你的镜像名> <上下文路径>
-t: 给构建成功的镜像打一个标签(起个名字),格式通常是用户名/镜像名:标签,如果不上传可以随便起,比如my-python-app:v1。.: 这个点代表构建上下文的路径。Docker引擎会把当前目录(包括子目录)的所有文件打包发送给Docker守护进程。所以不要把无关的大文件放在Dockerfile所在目录。可以用.dockerignore文件来忽略不需要的文件(类似.gitignore)。
实际操作:
- 在终端中,进入包含Dockerfile的目录(
my-app/)。 - 执行命令:
docker build -t my-python-app:latest . - 你会看到Docker一步步执行Dockerfile中的指令,每一行都会生成一个临时的“镜像层”。
- 构建成功后,用
docker images查看,你就能看到崭新的my-python-app镜像了!
运行你的自定义镜像:
docker run -d -p 5000:5000 my-python-app:latest
现在,你的自定义应用就在容器中运行起来了!
核心思想总结
- 分层构建: Dockerfile的每条指令都会创建一个新的镜像层。如果Dockerfile没变,重建时会直接使用缓存,极大加快构建速度。这就是为什么我们把变化频率低的操作(如
COPY requirements.txt和RUN pip install)放在前面,变化频率高的操作(如COPY . .复制代码)放在后面。 - 一个容器一个进程: 理想情况下,一个容器只运行一个主进程(由
CMD指定)。这符合Docker的“单一职责”原则。 - 镜像要尽可能小: 选择更小的基础镜像(如
-slim版本),及时清理不必要的缓存文件,让你的“模具”更轻便。
现在,你已经掌握了从“使用者”到“创造者”的关键一步!试着为你自己的项目写一个Dockerfile吧,这是学习Docker最有成就感的部分!
好的!我们继续用“物流系统”的比喻,来讲解Docker容器网络这个既重要又有趣的话题。
为什么需要容器网络?
想象一下我们的物流中心:
- 每个容器都是一个独立的“货箱”
- 这些货箱里运行着不同的服务:有的装数据库,有的装网站前端,有的装后端API
问题来了: 如果这些货箱都完全隔离,互相不知道对方的存在,那网站前端怎么访问后端API?后端又怎么连接数据库? 答案就是:我们需要在货箱之间修建“道路”,让它们能够相互通信! 这就是Docker网络的作用。
Docker网络的三种基本“道路系统”
Docker提供了几种不同的网络模式,就像不同级别的道路:
1. 桥接网络 – “物流中心内部道路”(默认模式)
比喻: 这是Docker自己创建的一个虚拟的内部道路系统。所有连接到这个网络的容器,就像在同一个物流园区里,可以通过内部道路相互访问。
- 特点:
- 容器之间可以互相通信
- 每个容器有自己独立的IP地址(比如172.17.0.2, 172.17.0.3)
- 与外界隔离,安全性较好
- 这是默认模式,如果你不指定网络,容器就会连接到默认的桥接网络
- 实际体验:
# 运行两个容器,它们会自动连接到默认的桥接网络 docker run -d --name web nginx docker run -d --name database mysql # 进入web容器,尝试ping数据库容器 docker exec -it web /bin/bash # 在容器内执行: ping database # 这会失败!因为默认桥接网络不支持容器名解析 ping 172.17.0.3 # 你需要知道数据库容器的实际IP地址
默认桥接网络的问题: 容器之间只能用IP地址通信,不能用容器名,很不方便!
2. 自定义桥接网络 – “带路牌的智能内部道路”(推荐!)
比喻: 我们自己修建一个更智能的内部道路系统,每个路口都有清晰的路牌(容器名)。
- 特点:
- 容器之间不仅可以通过IP通信,还可以直接用容器名相互访问
- 提供了自动的DNS解析服务(容器名自动解析为IP)
- 更好的隔离性(只有加入这个网络的容器才能互相访问)
- 创建和使用:
# 1. 创建一个自定义的桥接网络,命名为"my-app-network" docker network create my-app-network # 2. 运行容器时,明确指定加入这个网络 docker run -d --name web --network my-app-network nginx docker run -d --name database --network my-app-network mysql # 3. 现在进入web容器,可以直接用容器名访问数据库! docker exec -it web /bin/bash ping database # 成功了!因为自定义网络支持DNS解析
这是最常用的方式! 让你的多容器应用可以像在同一个”内部网络”中一样方便地通信。
3. 主机网络 – “直接把货箱放在马路边”
比喻: 跳过所有内部道路,直接把货箱放在公共马路(主机网络) 旁边。容器直接使用主机的网络堆栈。
- 特点:
- 容器没有自己的独立网络空间,直接使用主机的IP和端口
- 性能最好(因为没有网络转换的开销)
- 最不安全(端口冲突风险大)
- 使用方式:
docker run -d --name web --network host nginx这时候,Nginx就直接在主机的80端口上运行,你不需要-p 80:80端口映射了。
4. 无网络 – “完全隔离的封闭货箱”
比喻: 这个货箱没有任何对外的道路,完全封闭。
- 使用场景: 运行一些不需要网络连接的任务,比如数据批处理。
docker run --network none my-batch-job-image
实际场景:搭建一个完整的Web应用
假设我们要搭建一个典型的Web应用:Nginx(前端) + Python应用(后端) + MySQL(数据库)
步骤1:创建专属的“内部道路系统”
docker network create my-app-network
步骤2:启动数据库容器,并加入网络
docker run -d --name mysql-db \
--network my-app-network \
-e MYSQL_ROOT_PASSWORD=123456 \
-v mysql-data:/var/lib/mysql \
mysql:8.0
步骤3:启动后端应用容器,并加入网络
docker run -d --name backend-api \
--network my-app-network \
-v /path/to/your/code:/app \
your-python-app-image
步骤4:启动前端Web服务器,并加入网络
docker run -d --name nginx-frontend \
--network my-app-network \
-p 80:80 \
-v /path/to/nginx.conf:/etc/nginx/nginx.conf \
nginx
通信流程:
- 用户访问
http://你的服务器IP→ 到达 nginx-frontend 容器 - Nginx需要调用API → 它直接访问
http://backend-api:8000(用容器名!) - Python后端需要读写数据 → 它直接连接
mysql-db:3306(用容器名!)
这一切都能正常工作,就是因为所有容器都在同一个自定义网络 my-app-network中!
常用的网络管理命令
# 查看所有网络
docker network ls
# 查看某个网络的详细信息(包括哪些容器连接在上面)
docker network inspect my-app-network
# 将正在运行的容器连接到某个网络
docker network connect my-app-network existing-container
# 将容器从某个网络断开
docker network disconnect my-app-network container-name
# 删除网络(必须没有容器连接在上面)
docker network rm my-app-network
核心思想总结
- 默认桥接网络不好用:容器之间只能用IP地址通信,不能用名字。
- 自定义桥接网络是王道:提供了DNS解析(容器名→IP),让多容器应用开发变得简单。
- 网络就是容器间的”道路”:没有网络,容器就是孤岛;有了网络,它们才能协同工作。
- 安全隔离:不同的自定义网络是相互隔离的,你可以为不同的项目创建不同的网络。
一句话记住Docker网络:它就是为各个”货箱”(容器)修建内部道路,让它们能够用名字相互喊话、协同工作! 现在,你可以尝试创建一个自定义网络,运行几个容器来体验这种便捷的通信方式了!这是构建复杂多容器应用的基础。接下来我们可以聊聊docker-compose,它能让你用一份配置文件轻松管理整个”物流系统”(包括网络、容器、数据卷等)。
太棒了!Docker Compose 是我们 Docker 学习的最后一个重要拼图,它会让一切变得非常简单和优雅。
为什么需要 Docker Compose?
回忆一下我们之前部署多容器应用有多麻烦:
# 1. 创建网络
docker network create my-app-network
# 2. 启动数据库
docker run -d --name mysql-db --network my-app-network -e MYSQL_ROOT_PASSWORD=123456 -v mysql-data:/var/lib/mysql mysql:8.0
# 3. 启动后端
docker run -d --name backend-api --network my-app-network -v /path/to/code:/app your-python-app-image
# 4. 启动前端
docker run -d --name nginx-frontend --network my-app-network -p 80:80 nginx
问题:
- 命令又长又复杂,容易打错
- 要记住很多参数和环境变量
- 重启整个应用很麻烦
- 难以在不同环境(开发、测试、生产)之间保持一致性
Docker Compose 的比喻: 它就像是物流中心的”一键部署控制台”! 有了这个控制台,你不需要手动一个个安排货箱、修道路、接水管。只需要按下一个按钮(docker-compose up),整个物流系统就自动按设计图纸运转起来了。
Docker Compose 的核心:docker-compose.yml 文件
这个 YAML 文件就是我们的“物流中心设计蓝图”,它用人类可读的方式描述了整个多容器应用。
一个完整的 docker-compose.yml 例子
让我们用之前的三件套应用(Nginx + Python + MySQL)来举例:
# 设计蓝图的版本号
version: '3.8'
# 定义所有服务(就是我们的各个"货箱")
services:
# 第一个服务:前端Web服务器
nginx:
image: nginx:latest # 使用哪个模具(镜像)
ports:
- "80:80" # 端口映射:主机80端口 -> 容器80端口
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf # 配置文件挂载
depends_on:
- backend # 声明依赖:先启动backend再启动我
networks:
- app-network # 连接到哪个道路系统
# 第二个服务:后端API
backend:
build: ./backend # 不是用现成镜像,而是根据Dockerfile构建
environment:
- DATABASE_URL=mysql://db:3306/mydb # 环境变量
- DEBUG=true
volumes:
- ./backend:/app # 代码热重载:开发时修改代码自动生效
depends_on:
- db # 依赖数据库
networks:
- app-network
# 第三个服务:数据库
db:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=123456
- MYSQL_DATABASE=mydb
volumes:
- db_data:/var/lib/mysql # 数据持久化
networks:
- app-network
# 定义数据卷(我们的"外接硬盘")
volumes:
db_data:
# 定义网络(我们的"内部道路系统")
networks:
app-network:
driver: bridge
看到了吗?这个 YAML 文件清晰地描述了我们之前用一堆命令才能完成的事情!
Docker Compose 核心语法详解
1. 服务定义(Services)
每个服务相当于一个容器,可以配置:
image: 使用现成的镜像,如nginx:latestbuild: 根据 Dockerfile 构建镜像,如build: ./backendports: 端口映射,格式"主机端口:容器端口"volumes: 数据卷挂载,支持三种格式:- 命名卷:
db_data:/var/lib/mysql(推荐用于数据) - 绑定挂载:
./nginx.conf:/etc/nginx/nginx.conf(推荐用于配置文件) - 匿名卷:
/var/lib/mysql(不推荐)
- 命名卷:
environment: 环境变量,可以用字典或数组格式depends_on: 依赖关系,控制启动顺序networks: 连接的网络
2. 网络定义(Networks)
- 可以创建自定义网络,让服务之间用服务名互相访问
- 如果不指定,Compose 会自动创建一个默认网络
3. 数据卷定义(Volumes)
- 声明需要使用的命名卷,Compose 会自动创建和管理
Docker Compose 常用命令
有了”设计蓝图”,现在来学习”控制台按钮”:
1. 一键部署 – docker-compose up
# 启动所有服务(前台运行,可以看到日志)
docker-compose up
# 后台启动所有服务
docker-compose up -d
# 重新构建镜像并启动(Dockerfile有修改时使用)
docker-compose up --build
就像: 按下”开始运营”按钮,整个物流中心按蓝图自动运转起来。
2. 查看状态 – docker-compose ps
# 查看当前项目的服务状态
docker-compose ps
输出示例:
Name Command State Ports
----------------------------------------------------------------------------
myapp_backend_1 python app.py Up
myapp_db_1 docker-entrypoint.sh mysqld Up 3306/tcp, 33060/tcp
myapp_nginx_1 nginx -g daemon off; Up 0.0.0.0:80->80/tcp
3. 查看日志 – docker-compose logs
# 查看所有服务的日志
docker-compose logs
# 查看特定服务的日志
docker-compose logs backend
# 实时跟踪日志
docker-compose logs -f backend
4. 执行命令 – docker-compose exec
# 在backend服务中执行命令
docker-compose exec backend /bin/bash
docker-compose exec backend python manage.py migrate
5. 停止服务 – docker-compose down
# 停止并删除所有容器、网络(但保留数据卷)
docker-compose down
# 停止并删除所有容器、网络、数据卷
docker-compose down -v
6. 其他实用命令
# 查看服务依赖图
docker-compose config
# 重启服务
docker-compose restart
# 缩放服务实例数(比如启动3个backend实例)
docker-compose up --scale backend=3
实际项目结构示例
一个典型的 Docker Compose 项目目录结构:
my-app/
├── docker-compose.yml # Compose配置文件
├── .env # 环境变量文件(密码等敏感信息)
├── nginx/
│ ├── nginx.conf # Nginx配置
│ └── Dockerfile # 自定义Nginx镜像
├── backend/
│ ├── Dockerfile # 后端Dockerfile
│ ├── requirements.txt
│ └── app.py
└── frontend/
├── Dockerfile # 前端Dockerfile
└── src/
.env 文件示例(用于配置敏感信息):
MYSQL_ROOT_PASSWORD=mysecretpassword
DEBUG=true
DATABASE_URL=mysql://db:3306/mydb
然后在 docker-compose.yml 中使用:
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
开发环境 vs 生产环境
Docker Compose 的一个强大功能是支持多配置文件:
开发环境(使用热重载)
# 使用默认的 docker-compose.yml 和开发环境配置
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up
docker-compose.dev.yml:
version: '3.8'
services:
backend:
volumes:
- ./backend:/app # 代码热重载
environment:
- DEBUG=true
生产环境
# 使用生产环境配置
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
docker-compose.prod.yml:
version: '3.8'
services:
backend:
environment:
- DEBUG=false
- PRODUCTION=true
核心思想总结
- 声明式配置:用 YAML 文件描述”你想要什么”,而不是用命令指定”怎么做到”。
- 一键操作:复杂的多容器应用,现在只需要
docker-compose up和docker-compose down。 - 环境一致性:开发、测试、生产环境使用相同的配置,避免”在我电脑上是好的”问题。
- 服务编排:自动处理网络、数据卷、依赖关系等复杂问题。
一句话记住 Docker Compose:它就是一个”应用级别”的管理工具,让你用一份配置文件就能管理整个多容器应用的生命周期! 现在,你可以尝试为你之前学习的多容器应用创建一个 docker-compose.yml文件,体验一下一键部署的爽快感了!这是从 Docker 初学者到实践者的重要一步。




