Docker Compose
把一整套多容器应用,写进一个配置文件里,然后用一条命令统一启动compose 就是把“多个容器如何一起运行”写到一个 YAML 文件里,再用一条命令统一管理整套应用。第一步不一定非要有 Dockerfile。如果某个服务直接使用现成镜像,比如 nginx、mysql,Compose 文件里写 image: 就够了;只有当你要从自己的源码构建镜像时,才通常会配合 Dockerfile 和 build
文章目录
- 什么是compose?定义“多个容器怎么一起跑”
-
- Compose 默认会给你的应用创建一个网络,每个服务的容器都会加入这个默认网络,并且容器之间可以直接通过服务名互相访问
-
- 容器名(container name)/主机名(hostname)/服务名(service name,Compose 里)
- compose创建的默认网络,主机名,服务名的作用
- Compose 创建的是什么网络
- 和你手动创建网络是什么关系
- --driver:决定“网络是什么类型”,比如 bridge、overlay。容器互联:是“多个容器接到同一个网络里”
- 总结
- Compose 使用的三个步骤
- Compose 安装
- 示例
-
- composetest/app.py
- 创建 Dockerfile 文件
- 创建 compose.yml
- 使用 Compose 命令构建和运行您的应用
- 容器的日志
- 重新启动
- redis 怎么查看
- yml 配置指令参考
-
- build在告诉 Compose,这个服务的镜像要怎么构建
- cap_add,cap_dropcap_add 和 cap_drop 用来给容器增加或移除 宿主机Linux capabilities(内核能力)
- cgroup_parent为容器指定父 cgroup 组,意味着将继承该组的资源限制。
- Compose 里的 command,就是在启动容器时改掉镜像默认的 CMD
- container_name指定自定义容器名称,而不是生成的默认名称。不要让 Compose 自动生成容器名,而是强行把这个容器命名成xx
- depends_on 主要管“启动/停止顺序”,不等于“等对方完全可用再启动”
- deploy: 是“部署策略”配置,主要给编排平台用。
- devices 宿主机上的设备文件映射进容器里
- dns:给这个服务对应的容器,单独指定它要用哪些 DNS 服务器
- dns_search 自定义 DNS 搜索域。可以是单个值或列表。
- entrypoint覆盖容器默认的 entrypoint。
- env_file,env_file 用来把一个或多个环境变量文件里的键值,批量传给这个服务对应的容器
- environment 是在 Compose 里直接给容器设置环境变量
- expose expose 只是在容器网络内部声明/开放端口,不映射到宿主机。暴露给其他服务访问的内部端口
- extra_hosts 是手动往容器里的 /etc/hosts 加“名字 → IP”的映射
- healthcheck:给这个服务定义一个健康检查。Compose 会定期运行检查命令,判断容器是不是 healthy。它的行为和 Dockerfile 里的 HEALTHCHECK 基本一致,默认值也一致。
- image:这个服务启动容器时要用的镜像来源
- logging 用来配置这个服务容器的日志怎么收集、存到哪、怎么轮转
- network_mode 这个服务容器用哪种网络模式
- networks
- restart :容器退出以后,要不要自动再拉起来。
- secrets
- security_opt 给容器补充或修改安全限制
- stop_grace_period
- stop_signal 停止容器时,先发哪个信号。
- sysctls
- tmpfs 在容器里面挂一个“临时内存文件系统”
- ulimits 就是:覆盖这个容器默认的资源上限
- volumes 将主机的数据卷或者文件挂载到容器里。
什么是compose?定义“多个容器怎么一起跑”
把一整套多容器应用,写进一个配置文件里,然后用一条命令统一启动
compose 就是把“多个容器如何一起运行”写到一个 YAML 文件里,再用一条命令统一管理整套应用。



Compose 默认会给你的应用创建一个网络,每个服务的容器都会加入这个默认网络,并且容器之间可以直接通过服务名互相访问
为什么 Compose 也会创建网络,因为 Compose 的目标就是让一组服务一起运行并互相通信。Docker 官方说明,Compose 默认会为你的应用创建一个网络,每个服务对应的容器都会加入这个默认网络,并且能通过服务名互相发现和访问。

