Docker Intro

网上关于docker的介绍有很多。这里简单介绍docker的基本概念和常用命令。可以作为某种入门参考或者协作规范。

基本概念

Docker是一个开源的应用容器引擎,基于Go语言并遵从Apache2.0协议开源。它可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。
可供下载使用的docker有两种:docker desktop和docker engine。前者包含后者,并且集成了图形化界面,适合个人用户使用。后者则是命令行版本,适合服务器等无图形界面的环境使用。
Docker的核心概念包括以下几个方面:

  • 镜像(Image):Docker镜像是一个包含应用程序及其所有依赖项的轻量级、可执行的独立软件包。它包含运行应用程序所需的所有代码、运行时、库、环境变量和配置文件。
  • 容器(Container):Docker容器是镜像的一个运行实例。它是一个轻量级、独立的可执行软件包,包含运行应用程序所需的所有内容。容器可以在任何支持Docker的操作系统上运行。
  • 镜像库(Registry):Docker镜像库是一个存储和分发Docker镜像的集中式存储库。Docker Hub是最常用的公共镜像库,用户可以从中下载和上传镜像。

举一个便于理解的例子:对于熟悉OOP的人来说,可以把docker镜像理解为一个类,而docker容器则是这个类的一个实例。或者举一个更加生活化的例子:镜像相当于一个模具,而容器则是用这个模具制作出来的产品。

Docker和虚拟机的区别

Docker容器和传统的虚拟机有一些显著的区别:

  • 隔离性:一般虚拟机要求每个虚拟机都运行一个完整的操作系统,而Docker容器共享宿主机的操作系统内核,因此更加轻量级。
  • 启动速度:由于Docker容器不需要启动一个完整的操作系统,因此启动速度通常比虚拟机快得多。
  • 可移植性:Docker容器可以在任何支持Docker的操作系统上运行,而虚拟机通常需要特定的虚拟化软件。
  • 资源利用率:由于Docker容器共享宿主机的操作系统内核,因此资源利用率通常比虚拟机更高。

安装docker engine

参考官方文档进行安装。
这里提供一份基于WSL2的Ubuntu-24.04的安装示例。
首先检查本地的防火墙设置,确保没有关闭必要的网络访问权限:

1
sudo ufw status

预期输出是Status: inactive。如果是active,可以通过sudo ufw disable命令关闭防火墙。
然后检查是否需要卸载旧版本的docker:

1
sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-compose-v2 docker-doc podman-docker containerd runc | cut -f1)

接下来,设置docker的apt仓库。可以在~文件夹下创建一个脚本文件,例如:

1
nano setup_docker_repo.sh

并将以下内容写入该文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash
# Add Docker's official GPG key:
sudo apt update
sudo apt 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:
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc
EOF

sudo apt update

保存后,赋予执行权限并运行该脚本:

1
2
chmod +x setup_docker_repo.sh
./setup_docker_repo.sh

最后,安装docker engine:

1
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

安装完成后,可以通过以下命令验证docker是否安装成功:

1
sudo systemctl status docker

预期输出:

1
2
3
● docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
Active: active (running) since ...

如果没有看到active (running),可以尝试通过sudo systemctl start docker命令启动docker服务。

最后,检查docker的网络设置。可以创建一个测试容器来验证网络连接:

1
sudo docker run hello-world

如果看到Hello from Docker!的欢迎信息,说明docker安装和网络配置成功。
但在国内特殊的网络环境下,这个测试一般都是失败的。即便你为wsl2或者linux配置了代理,由于docker默认不走系统代理,导致无法访问Docker Hub来拉取镜像。
更严重的是,在这篇文章写作的时候(2026年1月),大部分国内镜像源都无法正常拉取镜像。解决这个问题的一个方法是利用代理来配置docker拉取镜像时的网络访问。

Docker网络配置

以下操作均在WSL2的Ubuntu-24.04中进行,并且已经为WSL2配置好了镜像模式,使用了win11宿主机的clash verge代理。具体可以参考我之前写过的这篇文章

首先,创建或编辑docker的配置文件

1
2
sudo mkdir -p /etc/docker
sudo touch /etc/docker/daemon.json

