如何在 Ubuntu 上安装和使用 Docker Compose
介绍
Docker 简化了在容器中管理应用程序进程的流程。虽然容器在某些方面类似于虚拟机,但它们更加轻量且资源友好。这使得开发人员能够将应用程序环境拆分为多个独立的服务。
对于依赖多个服务的应用程序来说,协调所有容器的启动、通信和关闭可能会很快变得难以管理。Docker Compose 是一个工具,允许你基于 YAML 文件中设定的定义运行多容器应用环境。它使用服务定义来构建可完全自定义的多容器环境,这些容器可以共享网络和数据卷。
本指南将引导您在 Ubuntu 服务器上安装 Docker Compose 并运行一个简单的容器。之后,您将学习如何使用 WordPress 应用程序和 MySQL 数据库构建多服务环境。我们还将讨论更高级的主题,包括服务扩展、定义自定义网络以及使用模块化包含指令。最后,本文提供了从旧版 docker-compose(v1)迁移到现代 docker compose(v2)的指南,以及关于解决常见问题(如端口冲突和权限错误)的详细说明。
主要要点:
- Docker Compose 是一个工具,用于通过单个 YAML 文件定义和运行多容器应用程序,以配置服务、网络和卷。
- 在 Ubuntu 上安装 Docker Compose,可以通过添加 Docker 官方仓库并使用
apt来安装docker-compose-plugin软件包。 - 现代命令是
docker compose(带空格)。它取代了已弃用的docker-compose(带连字符)v1 工具。 - 使用
docker compose up -d以分离模式启动应用程序,使用docker compose down停止并移除其所有容器和网络。 depends_on指令有助于控制服务的启动顺序,例如确保数据库容器在 Web 应用容器之前启动。- 最常见的问题是“权限被拒绝”(通过将用户添加到
docker组解决)、端口冲突(通过在 YAML 文件中更改主机端口解决)以及 YAML 语法错误(通过更正缩进解决)。 - 您可以使用
docker compose up --scale <service_name>=<number>命令运行服务的多个实例。 include指令允许你将大型的docker-compose.yml文件拆分成更小、更易管理的配置文件。
先决条件
要跟随本文,您需要:
- 以具有sudo权限的非root用户身份访问Ubuntu本地机器或开发服务器。如果您使用的是远程服务器,建议安装有效的防火墙。有关这些设置,请参阅我们的《Ubuntu初始服务器设置指南》。
- 在您的服务器或本地计算机上安装 Docker,按照《如何在 Ubuntu 上安装 Docker – 分步指南》的步骤 1 和 2进行操作。
注意: 从 Docker Compose v2 开始,Docker 已经转向使用 compose CLI 插件命令,而不再使用原来的 docker-compose,如我们在《如何在 Ubuntu 上安装 Docker Compose(逐步指南)》中所述。虽然安装方式有所不同,但实际上使用时,通常只需将 docker-compose 命令中的连字符去掉,变为 docker compose。有关完整的兼容性详情,请查看官方 Docker 文档中关于新 compose 与旧 docker-compose 命令兼容性的说明。
步骤 1 — 安装 Docker Compose
在 Ubuntu 上安装 Docker Compose 有两种方法:
- 使用仓库安装
- 手动安装
我们将在本节中讨论这两种方式。
使用 apt 仓库安装 Docker Compose
首先,让我们设置 Docker apt 仓库。
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update现在,您可以使用以下命令安装 Docker Compose:
sudo apt install docker-compose-pluginDocker Compose 已成功安装在您的系统上。要验证安装是否成功,您可以运行:
docker compose version你将看到类似这样的输出:
Docker Compose version v2.3.3手动安装 Docker Compose
为了确保你获得最新的稳定版本的 Docker Compose,你将从其官方 Github 仓库下载该软件。
首先,确认他们发布页面上可用的最新版本。在撰写本文时,最新的稳定版本是 v2.40.2。
使用以下命令下载:
mkdir -p ~/.docker/cli-plugins/
curl -SL https://github.com/docker/compose/releases/download/v2.40.2/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose接下来,设置正确的权限,以便可以执行 docker compose 命令:
chmod +x ~/.docker/cli-plugins/docker-compose在下一部分,您将看到如何设置一个 docker-compose.yml 文件,并使用此工具启动一个容器化环境。
步骤 2 — 设置 docker-compose.yml 文件
为了演示如何设置 docker-compose.yml 文件并使用 Docker Compose,你将使用来自 Docker Hub(公共 Docker 注册表)的官方 Nginx 镜像创建一个 Web 服务器环境。这个容器化环境将提供一个静态 HTML 文件。
首先在你的主文件夹中创建一个新目录,然后进入该目录:
mkdir ~/compose-demo
cd ~/compose-demo在此目录中,创建一个应用程序文件夹,作为你的 Nginx 环境的文档根目录:
mkdir app使用你喜欢的文本编辑器,在 app 文件夹中创建一个新的 index.html 文件:
nano app/index.html将以下内容放入此文件:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Docker Compose Demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kognise/water.css@latest/dist/dark.min.css">
</head>
<body>
<h1>This is a Docker Compose Demo Page.</h1>
<p>This content is being served by an Nginx container.</p>
</body>
</html>完成后保存并关闭文件。如果你使用的是 nano,可以通过按 CTRL+X,然后按 Y 和 ENTER 来确认操作。
接下来,创建 docker-compose.yml 文件:
nano docker-compose.yml
将以下内容插入到您的 docker-compose.yml 文件中:
services:
web:
image: nginx:alpine
ports:
- "8000:80"
volumes:
- ./app:/usr/share/nginx/html在现代 Docker Compose 中,version 字段是可选的,通常会省略,因为 Compose 可以自动检测配置版本。上面的示例没有包含 version 字段,这是大多数新项目推荐的做法。只有在需要兼容旧版本时才需要指定 version。
然后你有 services 块,在这里你设置属于此环境的服务。在你的例子中,你只有一个名为 web 的服务。该服务使用 nginx:alpine 镜像,并通过 ports 指令设置端口重定向。所有来自 主机(运行 Docker Compose 的系统)端口 8000 的请求都会被重定向到 web 容器的端口 80,Nginx 将在该端口运行。
volumes 指令将在主机与容器之间创建一个共享卷。这会将本地的 app 文件夹与容器共享,并且卷将在容器内部位于 /usr/share/nginx/html,这将覆盖 Nginx 的默认文档根目录。
保存并关闭文件。
你已经设置了一个演示页面和一个 docker-compose.yml 文件,用于创建一个容器化的 Web 服务器环境来提供服务。在下一步中,你将使用 Docker Compose 启动这个环境。
步骤 3 — 运行 Docker Compose
有了 docker-compose.yml 文件后,你现在可以执行 Docker Compose 来启动你的环境。以下命令将下载所需的 Docker 镜像,为 web 服务创建一个容器,并在后台模式下运行容器化环境:
docker compose up -dDocker Compose 会首先在本地系统中查找已定义的镜像,如果找不到该镜像,它将从 Docker Hub 下载镜像。你会看到如下输出:
Creating network "compose-demo_default" with the default driver
Pulling web (nginx:alpine)...
alpine: Pulling from library/nginx
cbdbe7a5bc2a: Pull complete
10c113fb0c77: Pull complete
9ba64393807b: Pull complete
c829a9c40ab2: Pull complete
61d685417b2f: Pull complete
Digest: sha256:57254039c6313fe8c53f1acbf15657ec9616a813397b74b063e32443427c5502
Status: Downloaded newer image for nginx:alpine
Creating compose-demo_web_1 ... done注意: 如果在运行 docker compose up 时遇到“权限被拒绝”错误,这通常意味着您的非根用户没有权限访问 Docker 守护进程的套接字。
默认情况下,Docker 守护进程绑定到一个 Unix 套接字 (/var/run/docker.sock),该套接字属于 root 用户。要解决此问题,您必须将非 root 用户添加到 docker 用户组中,该用户组是在安装 Docker 时创建的。
运行以下命令将您的用户添加到 docker 组:
sudo usermod -aG docker ${USER}运行此命令后,您需要退出并重新登录,以使组更改生效。您也可以通过输入以下命令在当前终端会话中激活更改:
newgrp docker此命令应解决与 Docker 套接字相关的任何权限错误。有关完整的操作指南,请参阅《如何在 Ubuntu 上安装 Docker – 分步指南》的步骤 2。
您的环境现在已在后台运行。要验证容器是否处于活动状态,您可以运行:
docker compose ps此命令将向您显示正在运行的容器及其状态的信息,以及当前正在进行的任何端口重定向:
Name Command State Ports
----------------------------------------------------------------------------------
compose-demo_web_1 /docker-entrypoint.sh ngin ... Up 0.0.0.0:8000->80/tcp如果您在本地机器上运行此演示,现在可以通过将浏览器指向 localhost:8000 来访问演示应用;如果您在远程服务器上运行此演示,则可以通过 your_server_domain_or_IP:8000 来访问。
你会看到这样的页面:
你在 docker-compose.yml 文件中设置的共享卷会使你的 app 文件夹中的文件与容器的文档根目录保持同步。如果你对 index.html 文件进行任何更改,容器会自动捕获这些更改,因此在你重新加载页面时,浏览器中会显示更新后的内容。
在下一步中,您将看到如何使用 Docker Compose 命令管理您的容器化环境。
步骤 4 — 熟悉 Docker Compose 命令
你已经了解了如何设置 docker-compose.yml 文件,并使用 docker compose up 启动你的环境。现在,你将看到如何使用 Docker Compose 命令来管理和与容器化环境进行交互。
要查看您的 Nginx 容器生成的日志,您可以使用 logs 命令:
docker compose logs你将看到类似这样的输出:
Attaching to compose-demo_web_1
web_1 | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
web_1 | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
web_1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
web_1 | 10-listen-on-ipv6-by-default.sh: Getting the checksum of /etc/nginx/conf.d/default.conf
web_1 | 10-listen-on-ipv6-by-default.sh: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
web_1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
web_1 | /docker-entrypoint.sh: Configuration complete; ready for start up
web_1 | 172.22.0.1 - - [02/Jun/2020:10:47:13 +0000] "GET / HTTP/1.1" 200 353 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36" "-"如果你想在不更改容器当前状态的情况下暂停环境执行,你可以使用:
docker compose pausePausing compose-demo_web_1 ... done在发出暂停后恢复执行:
docker compose unpauseUnpausing compose-demo_web_1 ... donestop 命令将终止容器的执行,但不会销毁与您的容器相关的任何数据:
docker compose stopStopping compose-demo_web_1 ... done如果你想删除与此容器化环境相关的容器、网络和卷,请使用 down 命令:
docker compose downRemoving compose-demo_web_1 ... done
Removing network compose-demo_default请注意,这不会移除 Docker Compose 用于启动您的环境的基础镜像(在您的案例中为 nginx:alpine)。这样,每当您使用 docker compose up 再次启动环境时,由于镜像已经存在于您的系统中,启动过程会快得多。
如果您还想从系统中删除基础镜像,可以使用:
docker image rm nginx:alpineUntagged: nginx:alpine
Untagged: nginx@sha256:b89a6ccbda39576ad23fd079978c967cecc6b170db6e7ff8a769bf2259a71912
Deleted: sha256:7d0cdcc60a96a5124763fddf5d534d058ad7d0d8d4c3b8be2aefedf4267d0270
Deleted: sha256:05a0eaca15d731e0029a7604ef54f0dda3b736d4e987e6ac87b91ac7aac03ab1
Deleted: sha256:c6bbc4bdac396583641cb44cd35126b2c195be8fe1ac5e6c577c14752bbe9157
Deleted: sha256:35789b1e1a362b0da8392ca7d5759ef08b9a6b7141cc1521570f984dc7905eb6
Deleted: sha256:a3efaa65ec344c882fe5d543a392a54c4ceacd1efd91662d06964211b1be4c08
Deleted: sha256:3e207b409db364b595ba862cdc12be96dcdad8e36c59a03b7b3b61c946a5741a注意:请参考我们的《在 Ubuntu 上安装 Docker 的逐步指南》,以获取关于 Docker 命令的更详细参考。
步骤5 — 构建多服务应用程序
Docker Compose 的真正力量在于管理协同工作的多个服务。Nginx 示例只是一个单一服务。让我们创建一个更实用的多服务应用程序:一个连接到 MySQL 数据库的 WordPress 网站。
这个设置涉及两个服务:wordpress(运行应用程序)和db(运行数据库)。我们还将使用 Docker 卷来确保即使容器被移除,数据库数据仍然可以保留。
让我们为这个应用程序创建一个新目录:
mkdir -p ~/compose-demo/wordpressapp
cd ~/compose-demo/wordpressapp在这个例子中,我们不需要任何本地文件,因为镜像将包含所有必要的软件。
创建一个新的 docker-compose.yml 文件:
nano docker-compose.yml粘贴以下配置文件。这个文件比较复杂,所以我们会逐部分进行分析。
services:
db:
image: mysql:8.0
container_name: mysql_db
volumes:
- db_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: <^your_root_password_here<^>
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress_user
MYSQL_PASSWORD: <^your_wordpress_password_here<^>
restart: unless-stopped
wordpress:
image: wordpress:latest
container_name: wordpress_app
ports:
- "8001:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress_user
WORDPRESS_DB_PASSWORD: <^your_wordpress_password_here<^>
WORDPRESS_DB_NAME: wordpress
volumes:
- ./wp_content:/var/www/html/wp-content
depends_on:
- db
restart: unless-stopped
volumes:
db_data:注意:这里我们为了示例目的,将密码的值硬编码了。在实际环境中使用时,请确保在 .env 文件中使用环境变量,以避免暴露您的凭据。
保存并关闭文件。记得将 <^...^> 占位符替换为强大且安全的密码。
让我们来分解一下这个文件中的新指令:
services:这个部分仍然定义了我们的容器。我们现在有两个:db和wordpress。image: 我们使用mysql:8.0作为数据库,使用wordpress:latest镜像作为应用程序。container_name:这用于为容器设置一个特定的、易于阅读的名称,比自动生成的名称更容易引用。environment:这是传递给容器的环境变量列表。mysql镜像使用这些变量来设置 root 密码并创建初始数据库和用户。wordpress镜像使用这些变量来确定如何连接到其数据库。卷(服务级别):- 对于
db服务,db_data:/var/lib/mysql将一个 Docker 管理的名为db_data的卷映射到容器内的 MySQL 数据目录。这可以保障你的数据安全。
- 对于
- 对于
wordpress服务,./wp_content:/var/www/html/wp-content将本地目录(wp_content)映射到WordPress内容目录。这允许你直接从主机机器编辑主题和插件。
depends_on:这表示 Compose 会在启动 wordpress 服务之前先启动 db 服务。这很重要,因为如果启动时找不到数据库,WordPress 将无法运行。volumes(顶层):此块定义了命名卷。db_data: 创建了一个 Docker 管理的卷,这是处理持久数据的推荐方式。现在,启动这个多服务应用程序:
docker compose up -dCompose 会拉取 mysql 和 wordpress 镜像,然后创建容器,首先启动 db 服务。
Creating network "compose-demo_default" with the default driver
Creating volume "compose-demo_db_data" with default driver
Pulling db (mysql:8.0)...
...
Pulling wordpress (wordpress:latest)...
...
Creating mysql_db ... done
Creating wordpress_app ... done您现在可以通过在浏览器中访问 localhost:8001 或 <^your_server_domain_or_IP<^>:8001 来访问您的新 WordPress 网站。您应该会看到 WordPress 安装界面。有关更详细的示例,请查看我们的文章《如何使用 Docker Compose 安装 WordPress》。
第6步 — 使用 Docker Compose 进行 AI/ML 开发
除了网页应用之外,Docker Compose 对于创建可复现的数据科学和机器学习(AI/ML)环境也是一个非常有用的工具。AI/ML 项目以其复杂的依赖关系而著称,包括特定的 Python 版本、像 TensorFlow 或 PyTorch 这样的库,以及像 NVIDIA CUDA 工具包这样的系统级驱动。Docker Compose 将整个环境捕获在配置文件中,解决了“在我的机器上可以运行”的问题,这对于可复现的研究至关重要。
在此示例中,您将创建一个由多种服务组成的人工智能/机器学习环境,包括:
- JupyterLab: 一个用于交互式开发的容器,由自定义的
Dockerfile构建。 - PostgreSQL:一个用于存储实验结果和元数据的数据库容器。
- GPU 访问:JupyterLab 容器将被配置为访问主机的 NVIDIA GPU,这是模型训练的常见需求。
先决条件: 这个示例要求您的主机上有 NVIDIA GPU 并已安装 NVIDIA 容器工具包。如果没有,在请求 GPU 资源时,容器将无法启动。
文件结构
首先,为你的项目创建一个目录。在该目录中,你将创建一个 docker-compose.yml 文件、一个 jupyter 目录、一个用于 Jupyter 的 Dockerfile 文件,以及一个 requirements.txt 文件。目录结构应该类似于这样:
ai-project/
├── docker-compose.yml
└── jupyter/
├── Dockerfile
└── requirements.txt创建组件文件
1. requirements.txt 文件
此文件列出了您的数据科学环境所需的 Python 包。
nano jupyter/requirements.txt添加所需的包。在此示例中,我们将包含用于数据处理、数据库连接和深度学习框架的库。
pandas
scikit-learn
tensorflow
jupyterlab
psycopg2-binary保存并关闭文件。
2. Dockerfile
此文件定义了您的自定义 JupyterLab 服务。它使用官方 Jupyter 镜像作为基础,并从 requirements.txt 安装软件包。
nano jupyter/Dockerfile粘贴以下内容:
# Start from a base image that includes Jupyter and scientific libraries
FROM jupyter/scipy-notebook:latest
# Copy our local requirements file into the container
COPY requirements.txt /tmp/requirements.txt
# Install the Python packages
RUN pip install --no-cache-dir -r /tmp/requirements.txt此文件指示 Docker 使用 jupyter/scipy-notebook 作为起点,将你的 requirements.txt 复制到容器中,然后使用 pip 安装这些包。
使用 docker-compose.yml 定义环境
现在,创建主 docker-compose.yml 文件。此文件将协调 db 服务和您自定义的 jupyter 服务。
nano docker-compose.yml粘贴以下配置,并用您自己的安全凭据替换 <^...^> 占位符。
services:
db:
image: postgres:15-alpine
container_name: ai_postgres_db
environment:
POSTGRES_USER: <^your_db_user^>
POSTGRES_PASSWORD: <^your_db_pass^>
POSTGRES_DB: experiments
healthcheck:
test: ["CMD-SHELL", "pg_isready -U <^your_db_user^> -d experiments"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
networks:
- ai_net
jupyter:
build: ./jupyter
container_name: ai_jupyter_lab
ports:
- "8888:8888"
volumes:
- ./notebooks:/home/jovyan/work
environment:
- POSTGRES_HOST=db
- POSTGRES_USER=<^your_db_user^>
- POSTGRES_PASSWORD=<^your_db_pass^>
- POSTGRES_DB=experiments
restart: unless-stopped
networks:
- ai_net
depends_on:
db:
condition: service_healthy
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
volumes:
postgres_data:
name: ai_project_data
networks:
ai_net:
driver: bridge这个配置文件介绍了几个重要的概念:
build: ./jupyter:这告诉 Docker Compose 为jupyter服务构建自定义镜像。它会在指定的./jupyter目录中寻找Dockerfile。volumes: - ./notebooks:/home/jovyan/work:这会将本地名为notebooks的目录绑定挂载到容器中。这确保了你创建的所有 Jupyter 笔记本都会直接保存在主机上,即使容器被删除,它们仍然会保留。healthcheck:如故障排除部分所示,此检查验证db服务不仅处于运行状态,而且在其他服务启动前已经准备好接受连接。depends_on: ... condition: service_healthy:这告诉jupyter服务在启动之前等待db的健康检查通过。deploy.resources.reservations.devices:这是请求 GPU 访问的模块。driver: nvidia:指定要使用的主机驱动程序。
count: 1: 请求一个 GPU。capabilities: [gpu]:确保容器具备使用 GPU 的必要能力。
运行 AI/ML 环境
当您的文件就位后,您就可以构建并运行这些服务了。
在你的
ai-project目录下,运行docker compose up命令。第一次运行时,你必须添加--build标志,以告诉 Compose 构建你的自定义jupyter镜像。docker compose up -d --buildCompose 首先会构建
jupyter镜像(可能需要几分钟,因为它会下载 TensorFlow),然后拉取postgres镜像,最后启动两个容器。[+] Building 5.8s (9/9) FINISHED => [internal] load build definition from Dockerfile ... [+] Running 3/3 ✔ Network ai-project_ai_net Created ✔ Container ai_postgres_db Started ✔ Container ai_jupyter_lab Started您现在可以通过在浏览器中访问
http://localhost:8888(或<^your_server_ip^>:8888)来访问 JupyterLab 界面。系统会提示您输入一个令牌,您可以从容器日志中获取该令牌:docker compose logs jupyter查找类似于
http://127.0.0.1:8888/lab?token=a1b2c3d4e5f6...的行在 Jupyter 笔记本中,你现在可以使用主机名
db以及你提供的凭据连接到数据库。你的环境还可以访问主机的 GPU 来进行模型训练。
扩展服务和使用自定义网络
Docker Compose 包含用于扩展服务和管理它们所使用网络的功能。
通过副本扩展服务
假设你在第2步中设置的 Nginx 网页服务器流量过大。你可以将 web 服务扩展为运行多个容器实例。Docker Compose 可以自动管理这一过程。
有两种主要方式来扩展服务。
1. 使用 replicas 键(v2 中推荐)
您可以直接在 docker-compose.yml 文件中使用 deploy 和 replicas 键定义所需的实例数量。此功能最初是 Docker Swarm 的一部分,但现在也可用于标准的 Compose 部署。
修改你在第 2 步中的 Nginx docker-compose.yml:
services:
web:
image: nginx:alpine
ports:
- "8000:80"
volumes:
- ./app:/usr/share/nginx/html
deploy:
replicas: 3当你运行 docker compose up -d 时,Compose 会创建三个 web 容器。然而,你会遇到一个问题:所有三个容器都会尝试绑定到主机端口 8000。这会导致第二个和第三个容器出现“端口已被分配”的错误。要在生产环境中解决此问题,你需要 从 web 服务中移除 ports 映射。这样,web 容器只能在 Docker 网络内部访问。一个独立的负载均衡服务将成为唯一拥有公共端口的服务,然后将流量分发到三个 web 副本。
一个示例配置如下所示(这是一个高级示例):
services:
web:
image: nginx:alpine
# No 'ports' mapping here.
# The service is only accessible inside the 'web-net' network.
volumes:
- ./app:/usr/share/nginx/html
deploy:
replicas: 3
networks:
- web-net
load_balancer:
image: nginx:latest
ports:
- "80:80" # The load_balancer is the only service with a public port.
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf # A config file that load balances to 'web'.
networks:
- web-net
depends_on:
- web
networks:
web-net:在此设置中,load_balancer 监听端口 80,并将请求内部路由到 web_1、web_2 和 web_3。
这个 replicas 键在与反向代理(如 Traefik 或其他 Nginx 实例)一起使用时最为有用,反向代理可以在 Docker 网络中的副本之间进行请求的负载均衡,而无需每个副本在主机上暴露一个端口。
2. 使用 --scale 标志
一种常见的缩放方法是 --scale 标志,这是从 Compose v1 延续下来的。该标志在运行时应用,并会覆盖任何 replicas 键。
然而,你必须小心端口定义。如果你的服务定义了固定主机端口映射(例如,"8000:80"),运行docker compose up --scale时,第二个和第三个容器会出现错误,因为它们都会尝试绑定到同一个主机端口。
要对暴露端口的服务使用 --scale,必须避免使用固定的主机端口映射。
选项 1:映射到随机主机端口(适合开发)
你可以修改你的
docker-compose.yml来只指定容器端口。这会告诉 Docker 将每个容器的 80 端口映射到主机上的一个 随机 可用端口。services: web: image: nginx:alpine ports: - "80" # No fixed host port volumes: - ./app:/usr/share/nginx/html现在,当你运行 scale 命令时:
docker compose up -d --scale web=3运行
docker compose ps查看结果。每个容器将运行在不同的随机分配的主机端口上:Name Command State Ports ---------------------------------------------------------------------------------- compose-demo_web_1 /docker-entrypoint.sh ngin ... Up 0.0.0.0:49154->80/tcp compose-demo-web_2 /docker-entrypoint.sh ngin ... Up 0.0.0.0:49155->80/tcp compose-demo-web_3 /docker-entrypoint.sh ngin ... Up 0.0.0.0:49156->80/tcp选项2:使用反向代理(适合生产环境)
另一种解决方案,如在
replicas部分中提到的,是完全从web服务中移除ports指令。然后,你可以使用一个单独的负载均衡容器(它拥有唯一的公共端口)来管理并将流量分发到Docker网络内的扩展副本之间。要停止并删除所有三个容器,命令保持不变:
docker compose down
docker-compose.yml - 修正 --scale
自定义网络配置
默认情况下,Docker Compose 会为您的应用创建一个单独的 bridge 网络。文件中的每个服务都会连接到该网络,这就是 wordpress 容器能够仅通过使用服务名 (db) 就找到 db 容器的原因。
但是,您可以定义自己的自定义网络,以获得更好的隔离和控制。
让我们修改 WordPress 示例以使用自定义的 bridge 网络。
services:
db:
image: mysql:8.0
...
networks:
- app_net
wordpress:
image: wordpress:latest
...
ports:
- "8001:80"
depends_on:
- db
networks:
- app_net
volumes:
db_data:
networks:
app_net:
driver: bridge这是我们新增的内容:
networks(顶层):这定义了一个名为app_net的新网络,并指定它应使用标准的bridge驱动。网络(服务级别):在db和wordpress下,这个键将它们连接到我们的app_net网络。
如果你有第三个服务(比如分析工具),并且你没有将其连接到 app_net,它将完全隔离,无法与 db 或 wordpress 容器通信。
对于使用 Docker Swarm 的多主机集群,你需要将 driver 从 bridge 改为 overlay。bridge 驱动用于单个主机上容器之间的通信,这是大多数 Docker Compose 使用场景的标准配置。
使用模块化配置文件
随着您的应用程序不断增长,您的 docker-compose.yml 文件可能会变得庞大且难以管理。Docker Compose 支持 include 指令,允许您将配置拆分到多个文件中。
想象一下,你希望将 WordPress 和数据库的定义分开,并且可能为开发特定的设置(例如在数据库上绑定端口)创建一个通用的 docker-compose.override.yml。
你的目录结构可能是这样的:
compose-demo/
├── docker-compose.yml
├── docker-compose.db.yml
├── docker-compose.web.yml此文件将仅定义db服务。
services:
db:
image: mysql:8.0
container_name: mysql_db
volumes:
- db_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: <^your_root_password_here<^>
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress_user
MYSQL_PASSWORD: <^your_wordpress_password_here<^>
restart: unless-stopped
volumes:
db_data:此文件将仅定义wordpress服务。
services:
wordpress:
image: wordpress:latest
container_name: wordpress_app
ports:
- "8001:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress_user
WORDPRESS_DB_PASSWORD: <^your_wordpress_password_here<^>
WORDPRESS_DB_NAME: wordpress
volumes:
- ./wp_content:/var/www/html/wp-content
depends_on:
- db
restart: unless-stopped现在,你的主要 docker-compose.yml 文件变得非常简单。它只需使用 include 来引入其他文件。
include:
- docker-compose.db.yml
- docker-compose.web.yml当你运行 docker compose up -d 时,Compose 会读取所有三个文件,合并配置,并启动 db 和 wordpress 服务,就好像它们定义在同一个文件中一样。这种方法使你的配置更加模块化且可重用。
注意: include: 指令需要 Docker Compose v2.20 或更高版本。如果您的系统使用旧版本,可以使用 docker compose -f docker-compose.db.yml -f docker-compose.web.yml up 来合并文件。
从 docker-compose(v1)迁移到 docker compose(v2)
您在第1步中安装的 docker compose 命令是“Compose v2”。它是一个基于 Go 的插件,直接内置在 Docker CLI 中。
原始版本“Compose v1”是一个单独的 Python 工具,通过 docker-compose(带连字符)调用。截至 2023 年 7 月,Compose v1 已不再受支持,并已被弃用。
主要变化与向后兼容性
- 命令:最明显的变化是命令。连字符(
docker-compose)现在用空格代替(docker compose)。 - 安装:v1 是通过
pip安装的。v2 随 Docker Desktop 提供,或者可以像在步骤 1 中那样作为 CLI 插件安装。 - YAML 文件:您的
docker-compose.yml文件几乎 100% 向后兼容。Compose v2 完全支持文件版本3.x。迁移时您无需更改 YAML 文件,只需更改您的命令即可。 --project-name标志:-p标志仍然可用,但完整标志现在是--project-name,而不是--project_name。
命令迁移指南
大多数命令是相同的,只是去掉了连字符。这里有一张表,将常用的v1命令与其v2对应命令进行比较。
docker-compose(v1)命令 | docker compose(v2)命令 | 笔记 |
|---|---|---|
| | 语法没有变化。 |
| | 语法没有变化。 |
| | 语法没有变化。 |
| | 语法没有变化。 |
| | 语法没有变化。 |
| | 语法没有变化。 |
| | 语法没有变化。 |
| | run 创建一个新的容器。exec 在已有的容器中运行。 |
| | --scale 标志在 v2 中仍然受支持。 |
如你所见,对于大多数日常使用来说,唯一的变化是将 docker-compose 改为 docker compose。如果你使用脚本,可以通过简单地去掉连字符来更新它们。
如果你仍然安装了旧的 docker-compose v1,可以将其删除以避免混淆:
sudo pip3 uninstall docker-compose或者,如果它是通过 apt 安装的:
sudo apt remove docker-compose常见 Docker Compose 问题排查
当你使用 Docker Compose 时,可能会遇到与文件语法、权限或容器运行时冲突相关的问题。大多数问题可以通过系统地检查你的配置、权限和容器日志来诊断和解决。
让我们来看一些最常见的错误及其解决方法。
权限被拒绝(Docker 套接字)
这是新手 Docker 用户最常遇到的错误之一。你运行 docker compose up 时,会看到有关 Docker 守护进程套接字的错误消息。
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock问题:您的非 root 用户没有权限与作为
root运行的 Docker 守护进程通信。解决方案: 您必须将您的用户添加到在 Docker 安装过程中创建的
docker组中。将您的用户添加到
docker组:sudo usermod -aG docker ${USER}
要使新的组成员身份生效,您必须先退出登录,然后重新登录。
或者,您可以通过输入以下命令在当前终端会话中激活组更改:
newgrp docker
这应该可以解决与 Docker 套接字相关的任何权限错误。
YAML 语法和缩进错误
Docker Compose 无法运行,并报告您的 docker-compose.yml 文件无效。
ERROR: The Compose file './docker-compose.yml' is invalid because:
services.web.ports contains an invalid type, it should be a list或者:
mapping values are not allowed in this context at line 5- 问题:
docker-compose.yml文件依赖严格的 YAML 语法。最常见的错误是缩进不正确。YAML 使用空格而不是制表符来定义结构。 解决方案:仔细检查你的
docker-compose.yml文件的缩进。- 标准是每个缩进级别使用两个空格。
- 确保同一级别的所有键(如
version、services、volumes)在同一列开始。 - 确保所有子键(例如服务下的
image、ports、volumes)比其父服务(例如web)缩进多两个空格。 - 使用带有 YAML 检查器的代码编辑器自动突出显示语法错误。
端口冲突
你尝试启动你的环境,但命令失败,并出现错误信息,提示地址已被占用。
Error starting userland proxy: listen tcp 0.0.0.0:8000: bind: address already in use问题: 您主机上的另一个进程已经在监听您尝试映射的端口(在此例中为
8000)。这通常是另一个 Docker 容器或本地开发服务器。解决方案: 你有两个选择:
停止其他进程。你可以使用以下命令找到使用该端口的进程:
sudo lsof -i :8000如果是另一个 Docker 容器,请使用
docker stop <container_id>停止它。
在你的
docker-compose.yml文件中更改主机端口。这通常是最简单的解决方法。将ports映射从"8000:80"改为其他端口,例如"8001:80"。... ports: - "8001:80" # Changed from 8000 ...
docker-compose.yml
服务失败,出现 连接被拒绝 (depends_on)
在多服务应用程序中(例如 WordPress 和 MySQL 的示例),wordpress 容器启动,但其日志显示在尝试连接 db 服务时出现 Connection refused 或 MySQL server has gone away。
问题:您使用了
depends_on,但该指令仅等待db容器启动。它不会等待容器内的 MySQL 应用程序 完全初始化并准备好接受连接。解决方案:应用程序(在此情况下为 WordPress)必须配置为重试连接。大多数现代镜像内置了此重试机制。对于没有内置机制的镜像,您必须实现健康检查。
你可以为你的
db服务添加一个healthcheck。然后可以将wordpress服务的depends_on配置为等待数据库“健康”,而不仅仅是“启动”。
MySQL 示例健康检查:
services:
db:
image: mysql:8.0
...
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p<^your_root_password_here<^>"]
interval: 10s
timeout: 5s
retries: 5
wordpress:
image: wordpress:latest
...
depends_on:
db:
condition: service_healthy # This now waits for the healthcheck
...卷和绑定挂载的错误
你可能会在容器日志中遇到权限错误,或者发现容器的数据目录为空。
文件/目录权限
问题:容器以特定用户运行(例如,
www-data,用户 ID 为 33),但你挂载的主机目录由你的用户拥有(例如,ubuntu,用户 ID 为 1000)。容器的用户没有权限写入主机目录。解决方案: 将主机目录的所有权更改为与容器内的用户 ID 匹配。您可以通过运行
docker compose exec <service_name> id来查找容器的用户 ID。例如,如果 ID 是 33(www-data常用的 ID):sudo chown -R 33:33 ./wp_content
相对路径问题
问题:你的
docker-compose.yml使用了像./app这样的相对路径,但你的index.html文件没有被提供服务。解决方案: Docker Compose 会从你运行
docker compose up命令的目录解析相对路径。始终从包含你的docker-compose.yml文件的同一目录运行 Compose 命令。
网络和服务发现
您的应用程序容器日志显示 未找到主机 或 无法解析主机: db。
- 问题:您的
wordpress容器无法在网络上找到db容器。 解决方案:默认情况下,Docker Compose 会创建一个网络,并将所有服务连接到该网络。服务可以使用它们的服务名称相互查找。
检查服务名称:确保您应用程序配置中的主机名(例如
WORDPRESS_DB_HOST环境变量)与docker-compose.yml中的服务名称(例如db)完全相同。
检查网络:使用
docker compose ps来查找你的项目名称(例如,compose-demo)。然后,检查默认网络:docker network inspect compose-demo_defaultJSON 输出将列出所有连接到此网络的容器。如果您的某个服务丢失,请检查您的
docker-compose.yml中是否存在可能将其隔离的自定义networks配置。
命令不匹配(v1 与 v2)
你输入 docker-compose up(带连字符)时,会看到“找不到命令”的错误。
docker-compose: command not found- 问题: 您正在尝试使用适用于 Compose v1 的命令,该工具是已弃用的 Python 工具。本指南安装的是 Compose v2,它是直接集成到 Docker CLI 中的插件。
- 解决方案:通过将连字符 (
-) 替换为空格来使用正确的命令。
docker-compose(v1) | docker compose(v2) |
|---|---|
docker-compose up | docker compose up |
docker-compose down | docker compose down |
docker-compose ps | docker compose ps |
docker-compose 日志 | docker compose 日志 |
在遵循本指南时,始终使用 docker compose(请注意空格)。
常见问题
1. Docker Compose 用于什么?
Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。
它使用一个单一的 YAML 文件(默认情况下为 docker-compose.yml)来配置应用程序的所有组件,这些组件被称为 服务。该文件还定义了允许服务相互通信的 网络 和用于持久数据的 卷。
通过这个单一文件,你可以使用简单的命令管理整个应用程序堆栈:
docker compose up启动并运行你的整个应用程序,包括所有指定的容器、网络和数据卷。docker compose down会停止并移除由你的应用创建的所有容器、网络和卷。
它最适用于管理需要多个组件的应用程序,例如需要 Web 服务器(如 Nginx)、应用程序后端(如 WordPress)和数据库(如 MySQL)的网页。
2. 我该如何在 Ubuntu 上安装 Docker Compose?
推荐的方法是将 Docker Compose 安装为 Docker CLI 的插件。这可以通过从 Docker 官方的 apt 仓库安装 docker-compose-plugin 软件包来完成。
首先,确保您已按照官方 Docker 文档在您的 Ubuntu 系统上设置 Docker 的
apt仓库。更新您的软件包列表:
sudo apt update安装 Docker Compose 插件:
sudo apt install docker-compose-plugin通过检查版本来验证安装。命令使用空格,而不是连字符。
docker compose version
3. 我如何将 Docker Compose 更新到最新版本?
由于 Docker Compose(v2)是作为系统包使用 apt 安装的,因此您可以通过标准的 Ubuntu 软件更新流程来更新它。
刷新本地软件包索引:
sudo apt update运行系统范围的升级,其中将包括 Compose 插件:
sudo apt upgrade或者,如果你只想更新插件本身,你可以运行:
sudo apt install --only-upgrade docker-compose-plugin
4. Docker Compose 可以在生产环境中使用吗?
是的,Docker Compose 经常用于生产环境,特别是用于在单台主机上运行的应用程序。
它提供了一种直接的方法来定义、部署和管理应用程序的服务、网络和数据卷的生命周期。对于单服务器部署来说,这是一个非常有效且易于管理的解决方案。
对于需要在多个主机(集群)之间协调容器的更复杂场景,其他工具更为常用。这些工具包括Docker Swarm(使用类似的 Compose 文件语法)和Kubernetes(它是大规模容器编排的行业标准)。
5. Docker和Docker Compose有什么区别?
本表概述了 Docker CLI 和 Docker Compose CLI 之间的主要区别。
| 功能 | docker(Docker 引擎) | docker compose(Compose 插件) |
|---|---|---|
| 范围 | 管理单个Docker对象。 | 将完整的多容器应用作为一个单元进行管理。 |
| 主要用途 | 构建镜像(docker build)、运行单个容器(docker run)、管理容器(docker ps)、镜像、卷和网络。 | 编排在 docker-compose.yml 文件中定义的多个服务。 |
| 命令 | 专注于单一对象的低级命令。例如:docker run -d -p 80:80 -v ./:/app nginx | 针对整个应用程序的高级命令。例如:docker compose up |
| 类比 | 一个 构建模块(一个单独的容器)。 | 整个建筑(申请)的蓝图和施工经理。 |
简而言之,你使用 docker 与单个容器进行交互。你使用 docker compose 来一次性管理整个应用程序堆栈(例如,网页、应用、数据库)。
6. 我如何使用 Docker Compose 运行多个容器?
你可以通过在 docker-compose.yml 文件中将它们定义为 服务 来运行多个容器。
创建一个名为
docker-compose.yml的文件。在此文件中,使用
services:键来定义您想要运行的每个容器。这里有一个实际的例子,它定义并运行了两个容器:一个 WordPress 网站和一个 MySQL 数据库。
services: db: # This is the first service (container) image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: <^your_password_here<^> MYSQL_DATABASE: wordpress volumes: - db_data:/var/lib/mysql wordpress: # This is the second service (container) image: wordpress:latest ports: - "8001:80" environment: WORDPRESS_DB_HOST: db WORDPRESS_DB_PASSWORD: <^your_password_here<^> depends_on: - db volumes: db_data:保存文件并在相同的目录下打开终端。
运行一个命令即可启动两个容器:
docker compose up -d
docker-compose.yml
Docker Compose 将读取该文件,为各个服务创建一个共享网络,拉取 mysql 和 wordpress 镜像,并为每个服务启动一个容器。
7. 为什么在开发中使用 Docker Compose?
Docker Compose 是开发环境中的标准工具,因为它解决了几个常见问题。
一致的环境: 它确保团队中的每个开发者运行完全相同的服务(数据库、缓存、Web 服务器),并且使用完全相同的版本和配置。这在
docker-compose.yml文件中定义,并提交到版本控制中。这消除了“在我机器上可以运行”的问题。简单性: 它替代了复杂的设置脚本和冗长的
docker run命令。开发者只需运行docker compose up即可启动整个应用栈,运行docker compose down即可停止它。服务隔离:开发人员可以在同一台机器上处理多个项目而不会产生依赖冲突。项目 A 可以使用 PostgreSQL 9.6,而项目 B 可以使用 PostgreSQL 14,因为每个数据库都运行在由各自 Compose 文件管理的隔离容器中。
轻松的集成测试:由于 Compose 会同时启动应用程序的所有依赖项,因此它为运行集成测试提供了一个完美的本地环境,以验证各个服务之间的交互。
结论
在本指南中,您安装了 Docker Compose 并配置了一个完整的多容器应用程序。您从一个用于 Nginx 网络服务器的基本 docker-compose.yml 文件开始,逐步发展到一个更复杂、现实的栈,包括 WordPress 应用程序和 MySQL 数据库。您已经学习了如何管理整个应用程序生命周期,从构建和运行服务到停止和删除它们。
现在,您已经熟悉了有效管理应用程序的关键概念,包括服务扩展、自定义网络定义,以及使用 include 指令将配置拆分为模块化文件。通过遵循迁移指南和故障排除步骤,您还可以解决常见问题,如端口冲突、“权限被拒绝”错误以及 YAML 语法错误。本节介绍的技能将使您能够构建一致、可复现的开发环境,并自信地部署单主机应用程序。
要获取所有可用的 docker compose 命令的完整参考,请查看官方文档。
有关更多 Docker 相关的教程,请查看以下文章:
- 如何在 Ubuntu 上安装 Docker – 分步指南
- 如何使用 Docker Compose 安装 WordPress
感谢您与 Linux-Console.net 社区一起学习。