容器名(container name)/主机名(hostname)/服务名(service name,Compose 里)



通常说的主机名,不是部署 Docker 的那台真实宿主机名字
主机名的作用是:
👉 让别的容器“找到你在哪”(解析到 IP)
compose创建的默认网络,主机名,服务名的作用
Docker Compose 会创建一个默认网络(bridge 网络),把所有容器都加入到这个网络里( 同一个 compose 里的服务默认都在同一个网络),Docker 的网络驱动会给每个容器分配 IP(但注意:这个 IP 是 动态的每次重启可能变),并自动把 服务名注册到Docker 在容器网络里内置的DNS 服务即:
服务名(redis)
↓ Docker 自动注册
内部 DNS 表
↓
容器 IP (172.x.x.x)
后续各个容器直接可以通过服务名访问


在 Docker 里:不是所有程序都默认监听 0.0.0.0。但在容器里,如果你想“外部/其他容器访问”,通常必须监听 0.0.0.0
在 Docker 网络里:“谁要被访问,谁就必须监听 0.0.0.0”





dns准确的说是,名字->ip的解析器

Docker Compose 中,Docker 自己内置了一个“局域网 DNS”,服务名 → 容器 IP






既然已经可以用 service name 访问了,那主机名有啥用?
hostname 不是给网络用的
你要记住一句非常关键的话:
❗ 网络访问靠 service name(DNS)
❗ hostname 是“容器内部身份”
因为在 Docker Compose 默认模式下:
👉 service name ≈ hostname(经常一样)









宿主机怎么访问容器呢?通过端口映射
在 Docker 里:
👉 宿主机访问容器 = 通过端口映射(port mapping)
而不是 service name,也不是容器 IP




容器网络里只能有一个容器监听 5000,否则 Docker 不知道给谁”?错误





容器怎么访问宿主机?
在 Docker 里:
👉 容器访问宿主机,本质是:走宿主机的“特殊地址”或“网关”
不是 service name



在 Docker 里:
👉 host.docker.internal 是“宿主机的名字(别名)”
👉 host-gateway 是“Docker 自动帮你填的宿主机 IP”












在 Docker 网络里有三种访问方式

容器 → 容器(内部通信):用服务名

宿主机 → 容器:用端口映射

外部网络 → 宿主机 → 容器:用端口映射


Compose 创建的是什么网络

和你手动创建网络是什么关系

–driver:决定“网络是什么类型”,比如 bridge、overlay。容器互联:是“多个容器接到同一个网络里”
–driver 只解决第 1 步里的“这个网络是什么类型”,不等于“容器已经互联了”

总结

Compose 使用的三个步骤





使用 Dockerfile 定义应用程序的环境
第一步不一定非要有 Dockerfile。
如果某个服务直接使用现成镜像,比如 nginx、mysql,Compose 文件里写 image: 就够了;只有当你要从自己的源码构建镜像时,才通常会配合 Dockerfile 和 build: 使用。Docker 官方明确说明,Compose 里的 build 是可选的
使用 docker-compose.yml/compose.yaml 定义构成应用程序的服务,这样它们可以在隔离环境中一起运行
是配置文件,用来写“这套应用要启动哪些服务、每个服务怎么启动、端口怎么映射、卷怎么挂、网络怎么连”。Docker 官方把 Compose 文件定义成用来描述应用的服务、网络、卷等配置
docker-compose.yml 的配置案例
有两个服务:一个是自己从当前目录构建出来的 web,一个是直接用现成镜像的 redis;web 把宿主机当前目录挂到容器 /code,把一个叫 logvolume01 的卷挂到 /var/log,并把容器 5000 端口映射到宿主机 5000。 Compose 默认会给它们放进同一个网络里,服务之间通常可以直接按服务名通信








最后,执行 docker-compose up/docker compose up 命令来启动并运行整个应用程序
执行命令,会读取当前目录里的 Compose 配置,然后把这套应用构建、创建、启动起来
compose.yaml:图纸
docker compose up:按图纸开工
compose.yaml 负责“定义怎么跑”,docker compose up 负责“真正跑起来”
Compose 安装
最简单的官方推荐安装方式是仓库安装,比如 Ubuntu / Debian:
sudo apt-get update
sudo apt-get install docker-compose-plugin
docker compose version
RPM 系发行版则是:
sudo yum update
sudo yum install docker-compose-plugin
docker compose version
这些都是官方当前写法
docker compose version

