好的,docker-compose 是 Docker 生态中用于定义和运行多容器 Docker 应用程序的核心工具。它非常强大且极其常用,绝对值得一份详细的教程。
docker compose 通过一个 文件(docker-compose.yml)来配置应用服务,然后使用一个简单的命令,就可以从该配置中创建并启动所有服务。
这是一份从入门到实践的详细 docker-compose 使用教程。
docker-compose?
想象一下,你有一个 Web 应用,它至少需要三个组件:
使用 Docker,你需要为每个组件单独运行 docker run ... 命令,并手动管理它们之间的网络连接(--link 或创建 docker network)、数据卷(-v)和环境变量(-e)。这个过程非常繁琐且容易出错。
docker-compose 允许你在一个 文件中声明式地定义所有这些服务、网络和卷,然后用一个命令管理它们的整个生命周期(启动、停止、重建)。
在现代 Docker 安装中(例如通过 Docker Desktop for Mac/Windows/Linux 或在 Linux 上使用官方安装脚本),docker compose (V2) 通常已经包含在内。
1. 验证安装:
xxxxxxxxxx21docker compose version2# 输出: Docker Compose version v2.x.x\2. (如果未安装) 在 Linux 上安装:
如果你的 docker 版本较旧,或安装时未包含该插件,可以手动安装:
方法一 (推荐):使用 Docker 的 apt/yum 仓库
xxxxxxxxxx71# Debian/Ubuntu2sudo apt-get update3sudo apt-get install docker-compose-plugin45# CentOS/RHEL/Fedora6sudo dnf install docker-compose-plugin7# (或 sudo yum install docker-compose-plugin)方法二:手动下载 (备用)
xxxxxxxxxx61# 检查最新版本: https://github.com/docker/compose/releases2DOCKER_COMPOSE_VERSION="v2.23.0" # 示例版本3DEST_DIR="/usr/libexec/docker/cli-plugins"4sudo mkdir -p $DEST_DIR5sudo curl -SL "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-linux-x86_64" -o $DEST_DIR/docker-compose6sudo chmod +x $DEST_DIR/docker-compose
docker-compose.yml
docker-compose 的所有魔力都在这个 文件中。它有几个顶层关键字段:
services: (必需) 定义你的应用包含的各个服务(容器)。volumes: 定义 "命名卷",用于数据持久化。networks: 定义 "自定义网络",用于服务间通信。configs / secrets: 用于管理配置和敏感数据。
docker-compose.yml 结构详解
我们将重点讲解最核心的 services 部分。
x
1# 顶层的 'version' 字段在 V2 中已不再是必需的2# version: '3.8' 34services5 6 # ---------------------------------7 # 服务 1: Web 服务器 (例如 Nginx)8 # "web" 是你自定义的服务名9 # ---------------------------------10 web11 # 方式一: 直接使用 Docker Hub 上的镜像12 image"nginx:latest"1314 # 方式二: 从本地 Dockerfile 构建15 # build:16 # context: . # Dockerfile 所在的目录17 # dockerfile: Dockerfile # Dockerfile 的文件名 (可省略, 默认即为 Dockerfile)1819 # 端口映射 (格式: 宿主机:容器)20 # 将宿主机的 8080 端口映射到容器的 80 端口21 ports22"8080:80"2324 # 卷挂载 (数据持久化或配置文件)25 volumes26 # 方式一: 命名卷 (推荐用于数据)27 # "nginx-data" 是在下面顶层 `volumes:` 中定义的命名卷28nginx-data:/usr/share/nginx/html29 30 # 方式二: 绑定挂载 (Bind Mount) (推荐用于配置文件/代码)31 # 将宿主机的 ./nginx.conf 挂载到容器的 /etc/nginx/nginx.conf32./nginx.conf:/etc/nginx/nginx.conf:ro # "ro" 表示只读 (readonly)3334 # 环境变量35 environment36NGINX_HOST=example.com37NGINX_PORT=8038 39 # 方式二: 从 .env 文件读取环境变量 (最佳实践)40 env_file41./.env.web4243 # 网络配置44 # 将此服务连接到 "app-network" 网络45 networks46app-network4748 # 启动依赖49 # 确保 "db" 和 "api" 服务启动后, 再启动 "web"50 depends_on51db52api5354 # ---------------------------------55 # 服务 2: 数据库 (例如 MySQL)56 # ---------------------------------57 db58 image"mysql:8.0"59 60 # 确保数据库数据持久化61 volumes62db-data:/var/lib/mysql63 64 # 最佳实践: 密码等敏感信息应放在 .env 文件中65 environment66MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD67MYSQL_DATABASE=$MYSQL_DATABASE68 69 networks70app-network71 72 # 健康检查 (高级):73 # `depends_on` 只等待容器启动, 而 `healthcheck` 能等待服务真正可用74 healthcheck75 test"CMD" "mysqladmin" "ping" "-h" "localhost"76 interval10s77 timeout5s78 retries57980 # ---------------------------------81 # 服务 3: 应用 API (示例)82 # ---------------------------------83 api84 build./api # 假设 api 代码和 Dockerfile 在 ./api 目录下85 networks86app-network87 environment88DB_HOST=db # <-- 关键点: 可以直接使用服务名 "db" 作为主机名89DB_USER=root90DB_PASSWORD=$MYSQL_ROOT_PASSWORD9192# ---------------------------------93# 顶层定义: 命名卷94# ---------------------------------95volumes96 nginx-data97 db-data9899# ---------------------------------100# 顶层定义: 自定义网络101# ---------------------------------102networks103 app-network104 driverbridge # 默认即为 bridge
在上面的例子中,api 服务如何连接到 db 服务?
environment: - DB_HOST=db当所有服务都连接到同一个自定义网络(app-network)时,Docker 会自动提供 DNS 服务发现。这意味着 api 容器内可以直接通过服务名 db 作为主机名 (Hostname) 来访问 db 容器的 IP 地址。
这解决了容器 IP 地址不固定的问题,是 docker-compose 的核心优势之一。
.env 文件
永远不要把密码、API 密钥等敏感信息硬编码到 docker-compose.yml 文件中(因为你可能需要把这个文件提交到 Git)。
最佳实践是使用 .env 文件。docker compose 会自动加载与 docker-compose.yml 同目录下的 .env 文件。
1. 创建 .env 文件 (并将其添加到 .gitignore):
xxxxxxxxxx31# .env2MYSQL_ROOT_PASSWORD=my-super-secret-password3MYSQL_DATABASE=my_app_db2. 在 docker-compose.yml 中引用:
xxxxxxxxxx71services2 db3 imagemysql8.04 environment5 # ${...} 语法会从 .env 文件或宿主机环境变量中读取6MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD7MYSQL_DATABASE=$MYSQL_DATABASE
假设你已经在 docker-compose.yml 所在的目录中。
docker compose up
前台启动: docker compose up
(日志会直接输出到当前终端,Ctrl+C 会停止并关闭所有服务)
后台启动 (最常用): docker compose up -d (-d = detached)
(在后台启动并运行所有服务)
强制重建镜像: docker compose up -d --build
(如果你的 Dockerfile 或代码有更改,使用此命令在启动前重新构建镜像)
docker compose down
这会停止并移除所有相关的容器和网络。
重要: 默认情况下,docker compose down 不会删除 volumes: (命名卷)!这是为了保护你的数据。
停止并删除命名卷: docker compose down -v (-v = volumes)
(这个命令会彻底清除所有,包括数据库数据,请谨慎使用!)
docker compose stop
docker compose start
docker compose restart
docker compose ps
docker-compose.yml 管理的所有容器及其状态 (Up/Down)。docker compose logs
docker compose logs -f (-f = follow)docker compose logs -f web (只看 web 服务的日志)
docker compose exec
这是调试的利器,等同于 docker exec。
获取 db 服务的 shell:
x
1docker compose exec db (进入容器后,你可以像在 Linux 中一样执行命令,例如 mysql -u root -p)
在 api 服务中运行数据库迁移 (示例):
1docker compose exec api python manage.py db upgrade
docker compose pull
docker-compose.yml 中定义的所有镜像的最新版本(例如 nginx:latest)。docker compose build
build:)重新构建本地的镜像。docker compose up -d 最后使用该命令重新创建并启动最新版容器
31volumes2 nginx-data3 db-data是 docker-compose.yml 文件中的 "顶层卷声明" (Top-Level Volume Declaration)。
它的作用是告诉 docker compose:“请为我创建并管理两个命名卷 (Named Volumes),一个叫 nginx-data,另一个叫 db-data。”
下面是详细的解释,包括它为什么存在以及如何工作。
volumes?(The Problem)
容器是“短暂”的 (Ephemeral)。
当你运行 docker compose up -d,它会创建容器。当你运行 docker compose down 来停止和移除应用时,这些容器会被彻底销毁。
db) 将数据文件写在容器的 /var/lib/mysql 目录中...web) 允许用户上传图片到容器的 /usr/share/nginx/html/uploads 目录中......那么在 docker compose down 之后,所有这些数据都会永久丢失。
volumes 如何解决问题?(The Solution)
命名卷 (Named Volumes) 就是 Docker 官方推荐的数据持久化方案。
一个“命名卷”是:
db-data)。
docker-compose 中的两部分docker-compose 中使用 volumes 分为两步:
volumes: (你问的)51# docker-compose.yml (文件末尾)23volumes# <--- 这是“顶层卷声明”4 nginx-data# <--- 声明一个叫 "nginx-data" 的命名卷5 db-data# <--- 声明一个叫 "db-data" 的命名卷docker compose:“我需要这两个数据桶,请你帮我创建它们。”docker compose up -d 时,Docker 会检查这两个卷是否存在。如果不存在,它会自动创建它们。/var/lib/docker/volumes/),你不需要关心具体路径,只需要记住它们的名字。
volumes: (如何使用)光声明了“数据桶”还不够,你必须告诉服务(容器)如何去使用它们。这在 services: 内部完成。
221# docker-compose.yml (文件上部)23services4 5 db6 imagemysql8.07 volumes# <--- 这是“服务级卷挂载”8 # 格式: [命名卷名称]:[容器内路径]9db-data:/var/lib/mysql1011 web12 imagenginxlatest13 volumes14nginx-data:/usr/share/nginx/html1516# ... (省略其他配置) ...1718# -----------------------------------19# 这里是第 1 部分的“顶层卷声明”20volumes21 nginx-data22 db-data工作流程解释 (db 服务):
docker compose 看到 db 服务需要挂载一个叫 db-data 的卷。volumes: 声明里查找,发现 db-data 已经被定义了。db 容器时,Docker 会把这个持久化的 db-data 卷(在宿主机上)“挂载”到容器内部的 /var/lib/mysql 路径上。结果:
当 db 容器内的 MySQL 进程向 /var/lib/mysql 写入数据时,这些数据实际上被直接写入到了宿主机上的 db-data 卷中。
使用这种方式,你可以获得:
数据持久化:
当你运行 docker compose down 时,db 容器被销毁,但 db-data 卷依然存在,里面的数据完好无损。
平滑升级:
下一次你运行 docker compose up -d(比如为了升级 MySQL 镜像),docker compose 会创建一个新的 db 容器,并把现有的 db-data 卷重新挂载到新容器的 /var/lib/mysql 目录。你的所有数据都还在。
数据与容器解耦:
你的数据(db-data)和你的应用逻辑(db 容器)的生命周期是分开的,这在运维中至关重要。
这是一个经典的 docker-compose 示例。
1. 创建项目目录:
xxxxxxxxxx21mkdir my-wordpress2cd my-wordpress2. 创建 .env 文件 (用于数据库密码):
xxxxxxxxxx31# .env2DB_ROOT_PASSWORD=somestrongpassword3DB_PASSWORD=wordpress_password3. 创建 docker-compose.yml 文件:
xxxxxxxxxx461# docker-compose.yml2version'3.8'34services5 6 # 服务 1: 数据库7 db8 imagemysql8.09 container_namewordpress-db # (可选) 自定义容器名10 environment11 MYSQL_ROOT_PASSWORD$DB_ROOT_PASSWORD12 MYSQL_DATABASEwordpress13 MYSQL_USERwordpress14 MYSQL_PASSWORD$DB_PASSWORD15 volumes16db-data:/var/lib/mysql17 networks18wp-net1920 # 服务 2: WordPress 应用21 wordpress22 imagewordpresslatest23 container_namewordpress-app24 depends_on25db26 ports27 # 将宿主机的 8000 端口映射到容器的 80 端口28"8000:80"29 environment30 WORDPRESS_DB_HOSTdb # <-- 关键: 使用服务名 "db"31 WORDPRESS_DB_USERwordpress32 WORDPRESS_DB_PASSWORD$DB_PASSWORD33 WORDPRESS_DB_NAMEwordpress34 volumes35 # (可选) 挂载 wp-content 以便保留插件和主题36wp-content:/var/www/html/wp-content37 networks38wp-net3940# 顶层定义41volumes42 db-data43 wp-content4445networks46 wp-net4. 启动应用:
xxxxxxxxxx11docker compose up -d5. 访问:
等待一两分钟让 WordPress 和数据库完成初始化。
在浏览器中打开 http://<你的服务器IP>:8000,你将看到 WordPress 的安装界面。
6. 清理:
当你测试完毕,可以运行:
xxxxxxxxxx51# 停止并移除容器/网络 (保留数据卷)2docker compose down34# (如果确定不要数据了) 彻底清除5docker compose down -v