接下来,使用你喜欢的文本编辑器打开/etc/docker/daemon.json文件,并添加以下内容:

1
2
3
4
5
6
7
8
9
{
"proxies": {
"default": {
"httpProxy": "http://<你的代理地址>:<端口>",
"httpsProxy": "http://<你的代理地址>:<端口>",
"noProxy": "localhost,127.0.0.1"
}
}
}

注意:这里httpsProxy的值也使用http协议开头。
<你的代理地址><端口>替换为你实际使用的代理服务器的地址和端口号。保存并关闭文件后,重启docker服务以应用新的配置:

1
2
sudo systemctl daemon-reload
sudo systemctl restart docker

这样,docker在拉取镜像时就会通过你配置的代理服务器进行网络访问,从而解决在国内网络环境下无法拉取镜像的问题。但是,这个方法无法保证容器运行时也使用代理访问网络。如果容器内的应用程序需要访问外部网络,还需要在容器内进行相应的代理配置。

要让容器内的应用程序使用代理,可以在运行容器时通过环境变量传递代理设置。例如:

1
sudo docker run -e http_proxy="http://<你的代理地址>:<端口>" -e https_proxy="http://<你的代理地址>:<端口>" <镜像名称>

在WSL2中还需要注意端口冲突问题,具体参考这篇文章
可以在管理员权限的powershell中运行以下命令解决:

1
2
3
4
5
netsh int ipv4 set dynamic tcp start=50000 num=15536
netsh int ipv6 set dynamic tcp start=50000 num=15536
# 重启网络
net stop winnat
net start winnat

参考文档:docker配置国内源和代理,以及如何让容器通过代理上网
docker 设置代理,以及国内加速镜像设置

Docker用户组设置

默认情况下,只有root用户和docker组的用户才能运行docker命令。为了避免每次运行docker命令都需要使用sudo,可以将当前用户添加到docker组中。
首先,创建docker组(如果尚未创建):

1
sudo groupadd docker

然后将当前用户添加到docker组:

1
sudo usermod -aG docker $USER

最后,重新登录以使组成员身份生效,或者可以使用以下命令立即应用更改:

1
newgrp docker

使用docker拉取镜像

1
docker pull <registry>/<namespace>/<image>:<tag>

<registry>:镜像仓库地址,例如docker.io(Docker Hub的默认地址)、gcr.io(Google Container Registry)等。如果使用默认的Docker Hub,可以省略此部分。
<namespace>:镜像的命名空间,通常是用户名或组织名。如果是library官方镜像,可以省略此部分。
<image>:镜像名称,例如ubuntu、nginx等。
<tag>:镜像标签,通常用于指定镜像的版本,例如latest、1.0等。如果省略此部分,默认使用latest标签。

关于docker hub

网址:https://hub.docker.com/
Docker Hub是一个公共的Docker镜像仓库,用户可以在上面查找、下载和分享Docker镜像。它提供了丰富的官方和社区维护的镜像,涵盖了各种应用程序和服务。
用户可以通过Docker Hub搜索所需的镜像,并使用docker pull命令将其下载到本地。Docker Hub还支持用户创建和管理自己的私有镜像仓库,以便在团队或组织内部共享镜像。
我们可以把docker和docker hub类比为git和github的关系。docker是一个工具,而docker hub是一个提供镜像存储和分发服务的平台。
在国内,可以使用镜像网站打开。

1
docker images

列出本地已有的镜像。一般格式如下:

1
2
REPOSITORY          TAG       IMAGE ID       CREATED        SIZE
nginx latest 4bb46517cac3 2 weeks ago 142MB
  • REPOSITORY:镜像的名称。
  • TAG:镜像的标签,通常用于区分不同版本的镜像。
  • IMAGE ID:镜像的唯一标识符。
  • CREATED:镜像创建的时间。
  • SIZE:镜像的大小。
1
docker rmi <image_id>/<repository>:<tag>

删除指定的镜像。

拉取好镜像后,可以使用以下命令运行一个容器:

1
docker run <image_name>

这个命令会基于指定的镜像创建并启动一个新的容器,并在接下来的命令行中输出容器的日志信息。如果需要在后台运行容器,可以添加-d选项(即detached模式):