示例
composetest/app.py



Compose 默认会创建一个网络,每个服务加入后,都可以通过服务名被其他容器访问;容器可以把服务名解析成对应容器的 IP

host=‘redis’,去同一个 Compose 网络里,找名字叫 redis 的那个服务
详情见:compose创建的默认网络,主机名,服务名的作用 章节
创建 Dockerfile 文件
Dockerfile 通常不需要后缀。
标准写法就是文件名直接叫:
Dockerfile
不是:
Dockerfile.txt
Dockerfile.docker
Dockerfile.yml
默认推荐文件名就是 Dockerfile,没有后缀;如果你改了名字,就要用 -f 指定
为什么没有后缀也可以? 因为 Docker 默认找的就是这个固定名字: Dockerfile 所以你执行: docker build .
时,Docker 会默认在当前目录找这个文件。
如果你不用这个名字,也不是不行。 比如你写成: mydockerfile Dockerfile.dev Dockerfile.test
那构建时要手动指定: docker build -f Dockerfile.dev . 这里 -f 就是告诉 Docker: 这次用哪个
Dockerfile。




apk 是 Alpine Linux 的包管理器命令,相当于:
Ubuntu / Debian 里的 apt
CentOS / RHEL 里的 yum / dnf
Alpine 官方文档直接说明了,apk 是 Alpine Package Keeper,用来管理 Alpine 系统里的软件包





默认情况下,COPY . . 会把当前 build context 里的所有文件再复制一遍,其中也包括 requirements.txt


COPY . . COPY requirements.txt requirements.txt 重复了吧,为什么不只保留COPY . .
先单独 COPY requirements.txt,再 pip install,最后 COPY . .,是为了让“安装依赖”这一层尽量复用缓存;不是重复,而是优化构建速度



某一层一旦变了,后面所有层都要重新执行
Docker 看的是层有没有变,某一层一旦变了,后面所有层都要重新执行


Docker 缓存:“上次 build 做过、而且结果还能复用的那些中间结果。每一条指令都会变成最终镜像中的一层
上次 build 做过、而且结果还能复用的那些中间结果。”
Docker 官方说得很直接:Dockerfile 里的每一条指令都会变成最终镜像中的一层;当你再次构建同一个镜像时,只要某一层没有变化,Docker 就可以直接复用那一层,不必重新执行


创建 compose.yml
services:
web:
build: .
ports:
- "5000:5000"
redis:
image: redis:alpine
:用 Compose 一次启动两个服务:一个你自己构建的 web,一个现成的 redis。 Compose 会默认给这两个服务创建一个应用网络,服务之间可以直接用服务名互相访问



使用 Compose 命令构建和运行您的应用
docker compose up/docker-compose up
般要在 docker-compose.yml 或 compose.yaml 所在的目录执行 docker compose up
因为 docker compose up 默认会在当前目录查找 Compose 配置文件。Docker 官方当前说明,默认会查找 compose.yaml、compose.yml、docker-compose.yaml、docker-compose.yml
如果你不在这个目录里,也不是完全不能执行。可以显式指定文件






执行 docker compose up 之后,当前窗口会持续显示各个容器打印出来的输出。默认 attached mode 下,你会看到所有容器的日志


docker compose up -d

-d 之后怎么看日志,docker compose logs


容器启动后,只要程序还在往标准输出/标准错误里打印内容,这些都算容器日志
docker compose logs
看到的是:
从容器启动以来,到你执行这条命令这一刻为止的日志。

容器的日志
docker compose logs 看的主要就是容器进程写到 STDOUT(标准输出) 和 STDERR(标准错误) 的内容。 Docker 官方对
docker logs 的说明就是“获取容器的 STDOUT 和 STDERR 日志”,而 docker compose up
默认也是把各服务的输出聚合显示出来
主要能看到容器的标准输出和标准错误;Docker 默认使用日志驱动记录容器日志,默认通常是 json-file。如果程序只把日志写进容器内文件,而不输出到 stdout/stderr,那 docker logs / docker compose logs 一般看不到


