如何使用 docker compose run 命令执行一次性任务

DockerDockerBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

在本实验中,你将学习如何高效使用 docker compose run 命令在 Docker Compose 服务中执行一次性任务。这是一种强大的技术,可用于运行管理命令、调试或执行特定操作,而无需启动完整的服务堆栈。

我们将探讨多种场景,包括覆盖默认服务命令、启用服务端口进行交互、手动映射端口、在不启动关联服务的情况下运行命令,以及在执行后自动移除容器。完成本实验后,你将熟练掌握使用 docker compose run 处理各种一次性任务的技巧。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL docker(("Docker")) -.-> docker/ContainerOperationsGroup(["Container Operations"]) docker(("Docker")) -.-> docker/ImageOperationsGroup(["Image Operations"]) docker(("Docker")) -.-> docker/NetworkOperationsGroup(["Network Operations"]) docker/ContainerOperationsGroup -.-> docker/run("Run a Container") docker/ContainerOperationsGroup -.-> docker/ps("List Running Containers") docker/ContainerOperationsGroup -.-> docker/stop("Stop Container") docker/ContainerOperationsGroup -.-> docker/rm("Remove Container") docker/ImageOperationsGroup -.-> docker/pull("Pull Image from Repository") docker/NetworkOperationsGroup -.-> docker/network("Manage Networks") subgraph Lab Skills docker/run -.-> lab-555091{{"如何使用 docker compose run 命令执行一次性任务"}} docker/ps -.-> lab-555091{{"如何使用 docker compose run 命令执行一次性任务"}} docker/stop -.-> lab-555091{{"如何使用 docker compose run 命令执行一次性任务"}} docker/rm -.-> lab-555091{{"如何使用 docker compose run 命令执行一次性任务"}} docker/pull -.-> lab-555091{{"如何使用 docker compose run 命令执行一次性任务"}} docker/network -.-> lab-555091{{"如何使用 docker compose run 命令执行一次性任务"}} end

运行覆盖服务命令的一次性命令

在这一步中,我们将学习如何在 Docker 容器中运行一次性命令,覆盖 Docker 镜像或 Dockerfile 中指定的默认命令。这对于运行管理任务、调试或在容器环境中执行特定脚本非常有用,而无需启动主服务。

首先,让我们拉取一个简单的 Docker 镜像用于演示。我们将使用 ubuntu 镜像。

docker pull ubuntu:latest

你会看到显示镜像正在被拉取和下载的输出。

Using default tag: latest
latest: Pulling from library/ubuntu
...
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest

现在,让我们基于 ubuntu 镜像在容器中运行一个一次性命令。我们将使用 docker run 命令,指定镜像名称和要执行的命令。例如,运行 ls -l / 命令来列出容器中根目录的内容。

docker run ubuntu ls -l /

这个命令会从 ubuntu 镜像创建一个新容器,在其中执行 ls -l / 命令,然后退出。你应该会看到类似以下的输出,显示根目录的内容:

total 68
drwxr-xr-x   2 root root  4096 Oct 26 00:00 bin
drwxr-xr-x   2 root root  4096 Oct 26 00:00 boot
drwxr-xr-x   5 root root   360 Nov  1 00:00 dev
drwxr-xr-x  19 root root  4096 Nov  1 00:00 etc
drwxr-xr-x   2 root root  4096 Oct 26 00:00 home
drwxr-xr-x   7 root root  4096 Oct 26 00:00 lib
drwxr-xr-x   2 root root  4096 Oct 26 00:00 lib64
drwxr-xr-x   2 root root  4096 Oct 26 00:00 media
drwxr-xr-x   2 root root  4096 Oct 26 00:00 mnt
drwxr-xr-x   2 root root  4096 Oct 26 00:00 opt
drwxr-xr-x   2 root root  4096 Oct 04 14:00 proc
drwx------   2 root root  4096 Oct 26 00:00 root
drwxr-xr-x   2 root root  4096 Oct 26 00:00 run
drwxr-xr-x   2 root root  4096 Oct 26 00:00 sbin
drwxr-xr-x   2 root root  4096 Oct 26 00:00 srv
drwxr-xr-x   2 root root  4096 Oct 26 00:00 sys
drwxrwxrwt   2 root root  4096 Oct 26 00:00 tmp
drwxr-xr-x  11 root root  4096 Oct 26 00:00 usr
drwxr-xr-x  12 root root  4096 Oct 26 00:00 var

在这个例子中,ubuntu 镜像的默认命令通常是像 /bin/bash 这样的 shell。通过在镜像名称后提供 ls -l /,我们告诉 Docker 运行这个特定命令而不是默认命令。

让我们尝试另一个命令,例如 pwd 来打印容器内的当前工作目录。

docker run ubuntu pwd

输出应该是 /,表示根目录是默认工作目录。

/

这演示了如何轻松地在容器内执行任意命令,而无需交互式进入容器或修改镜像的默认命令。

运行启用服务端口的命令