1
docker run -d <image_name>

将会打印出容器的ID,表示容器已经在后台运行。
若要查看正在运行的容器,可以使用以下命令:

1
docker ps

一般格式如下:

1
2
CONTAINER ID   IMAGE         COMMAND                  CREATED          STATUS          PORTS                    NAMES
d1f5c3e8b6a1 nginx:latest "/docker-entrypoint.…" 10 minutes ago Up 10 minutes 80/tcp hopeful_morse
  • CONTAINER ID:容器的唯一标识符。
  • IMAGE:容器所基于的镜像名称。
  • COMMAND:容器启动时执行的命令。
  • CREATED:容器创建的时间。
  • STATUS:容器的当前状态。
  • PORTS:容器映射的端口信息。
  • NAMES:容器的名称。

如果需要查看所有容器(包括未运行的),可以添加-a选项:

1
docker ps -a

实际上,docker不一定非要先拉取镜像再运行容器。你也可以直接运行一个容器,docker会自动帮你拉取所需的镜像:

1
docker run <image_name>

接下来介绍几个重要参数:

  • -p <host_port>:<container_port>:将容器的端口映射到宿主机的端口。例如,-p 8080:80表示将容器的80端口映射到宿主机的8080端口。这是因为宿主机和容器的网络是隔离的,不能直接访问容器内的服务,只有把宿主机的端口映射到容器的端口,才能通过宿主机访问容器内的服务。
  • -v <host_path>:<container_path>:将宿主机的目录挂载到容器内。例如,-v /home/user/data:/data表示将宿主机的/home/user/data目录挂载到容器内的/data目录。这是因为容器内的文件系统是隔离的,不能直接访问宿主机的文件,只有通过挂载目录,才能在容器内访问宿主机的文件。同时,这样做也可以实现数据的持久化,避免容器删除后数据丢失,否则容器内的数据会随着容器的删除而丢失。另外,我们也可以通过先创建一个命名卷,然后将命名卷挂载到容器内来实现数据的持久化。例如:
1
2
docker volume create my_volume
docker run -v my_volume:/data <image_name>

同时,可以使用docker volume inspect <volume_name>命令查看命名卷的详细信息,包括挂载路径等。docker volume ls命令可以列出所有的命名卷。docker volume rm <volume_name>命令可以删除指定的命名卷。docker volume prune命令可以删除所有未使用的命名卷。

  • --name <container_name>:为容器指定一个名称,方便后续管理和访问。例如,--name my_nginx表示将容器命名为my_nginx。例如:
1
docker run --name my_nginx -d -p 8080:80 nginx
  • -e <ENV_VAR>=<value>:设置容器内的环境变量。例如,-e ENV=production表示在容器内设置一个名为ENV的环境变量,值为production。例如,运行一个MySQL容器时,可以通过环境变量设置数据库的用户名和密码:
1
docker run -e MYSQL_ROOT_PASSWORD=my-secret-pw -e MYSQL_DATABASE=mydb -d mysql:latest
  • --rm:容器停止后自动删除容器文件系统。这对于临时运行的容器非常有用,可以避免占用过多的磁盘空间。例如:
1
docker run --rm <image_name>
  • -it:以交互模式运行容器,并分配一个伪终端(TTY)。这对于需要在容器内执行命令或调试非常有用。例如:
1
docker run -it <image_name> alpine
  • restart:设置容器的重启策略。例如,--restart unless-stopped表示容器会在崩溃后自动重启,除非手动停止它。这对于需要长期运行的服务非常有用。例如:
1
docker run --restart unless-stopped -d <image_name>

调试容器

如果需要停止或者启动一个容器,可以使用以下命令:

1
2
docker stop <container_id>/<container_name>
docker start <container_id>/<container_name>

查看容器的信息:

1
docker inspect <container_id>/<container_name>

查看容器的日志:

1
docker logs <container_id>/<container_name>

使用-f选项可以实时查看日志输出:

1
docker logs -f <container_id>/<container_name>

如果只需要创建一个容器而不启动它,可以使用以下命令:

1
docker create <image_name>

进入正在运行的容器:

1
docker exec -it <container_id>/<container_name> /bin/bash

退出容器:

1
exit

Dockerfile