单个容器查看日志:docker logs <容器名或容器ID>/docker logs -f <容器名或容器ID>


Compose 启动的一组容器
把这一组容器的日志一起看,用docker compose logs/docker compose logs -f


只看其中某一个服务的日志:docker compose logs/docker compose logs -f 服务名

容器日志:某一个具体容器的日志,服务日志:某个服务下面一个或多个容器的日志,Compose 帮你按服务名聚合着看
service 定义是:服务是一个抽象定义,背后由一组容器支撑。
Compose 看的是“服务”的日志
但这些日志来源仍然是服务对应容器的 stdout/stderr
Compose 不是不能看容器日志,而是它默认帮你按“服务”把容器日志组织起来看;单个容器的精确日志则更适合用 docker logs。





2者看起来有区别的例子



重新启动
docker compose restart/docker compose restart 服务名 重新启动现有服务/某个服务不会重新构建镜像



docker compose down 连容器一起删掉,再重新创建启动
把这套 Compose 应用停掉,并且把它创建出来的容器和网络删除掉。 Docker 官方对 down 的定义就是:停止容器并移除容器、网络。默认也会移除 Compose 项目里定义的默认网络
命名卷默认不会被删
匿名卷默认也不会被删
但如果你加 -v,卷也会一起删掉。Docker 官方对 down --volumes 的说明就是移除命名卷和匿名卷
docker compose down = 停掉并拆掉这套 Compose 应用的运行环境,但默认不删镜像,也不删卷







docker compose down --rmi local/–rmi all 把相关镜像也删掉



–rmi local,“tag”理解成镜像的“名字:版本”




docker compose down -v,docker compose rm -v,删除卷

查看卷 docker volume ls,docker volume inspect <卷名>

docker volume inspect 就是看“这个卷叫什么、什么时候建的、数据实际存在哪”



删除卷
如果你知道卷名,直接删:
docker volume rm <卷名>
这个是“删除一个或多个卷”。正在被容器使用的卷不能直接删,必要时可以加 -f 强制
docker volume prune
它会删除未使用的本地卷;默认只删匿名卷


代码或 Dockerfile 改了,要重新构建再启动,docker compose up -d --build
第一,先重新构建需要 build 的镜像。
这里的 --build 表示在启动容器前先 build 镜像。
第二,再把容器在后台启动起来。
这里的 -d 是 detached mode,也就是后台运行,不一直占着当前终端。
你可以把它翻成人话:
“我改了代码环境或 Dockerfile,先重新做镜像,再把这套服务启动起来,而且放到后台跑。”
–build = “该重做镜像的我先帮你重做”,不是“你得先自己停掉、删掉再执行”


redis 怎么查看
按 Compose 服务名进去 docker compose exec 服务名redis-cli。按具体容器名进去 docker exec -it <实际容器名/容器id> redis-cli
docker compose exec redis redis-cli
= 在 redis 服务 对应的容器里执行 redis-cli
docker exec -it 容器名 redis-cli
= 在 某个具体容器 里执行 redis-cli
docker exec -it 容器ID redis-cli
= 同上,只是把容器名换成了容器 ID






KEYS * 查看当前数据库里的所有键。看某个键的值 GET

看当前数据库有多少个键:
DBSIZE
看某个键是什么类型:
TYPE hits
删除某个键:
DEL hits
SCAN 0 是 Redis 的游标式遍历键命令


怎么判断redis有没有用卷做持久化
看 Compose 文件里 Redis 服务有没有 volumes:docker compose config


检查容器挂载信息:docker inspect <redis容器名/容器id>

Compose 文件redis没写 volumes:,检查结果里还是出现了 Mounts
没在 Compose 文件里手写 volumes:,但 Redis 还是挂了一个卷。原因是:
Redis 官方镜像自己就在镜像里声明了 /data 是一个 VOLUME。 Dockerfile 里的 VOLUME
会让容器运行时在这个位置创建挂载点;如果你没手动指定卷,Docker 会创建一个匿名卷。Redis
官方镜像文档明确提到,启用持久化时数据存放在 VOLUME /data