在这一步中,我们将探讨如何在 Docker 容器中运行命令的同时,保持容器内服务配置的端口处于可用状态。当你需要运行临时命令进行调试或管理,但仍希望主服务能从容器外部访问时,这个功能非常有用。

我们将使用一个简单的 Web 服务器镜像进行演示。让我们拉取流行的 Web 服务器镜像 nginx

docker pull nginx:latest

你会看到显示镜像正在被拉取的输出。

Using default tag: latest
latest: Pulling from library/nginx
...
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest

默认的 nginx 镜像配置为监听容器内的 80 端口。要使这个端口能从宿主机访问,我们需要使用 docker run 命令的 -p 参数将主机端口映射到容器端口。让我们将主机的 8080 端口映射到容器的 80 端口。

现在,我们不运行默认的 Nginx 服务,而是运行一个简单的命令如 echo "Hello from the container",同时保持端口映射生效。

docker run -p 8080:80 nginx echo "Hello from the container"

你可能期望这会启动 Nginx 服务器然后打印 "Hello from the container"。然而,当你在 docker run 的镜像名称后提供命令时,该命令会替换默认命令。因此在这个例子中,容器将运行 echo 命令然后退出。即使我们指定了端口映射,Nginx 服务器也不会启动。

输出将只是:

Hello from the container

如果你尝试在浏览器中访问 http://localhost:8080 或使用 curl,你会发现连接被拒绝,因为 Nginx 服务器没有运行。

curl http://localhost:8080

输出可能是:

curl: (7) Failed to connect to localhost port 8080 after ... connection refused

这说明了重要的一点:当你用 docker run <image> <command> 覆盖默认命令时,容器只会执行提供的命令,而不会启动镜像设计运行的服务。因此,虽然配置了端口映射,但由于监听该端口的服务没有运行,端口映射也不会生效。

要在服务运行且其端口可用的同时运行命令,通常需要先在后台启动服务,然后执行你的命令。然而 docker run 命令设计用于运行单个命令后退出。要实现与运行中的服务并行执行命令的效果,通常需要在已经运行服务的容器上使用 docker exec 命令。我们将在后续步骤中探讨 docker exec

本步骤的关键要点是理解:为 docker run 提供命令会覆盖默认的入口点/命令,因此即使指定了端口映射,默认配置运行的服务也不会启动。

运行手动端口映射的命令

在这一步中,我们将继续探索 docker run 的端口映射功能,重点介绍如何显式指定主机和容器端口。虽然上一步展示了覆盖默认命令会阻止服务运行,但理解手动端口映射对于你确实需要访问服务的情况至关重要。

我们将继续使用 nginx 镜像。提醒一下,nginx 镜像在容器内部暴露了 80 端口。为了从宿主机访问这个端口,我们使用 -p 参数,后跟 host_port:container_port

让我们运行 nginx 容器,并将主机的 8081 端口映射到容器的 80 端口。这次我们不会提供覆盖命令,因此默认的 Nginx 服务将会启动。我们还将以分离模式 (-d) 运行它,使其在后台运行。

docker run -d -p 8081:80 nginx

你会看到一长串字符,这是容器 ID,表示容器已在分离模式下启动。

<container_id>

现在容器正在运行且端口已映射,你可以使用 curl 或网页浏览器从宿主机访问 Nginx 欢迎页面。

curl http://localhost:8081

你应该能看到默认 Nginx 欢迎页面的 HTML 内容。这确认了 Nginx 服务器正在容器内运行,并且可以通过主机的 8081 端口访问。

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</body>
</html>

这演示了手动端口映射如何让你控制使用哪个主机端口来访问容器内特定端口上运行的服务。这对于避免宿主机上的端口冲突以及使容器化服务可访问至关重要。

要停止正在运行的容器,你可以使用 docker stop 命令后跟容器 ID 或名称。你可以通过运行 docker ps 来查找容器 ID。

docker ps

这将显示正在运行的容器列表。找到你刚刚启动的 nginx 容器的容器 ID。

CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                  NAMES
<container_id>   nginx     "nginx -g 'daemon off"   About a minute ago   Up About a minute   0.0.0.0:8081->80/tcp   <container_name>

现在,使用容器 ID 停止容器。将 <container_id> 替换为你输出中的实际 ID。

docker stop <container_id>

容器 ID 将再次被打印出来,确认容器已停止。

<container_id>

如果你在停止容器后再次尝试访问 http://localhost:8081,连接将被拒绝。

在不启动关联服务的情况下运行命令

在本步骤中,我们将学习如何在多容器应用的 Docker 容器中运行命令,而不启动其他关联服务。这对于运行数据库迁移、设置脚本或调试命令特别有用,无需启动整个应用栈。

虽然 Docker Compose 是管理多容器应用的标准工具,并具备在特定服务上运行一次性命令的功能,但我们将在此演示底层的 Docker 概念。由于本环境未预装 Docker Compose,我们将重点使用 docker run 命令配合网络功能。

让我们模拟一个包含两个容器的简单场景:一个 web 应用和一个数据库。我们将使用通用的 ubuntu 镜像代表 web 应用,postgres 镜像作为数据库。