Dockerfile是一个文本文件,包含了一系列指令,用于定义如何构建一个Docker镜像。通过编写Dockerfile,我们可以自动化地创建自定义的Docker镜像,而不需要手动执行一系列的docker命令。
举一个有生活气息的例子:镜像是一个制作糕点的模具,而Dockerfile则是这个模具的一张蓝图,描述了如何制作这个模具。通过Dockerfile,我们可以定义所需的原材料(基础镜像)、制作步骤(指令)以及最终的装饰(配置和环境变量)等。
下面是一个简单的Dockerfile示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 使用官方的Python基础镜像
FROM python:3.9-slim
# 设置工作目录
WORKDIR /app
# 复制当前目录的内容到容器的/app目录
COPY . /app
# 安装依赖包
RUN pip install --no-cache-dir -r requirements.txt
# 设置环境变量
ENV PORT=8080
# 暴露容器的8080端口
EXPOSE 8080
# 运行应用程序
CMD ["python", "app.py"] #或者 ENTRYPOINT ["python", "app.py"]

这个Dockerfile定义了一个基于Python 3.9的镜像,设置了工作目录为/app,将当前目录的内容复制到容器内的/app目录,安装了依赖包,设置了环境变量PORT,暴露了8080端口,并指定了容器启动时运行的命令。

关于requirements.txt

requirements.txt是一个文本文件,通常用于Python项目中,列出了项目所需的所有依赖包及其版本。通过这个文件,开发者可以方便地管理和安装项目的依赖包。
下面是一个简单的requirements.txt示例:

1
2
3
flask==2.0.1
requests==2.25.1
numpy==1.21.0

这个文件列出了三个依赖包:Flask、Requests和NumPy,并指定了它们的版本号。通过运行以下命令,可以根据requirements.txt文件安装所有列出的依赖包:

1
pip install -r requirements.txt

要构建这个Docker镜像,可以在包含Dockerfile的目录下运行以下命令:

1
docker build -t my_python_app .

这里,-t my_python_app用于为镜像指定一个名称,.表示当前目录是Dockerfile所在的位置。
然后我们使用以下命令运行容器:

1
docker run -d -p 8080:8080 my_python_app

这将启动一个后台运行的容器,并将宿主机的8080端口映射到容器的8080端口。在浏览器中访问http://localhost:8080,就可以看到运行在容器内的Python应用程序了。

我们还可以把这个镜像推送到Docker Hub,以便在其他机器上使用。首先,需要登录Docker Hub:

1
docker login

一般会有如下输出:

1
2
3
4
5
6
7
8
USING WEB-BASED LOGIN

i Info -> To sign in with credintials on the command line, use 'docker login -u <username>'

Your one-time device confirmation code is: XXXX-XXXX
Press ENTER to open your browser or submit your device code here: https://login.docker.com/XXXX-XXXX

Waiting for authorization...

这里需要你将提供的验证码输入提供的网址中进行验证。验证成功后,就可以将镜像推送到Docker Hub了。首先,需要为镜像打上用户名:

1
docker build -t <username>/my_python_app .

然后,使用以下命令将镜像推送到Docker Hub:

1
docker push <username>/my_python_app

这样,其他人就可以通过docker pull <username>/my_python_app命令来拉取并使用这个镜像了。

Docker 网络

Docker容器默认使用桥接网络(bridge network),这意味着每个容器都有自己的网络命名空间,并且通过NAT与宿主机和其他容器通信。Docker还提供了其他类型的网络模式,例如host网络、overlay网络和macvlan网络等。

桥接网络(bridge network)

这是Docker的默认网络模式。每个容器都会连接到一个虚拟的桥接网络,分配一个静态ip地址,一般是172.17开头,容器之间可以通过内部IP地址进行通信。宿主机需要通过端口映射与容器通信。
我们可以通过如下命令创建一个内部桥接网络的子网:

1
docker network create <network_name>

然后在运行容器时指定网络:

1
docker run --network <network_name> <image_name>

这样,容器就会连接到指定的桥接网络中,在同一个子网的所有容器都可以通过容器名称进行通信,而不需要使用IP地址。

网络示意图

Host网络(host network)