redis逻辑数据库
Redis 可以有多个逻辑库
编号从 0 开始
有多少个库是可配置的
很多情况下默认是 16 个,也就是 0 到 15

切换逻辑数据库 select x.怎么看当前 Redis 一共有多少个库 CONFIG GET databases



yml 配置指令参考
build在告诉 Compose,这个服务的镜像要怎么构建


context

dockerfile

args





target

cap_add,cap_dropcap_add 和 cap_drop 用来给容器增加或移除 宿主机Linux capabilities(内核能力)
cap_add / cap_drop 是在调容器的“内核能力清单”,不是简单的文件权限



cgroup_parent为容器指定父 cgroup 组,意味着将继承该组的资源限制。
cgroup 理解成:
Linux 用来管进程资源的一种分组机制






Compose 里的 command,就是在启动容器时改掉镜像默认的 CMD





container_name指定自定义容器名称,而不是生成的默认名称。不要让 Compose 自动生成容器名,而是强行把这个容器命名成xx
container_name 就是手动指定容器名;好处是名字固定好找,代价是这个服务不能方便地扩成多个副本




depends_on 主要管“启动/停止顺序”,不等于“等对方完全可用再启动”
depends_on 默认是“排队启动”,不是“等对方完全可用”;要等可用,得配 healthcheck + condition: service_healthy
不设置:别依赖顺序
设置 depends_on:有顺序
还想等服务真正可用:再加 healthcheck + condition: service_healthy



services:
web:
build: .
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
redis:
image: redis
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
retries: 5
start_period: 30s
timeout: 10s

数据库容器启动后,先给它 30 秒热身;然后每 10 秒检查一次“Postgres 是否已经能接收连接”;单次检查最多等 10 秒;如果连续失败 5 次,就认为它不健康





deploy: 是“部署策略”配置,主要给编排平台用。
在 Compose 规范里它是可选部分;如果当前实现不支持,就会被忽略。在 Docker 的 Swarm 场景里,这一块最有用;而本地普通
docker compose up 下,很多 deploy 字段并不会按你想的那样生效
mode / replicas:决定跑几个实例
endpoint_mode:决定别人怎么访问这组实例
resources:决定资源上限和预留
restart_policy:决定失败后怎么重启
update_config / rollback_config:决定升级和回滚怎么做
devices 宿主机上的设备文件映射进容器里
devices 不是挂普通文件,而是把宿主机真实硬件设备“接”给容器用



dns:给这个服务对应的容器,单独指定它要用哪些 DNS 服务器


daemon.json 管“默认大家都用什么”,Compose 的 dns: 管“这个服务例外用什么”,容器里最终看到的是 /etc/resolv.conf 那一层的结果





dns_search 自定义 DNS 搜索域。可以是单个值或列表。





entrypoint覆盖容器默认的 entrypoint。
在 Compose 里,entrypoint 用来覆盖镜像原本的 ENTRYPOINT
Compose 里的 entrypoint 是用来改“容器启动时固定执行的入口程序”的;字符串形式像直接写一条入口命令,数组形式像把程序和参数一个个写清楚


env_file,env_file 用来把一个或多个环境变量文件里的键值,批量传给这个服务对应的容器
env_file = 从外部文件批量给容器注入环境变量;多个文件时后面的覆盖前面的,environment 又会覆盖 env_file
不用管谁写在前面或后面。
在 Compose 里,只要同一个服务同时设置了 env_file 和 environment,environment 一定优先,官方原话就是:environment has precedence over env_file





.env 文件本身怎么写?

environment 是在 Compose 里直接给容器设置环境变量
environment 就是直接在 Compose 文件里给容器设置环境变量;像 true、false 这种值最好加引号,避免被 YAML 当成布尔值




expose expose 只是在容器网络内部声明/开放端口,不映射到宿主机。暴露给其他服务访问的内部端口
expose 只是在容器网络内部声明/开放端口,不映射到宿主机。
Docker Compose 官方写得很清楚:expose 定义的是容器对外暴露给其他服务访问的内部端口,这些端口不应该发布到宿主机,而且这里只能写容器内部端口



extra_hosts 是手动往容器里的 /etc/hosts 加“名字 → IP”的映射