首先拉取 postgres 镜像:

docker pull postgres:latest

你会看到镜像正在被拉取的输出:

Using default tag: latest
latest: Pulling from library/postgres
...
Status: Downloaded newer image for postgres:latest
docker.io/library/postgres:latest

现在创建一个 Docker 网络,使容器能通过名称相互通信:

docker network create my-app-network

你将看到网络 ID 被打印出来:

<network_id>

接着运行 postgres 容器并连接到我们的网络,同时为 PostgreSQL 用户设置密码:

docker run -d --network my-app-network --name my-database -e POSTGRES_PASSWORD=mypassword postgres

容器 ID 会被打印出来,表示数据库容器正在后台运行:

<container_id>

假设我们的"web 应用"容器需要运行一个与数据库交互的命令,比如数据库迁移脚本。通常如果使用 Docker Compose,我们可以在 web 服务上运行命令,Docker Compose 会处理网络设置和连接。

仅使用 docker run 时,如果 web 应用容器尝试连接 my-database,它通常需要位于同一网络中。

让我们在连接到同一网络的 ubuntu 容器中运行命令,模拟可能与数据库交互的命令。我们将尝试通过名称 (my-database) ping 数据库容器:

docker run --network my-app-network ubuntu ping -c 4 my-database

这个命令将:

  1. ubuntu 镜像创建新容器
  2. 连接到 my-app-network
  3. 在容器内运行 ping -c 4 my-database 命令

由于 ubuntu 容器与 my-database 容器在同一网络,它能将名称 my-database 解析为数据库容器的 IP 地址并进行 ping 操作。

你将看到显示 ping 请求和响应的输出:

PING my-database (172.18.0.2) 56(84) bytes of data.
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=1 ttl=64 time=0.050 ms
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=2 ttl=64 time=0.054 ms
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=3 ttl=64 time=0.054 ms
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=4 ttl=64 time=0.054 ms

--- my-database ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3060ms
rtt min/avg/max/mdev = 0.050/0.053/0.054/0.001 ms

这演示了你可以:

  1. 在容器中运行一次性命令
  2. 让它与同一网络上的其他容器交互
  3. 无需启动运行命令的容器的默认服务(本例中 ubuntu 容器没有典型"服务")

关键在于将运行命令的容器连接到需要交互的服务所在的同一网络。

最后清理运行的数据库容器和网络:

docker stop my-database
my-database
docker rm my-database
my-database
docker network rm my-app-network
my-app-network

运行命令并自动移除容器

在本步骤中,我们将学习如何在 Docker 容器中运行命令,并在命令执行完毕后自动移除容器。这对于一次性任务、脚本或作业非常有用,当执行完成后你不需要保留容器。自动移除容器有助于保持系统整洁,避免积累已停止的容器。

我们将再次使用 ubuntu 镜像进行演示。我们将运行一个简单的命令,比如打印一条消息然后退出。要实现在命令完成后自动移除容器,我们需要在 docker run 命令中使用 --rm 参数。

让我们使用 --rm 参数运行容器,并执行 echo "这个容器将被自动移除" 命令:

docker run --rm ubuntu echo "这个容器将被自动移除"

这个命令会:

  1. ubuntu 镜像创建新容器
  2. 在容器内运行 echo "这个容器将被自动移除" 命令
  3. echo 命令执行完毕后,容器将自动被移除

你应该能看到 echo 命令的输出:

这个容器将被自动移除

命令执行完毕后,容器会被停止并移除。要验证容器是否已被移除,可以使用 docker ps -a 命令,它会列出所有容器(包括已停止的)。

docker ps -a

你应该不会在列表中看到刚刚运行的容器。如果你不使用 --rm 参数运行命令,容器仍会出现在输出中,状态为"Exited"。

让我们再试一个例子。我们将运行一个使用 sleep 暂停几秒然后退出的命令,同样使用 --rm 参数:

docker run --rm ubuntu sh -c "echo '开始休眠...'; sleep 5; echo '休眠结束。'"

这个命令使用 sh -c 执行一个简单的脚本,在休眠 5 秒前后打印消息。

你会立即看到第一条消息:

开始休眠...

大约 5 秒后,你会看到第二条消息:

休眠结束。

脚本执行完毕后,容器会被自动移除。你可以再次用 docker ps -a 验证:

docker ps -a

运行休眠命令的容器不应该出现在容器列表中。

对于设计为执行特定任务后立即退出的容器,使用 --rm 参数是一个好习惯,它有助于管理磁盘空间并保持容器列表的整洁。

总结

在本实验中,我们学习了如何使用 docker compose run 命令在 Docker Compose 服务中执行一次性任务。我们首先了解了如何覆盖服务配置中定义的默认命令,这让我们能够运行任意命令以进行管理或调试。

接着,我们探索了如何为这些一次性任务管理网络连接,特别是通过启用服务端口和手动映射端口的方式。最后,我们学习了如何在不启动关联服务的情况下运行命令,以及如何在命令完成后自动移除容器,从而确保环境的整洁。