在host网络模式下,容器与宿主机共享网络命名空间。这意味着容器可以直接使用宿主机的网络接口和IP地址,而不需要进行端口映射。这样可以提高网络性能,但也会带来安全风险,因为容器可以访问宿主机的所有网络资源。
要使用host网络模式,可以在运行容器时添加--network host选项:

1
docker run --network host <image_name>

None网络模式

在none网络模式下,容器没有任何网络连接。这意味着容器无法与宿主机或其他容器通信。这个模式通常用于需要完全隔离网络的场景,例如安全敏感的应用程序。
要使用none网络模式,可以在运行容器时添加--network none选项:

1
docker run --network none <image_name>

使用命令查看所有网络:

1
docker network ls

删除网络:

1
docker network rm <network_name>

Docker Compose

当我们需要同时运行多个容器,并且这些容器之间有复杂的依赖关系时,使用Docker Compose可以大大简化管理和部署的过程。Docker Compose允许我们通过一个YAML文件来定义和运行多个容器应用程序。

下面是一个简单的docker-compose.yaml示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
services:
my_mongodb:
image: mongo
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: example
volumes:
- mongo_data:/data/db

my_mongodb_express:
image: mongo-express
prots:
- "8081:8081"
environment:
ME_CONFIG_MONGODB_ADMINUSERNAME: root
ME_CONFIG_MONGODB_ADMINPASSWORD: example
ME_CONFIG_MONGODB_SERVER: my_mongodb
depends_on:
- my_mongodb

在这个示例中,我们定义了两个服务:my_mongodbmy_mongodb_expressmy_mongodb服务使用官方的MongoDB镜像,并设置了环境变量来初始化数据库。它还挂载了一个命名卷mongo_data来持久化数据。my_mongodb_express服务使用Mongo Express镜像,并通过端口映射将容器的8081端口暴露到宿主机的8081端口。它还设置了环境变量来配置连接到MongoDB服务,并指定了依赖关系,确保在启动my_mongodb_express服务之前先启动my_mongodb服务。
实际上,这个文件与docker run命令是一一对应的。上面的docker-compose.yaml文件等价于以下两个docker run命令:

1
2
3
docker run -d --name my_mongodb -e MONGO_INITDB_ROOT_USERNAME=root -e MONGO_INITDB_ROOT_PASSWORD=example -v mongo_data:/data/db mongo --network network1 mongo

docker run -d --name my_mongodb_express -p 8081:8081 -e ME_CONFIG_MONGODB_ADMINUSERNAME=root -e ME_CONFIG_MONGODB_ADMINPASSWORD=example -e ME_CONFIG_MONGODB_SERVER=my_mongodb --network network1 mongo-express

这里唯一的区别就是,我们在docker-compose.yaml文件中没有显式地指定网络,因为Docker Compose会自动为所有服务创建一个默认的网络,并将它们连接到该网络中。另外,depends_on指令确保了服务的启动顺序。
我们可以通过以下命令来启动定义在docker-compose.yaml文件中的所有服务:

1
docker compose up -d

这将会自动识别当前目录下的docker-compose.yaml文件,并在后台启动所有定义的服务。如果文件名不是默认的docker-compose.yaml,可以使用-f选项指定文件名:

1
docker compose -f <custom-compose>.yaml up -d

要停止并删除这些服务,可以使用以下命令:

1
docker compose down

如果只想要停止服务而不删除容器,可以使用:

1
docker compose stop

接下来,我们启动服务:

1
docker compose start

我们可以使用以下命令查看正在运行的服务:

1
docker compose ps

一般格式如下:

1
2
3
4
    Name                   Command               State           Ports
----------------------------------------------------------
my_mongodb_1 docker-entrypoint.sh mongod Up 27017/tcp
my_mongodb_express_1 docker-entrypoint.sh npm start Up
  • Name:服务的名称。
  • Command:服务启动时执行的命令。
  • State:服务的当前状态。
  • Ports:服务映射的端口信息。
  • 我们还可以查看服务的日志输出:
1
docker compose logs -f

使用-f选项可以实时查看日志输出。

参考视频:40分钟的Docker实战攻略,一期视频精通Docker
参考文档: DockerCheatsheet

2026年1月12日。