healthcheck:给这个服务定义一个健康检查。Compose 会定期运行检查命令,判断容器是不是 healthy。它的行为和 Dockerfile 里的 HEALTHCHECK 基本一致,默认值也一致。



CMD 是直接执行“程序 + 参数”,CMD-SHELL 是把整行命令交给 shell 去解释。





image:这个服务启动容器时要用的镜像来源
image = 这个服务启动容器时要用的镜像来源









logging 用来配置这个服务容器的日志怎么收集、存到哪、怎么轮转

driver表示用哪种日志驱动。
Compose 文档里说,logging.driver 指定服务容器使用的日志驱动;可用值跟平台有关。Docker Engine
层面默认日志驱动通常是 json-file。Docker 官方还特别提醒:json-file 默认不做日志轮转,而 local
驱动默认会做轮转,很多场景下更推荐 local

driver: local


driver: json-file 把容器日志保存成 JSON 文件
把容器日志保存成 JSON 文件。这是 Docker Engine 的默认日志驱动。它适合直接用 docker logs / docker compose logs 查看。官方同时提醒,如果不做轮转,日志文件可能越积越大

driver: syslog 把日志发到宿主机或远端的 syslog 系统
把日志发到宿主机或远端的 syslog 系统。Docker 官方说明,syslog 驱动会把日志写到 syslog facility,前提是宿主机上有可用的 syslog 服务

driver: none 完全不记录日志
options表示这个驱动自己的配置项,
不同驱动支持的选项不同。Compose 文档说明,驱动相关选项通过 options 这个键值对来传
即使你没在 Compose 里配置 logging:,docker compose logs 也通常能看到日志,因为 Docker daemon 本身有一个默认日志驱动

日志存到哪?

docker info --format '{{.LoggingDriver}}'
docker inspect -f '{{.HostConfig.LogConfig.Type}}' <容器名或容器ID>
docker inspect -f '{{.LogPath}}' <容器名或容器ID>


删除日志





logging 是“Docker 怎么收集和保存容器日志的配置”,而“容器的日志”通常指容器进程写到 STDOUT/STDERR 的那些输出
容器日志:程序往控制台打出来的内容
logging:Docker 用什么方式接住并保存这些内容


network_mode 这个服务容器用哪种网络模式


network_mode:none


network_mode: “host”

network_mode: “service:[service name]”


network_mode: “container:[container name/id]”

networks


顶层 networks:这是在定义有哪些网络,以及这些网络怎么创建,比如用什么驱动

顶层 networks: 里的 driver 是在指定这张网络用哪种网络驱动创建

服务里的 networks:这是在说:这个服务要接入哪些网络。如果一个服务接入同一网络,才能和这个网络上的其他服务通信

aliases 就是这个服务在某张网络上的别名。

restart :容器退出以后,要不要自动再拉起来。




secrets
先在顶层 secrets 里定义 secret,再在具体服务里通过 services..secrets 去引用它;而且 secret 的访问权限是按服务单独授予的。
在 Compose 里,secrets 会把你本机文件里的内容,挂到容器里的 /run/secrets/<名字>
这个位置,而且是按服务单独授权的;只在顶层定义 secret 还不够,服务里还得显式写 secrets: 才能拿到


security_opt 给容器补充或修改安全限制



stop_grace_period





stop_signal 停止容器时,先发哪个信号。



sysctls
sysctls = 给容器自己的“内核参数空间”调参数;能调的前提是这个参数本身支持命名空间隔离,且不能影响宿主机



tmpfs 在容器里面挂一个“临时内存文件系统”
哪个目录被声明成 tmpfs,往那个目录及其子目录写文件,就是写到 tmpfs





ulimits 就是:覆盖这个容器默认的资源上限




volumes 将主机的数据卷或者文件挂载到容器里。
Docker 把容器外部存储大致分成 bind mounts、volumes、tmpfs;其中 bind mount 是“把宿主机上的某个真实文件或目录挂进容器”,而 volume 是“由 Docker 自己管理的持久化存储目录
volumes: 是 Compose 里声明“挂载”的地方;但具体挂载出来的东西,可能是 bind mount,也可能是 Docker volume





更多推荐





所有评论(0)