文章

Docker 使用指南

Docker 使用指南

概述

Docker 是一个开源的容器化平台,它允许开发者将应用程序及其所有依赖项打包到一个轻量级、可移植的容器中。这个容器可以在任何支持 Docker 的环境中运行,确保应用程序在不同环境中的一致性。

为什么使用 Docker

传统部署的痛点:以前,搭建一致的开发环境和运行环境的工作通常是使用 Shell 脚本完成的,这种方式存在诸多问题:

  • 容易出错:脚本不完善、基础环境差异导致的各种问题
  • 耗时较长:复杂环境需要几十分钟甚至几小时的配置时间
  • 依赖复杂:需要多种工具配合,甚至需要人工参与
  • 维护困难:脚本的编写、维护、测试和管理都是难题

Docker 的优势:Docker 在解决环境一致性问题方面表现卓越:

  • 环境一致性:通过配置文件确保环境完全一致
  • 高效部署:支持镜像共享,大大提高部署效率
  • 低出错率:基础镜像固定,减少环境差异导致的问题
  • 配置简单:通过几个配置文件即可完成复杂环境搭建

本文内容及阅读建议

本文内容:本文旨在帮助读者掌握 Docker 的使用,充分利用其在搭建开发环境和运行环境方面的优势。内容涵盖:

  • 相关教程资源
  • Docker 的整体架构和核心概念
  • Docker 安装配置步骤
  • Docker 的代理配置
  • 常用的 docker 命令
  • Dockerfile 基本语法/指令和实用的模板(代码片段),以及最佳实践
  • Docker Compose 基本用法
  • 环境变量文件(.env)基本用法
  • Dev Container 简介和配置示例
  • Docker Hub 简介和使用示例
  • 专项应用指南:如利用 Docker 搭建易移植的 ROS2 开发环境
  • 问题总结与解决方案

阅读建议

  • 建议结合官方文档进行学习,本文提供要点总结和个人理解
  • 不要过于追求速度,理解概念比快速完成更重要
  • 多动手实践,Docker 的学习需要大量的实际操作
  • 遇到问题时优先查阅官方文档,它是最权威的信息源

教程资源

Docker 官方文档非常友好且详尽,特别是 Get Started 部分,强烈建议从官方文档开始学习。官方文档的优势在于内容准确、更新及时,且提供了循序渐进的学习路径。

官方教程(推荐)

入门教程

深入学习

中文学习资源

在线教程

整体架构与核心概念

详见 https://docs.docker.com/get-started/docker-overview/#docker-architecture

Docker 的整体架构如下所示:

docker-architecture.webp

可以看到,Docker 总体分为 3 大部分:Client、Docker Host、Registry。

核心组件

Docker Client(客户端)

  • 即我们平时使用的 docker 命令
  • 通过 HTTP REST API 与 Docker daemon 通信
  • 可以与远程或本地的 daemon 通信

Docker Host(主机)

  • 运行 Docker daemon(dockerd)的主机
  • 负责管理镜像、容器、网络和存储卷
  • 接收并执行来自客户端的命令

Registry(仓库)

  • 存储和分发 Docker 镜像的服务
  • Docker Hub 是默认的公共仓库
  • 可以搭建私有仓库

核心概念及关系

镜像(Image)

  • 只读的模板,用于创建容器
  • 由多个层(Layer)组成,支持增量更新
  • 通过 Dockerfile 构建

容器(Container)

  • 镜像的运行实例
  • 包含应用程序及其运行环境
  • 相互隔离但共享操作系统内核

docker 和 dockerd 通信机制

  • 本地通信:Unix Socket(/var/run/docker.sock)
  • 远程通信:TCP Socket(通常端口 2375/2376)
  • 应用层协议:HTTP REST API

图中命令的执行流程

图中指示了三个命令的大致流程:runbuildpull,对应图中三种箭头(实线、细虚线、粗虚线)

先说 docker build,执行该命令需要传递构建上下文(build context),例如docker build .,这时 docker 客户端(docker)会将当前目录下的所有文件传输给 docker 服务端(daemon),并命令它进行镜像的构建,服务端得到此命令和构建上下文后开始构建,构建完成后得到镜像(Images)并保存。

再说 docker pull,执行该命令会从 Registry 下载镜像文件,然后保存到本地。

最后说 docker run,该命令会在本地查找你给定的镜像参数,如果发现本地有,则直接使用并运行,如果没有,则查找 Dockerfile 以构建,如果找不到 Dockerfile,则搜索远程的 Registry,如果找到则 pull 到本地,得到镜像后将运行该镜像,从而得到容器。

安装和配置

详见官方文档

Linux

安装 Docker Engine

Linux 有众多发行版,这里以 Ubuntu 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 移除旧包
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done

# 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
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 测试
sudo docker run hello-world

# 添加以普通用户运行的能力
sudo groupadd docker
sudo usermod -aG docker $USER
newgrp docker

# 再测试
docker run hello-world

# 设置开机自启动
sudo systemctl enable docker.service
sudo systemctl enable containerd.service

详见 https://docs.docker.com/engine/install/ubuntu/https://docs.docker.com/engine/install/linux-postinstall/

配置 Docker Engine

Linux 下 Docker Engine 的配置文件位于 /etc/docker/daemon.json,目前我用到的配置只有代理,如下所示:

1
2
3
4
5
6
7
8
{
        "proxies": {
                "http-proxy": "http://192.168.3.107:7890",
                "https-proxy": "http://192.168.3.107:7890",
                "no-proxy": "127.0.0.0/8,192.168.0.0/16"
        }
}

关于网络代理配置的具体内容参见后文相关章节

Windows

官方文档:https://docs.docker.com/desktop/setup/install/windows-install/

在 Windows 上安装 Docker 主要是通过安装 Docker Desktop 来完成。由于 Docker Desktop 底层通常使用 WSL2,因此我们需要先安装 WSL2。安装 WSL2 过程中需要输入一些命令,因此会用到 Shell,这里我推荐 pwsh,此外建议安装 Windows Terminal,以方便在各个 shell 间切换,如 WSL shell、Git Bash、pwsh 等。

由于安装 WSL2 最简单的方法是安装一个 Ubuntu 发行版,因此我们会安装 Ubuntu 22.04。

下面先说明下如何安装上述基础工具。

安装基础工具

  1. 安装 Windows Terminal。Windows Terminal 在 Windows 11 中通常默认安装了的,如果没有安装则可以使用 Microsoft Store 安装。
  2. 安装 PowerShell 7+。建议通过 Microsoft Store 安装。需要注意的是,这里的 Powershell 7 不是 Windows 内置的旧版 Powershell 5
  3. WSL2 + Ubuntu 22.04:首先使用管理员权限打开 powershell,然后执行以下命令即可:

    1
    
    wsl --install Ubuntu-22.04
    

    如果你已经安装过 wsl,则可能需要更新,执行以下命令即可:

    wsl --update
    

    安装完成后使用 wsl --version 查看其版本是否是预期的 v2, 如下所示:

    PS C:\Users\wsxq2> wsl --version
    WSL 版本: 2.5.9.0
    内核版本: 6.6.87.2-1
    WSLg 版本: 1.0.66
    MSRDC 版本: 1.2.6074
    Direct3D 版本: 1.611.1-81528511
    DXCore 版本: 10.0.26100.1-240331-1435.ge-release
    Windows: 10.0.26100.4652
    PS C:\Users\wsxq2>
    

    详见:https://documentation.ubuntu.com/wsl/latest/howto/install-ubuntu-wsl2/

安装 Docker Desktop

以上基础工具安装完成后,即可安装 Docker Desktop。Docker Desktop 同样可以从 Microsoft Store 安装。或者使用 winget 安装:

1
winget install Docker.DockerDesktop

也可从官方网站下载安装

配置 Docker Desktop

  1. 网络代理。如果不设置代理(翻墙),docker pull 可能非常慢。假设你已经有梯子,且本地代理为:http://127.0.0.1:7890,则可在 Docker Desktop 中的 Settings-Resources-Proxies 处进行设置。详见 后文
  2. WSL 集成。开启 WSL 集成后,可以在 WSL 中使用 docker 命令,就好像是在 WSL 中的 Linux 中安装了 docker 一样,强烈推荐。步骤如下:
    1. 找到 Docker Desktop 中的 Settings-Resources-WSL integration
    2. 勾选Enable integration with my default WSL distro
    3. 重启 Docker Desktop 后可在下方的 WSL 列表中检查是否成功启用,如果没有,也可以手动勾选相应的发行版启用。

    详见 https://docs.docker.com/desktop/features/wsl/

    另外建议阅读 https://docs.docker.com/desktop/features/wsl/best-practices/https://docs.docker.com/desktop/features/wsl/use-wsl/,前者说明了建议在 WSL 中执行 docker 相关命令,以提高性能,后者说明了如何和 VSCODE 结合使用。

网络代理配置

Docker 的代理逻辑一度让我感到十分混乱,理清楚后发现其实很简单:docker 采用的是 C/S 架构,即 client/server 架构,客户端就是docker命令,服务端就是dockerd命令,ddaemon的缩写,表示后台运行。因此,对于 Docker 的代理主要包括两种,一个是影响客户端的,另一个是影响服务端的。

为了方便说明,官方将客户端称之为 docker cli,服务端称之为 docker daemon。对这两种代理,官方文档中均作出了明确的说明:

具体而言,docker daemon 的代理决定了 docker pull 时是否通过代理去从 Docker Hub 获取 docker 镜像;而 docker cli 的代理决定了 docker 容器内部的 HTTP_PROXY、HTTPS_PROXY 等环境变量是否设置,如果设置,则 curl、wget 等命令均会走这些环境变量指定的代理,而许多命令(如 PHP 的 composer 命令)底层访问 HTTP/HTTPS 时使用的即是 curl,所以会根据 HTTP_PROXY 等环境变量决定是否走代理。

对于每种代理,又有命令行选项和配置文件两种方式可以设置。此外,对于 daemon,如果安装了 Docker Desktop,则其代理又以界面配置为准,会忽略配置文件中的设置。下面具体说明

docker daemon

use docker desktop

Docker Desktop 是 docker 官方现在主推的 docker 使用方式,安装好后会自动安装 docker,dockerd,docker-compose 等相关命令,且能直观简单地管理容器、镜像、卷等。当然如果系统资源有限,或者不支持图形界面,则 Docker Desktop 就没有多大优势了。

如果使用了 Docker Desktop,则直接使用该 GUI 软件中的代理设置功能即可,具体位置在:Settings->Resources->Proxies

Docker Desktop 官方相关文档:

需要注意的是:代理地址 locahost 或者 127.0.0.1 可能不起效果,这时可使用网卡上设置的 IP 地址(虚拟网卡上设置的也可,例如vEthernet (WSL (Hyper-V firewall)))。当然如果该地址是通过 DHCP 获得的,那么需要注意它可能会改变,需要即时更新。

not use docker desktop

如果未使用 Dockers Desktop,则可通过dockerd的命令行参数传入代理配置或者通过其配置文件设置代理。当然,用得比较多的还是配置文件,其默认配置文件为 /etc/docker/daemon.json,添加如下内容即可使用代理:

1
2
3
4
5
6
7
{
  "proxies": {
    "http-proxy": "http://proxy.example.com:3128",
    "https-proxy": "https://proxy.example.com:3129",
    "no-proxy": "*.test.example.com,.example.org,127.0.0.0/8"
  }
}

例如:

1
2
3
4
5
6
7
{
  "proxies": {
    "http-proxy": "http://192.168.3.107:7890",
    "https-proxy": "http://192.168.3.107:7890",
    "no-proxy": "127.0.0.0/8,192.168.0.0/16"
  }
}

对于命令行方式,相应的命令行参数为--http-proxy--https-proxy。由于使用不多,且官方也不推荐,就不举例了。

除了这两种方式外,还有个环境变量方式。即dockerd也会按照环境变量HTTPS_PROXYHTTP_PROXY等进行代理配置。通常dockerd的启动是在开机时自动进行的,这时可能还未设置环境变量,那我们如何使其生效呢?对于使用systemctl的系统,可以通过 systemd unit file 来达到我们的目的。详见 Daemon proxy configuration | Docker Docs。也可参考 https://blog.csdn.net/omage/article/details/139560401

docker cli

Docker Desktop 中的设置不会影响 docker cli,docker cli 还是根据其配置文件或者命令行参数来设置代理的。其默认配置文件为~/.docker/config.json,故而直接在该文件中添加如下配置即可实现代理:

1
2
3
4
5
6
7
8
9
"proxies":
{
    "default":
    {
        "httpProxy": "http://127.0.0.1:3001",
            "httpsProxy": "http://127.0.0.1:3001",
            "noProxy": "*.test.example.com,.example2.com"
    }
}

例如 Windows 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
    "auths": {},
    "credsStore": "desktop",
    "proxies": {
        "default": {
            "httpProxy": "http://192.168.56.200:7890",
            "httpsProxy": "https://192.168.56.200:7890",
            "noProxy": "*.test.example.com,.example.org,127.0.0.0/8"
        }
    },
    "currentContext": "default",
    "plugins": {
        "-x-cli-hints": {
            "enabled": "true"
        }
    }
}

设置好后,执行docker run或者docker build时会自动设置容器中的相关环境变量,包括HTTP_PROXY等,从而实现容器内部的代理,使内部执行 HTTP/HTTPS 相关命令时走代理,包括apt命令、composer命令等。

也可在执行docker rundocker build时手动在命令行配置代理,例如:

1
2
docker build --build-arg HTTP_PROXY="http://proxy.example.com:3128" .
docker run --env HTTP_PROXY="http://proxy.example.com:3128" redis

Docker 常用命令

Docker 命令遵循 docker [OPTIONS] COMMAND [ARG...] 的格式。以下是最常用的命令及其详细说明:

docker build - 构建镜像

1
2
3
4
5
6
7
8
9
10
11
# 基本用法
docker build -t my-app:latest .

# 指定 Dockerfile
docker build -f Dockerfile.prod -t my-app:prod .

# 传递构建参数
docker build --build-arg HTTP_PROXY=http://proxy:8080 -t my-app .

# 不使用缓存
docker build --no-cache -t my-app .

核心参数说明:

  • -t, --tag:指定镜像名称和标签
  • -f, --file:指定 Dockerfile 路径
  • --build-arg:传递构建时变量
  • --no-cache:不使用构建缓存

docker run - 运行容器

1
2
3
4
5
6
7
8
9
10
11
# 基本运行
docker run ubuntu:20.04 echo "Hello World"

# 交互式运行
docker run -it ubuntu:20.04 bash

# 后台运行并映射端口
docker run -d -p 8080:80 nginx

# 挂载卷和设置环境变量
docker run -v $(pwd):/app -e NODE_ENV=production node:16

重要参数:

  • -i, --interactive:保持 STDIN 开放
  • -t, --tty:分配伪终端
  • -d, --detach:后台运行
  • -p, --publish:端口映射
  • -v, --volume:挂载卷
  • -e, --env:设置环境变量
  • --name:指定容器名称

docker pull - 拉取镜像

1
2
3
4
5
6
7
8
# 拉取最新版本
docker pull ubuntu

# 拉取指定版本
docker pull ubuntu:20.04

# 从私有仓库拉取
docker pull registry.example.com/my-app:v1.0

docker exec - 在运行容器中执行命令

1
2
3
4
5
6
7
8
# 在容器中执行命令
docker exec container_name ls /app

# 进入容器的交互式 shell
docker exec -it container_name bash

# 以特定用户执行
docker exec -u root container_name apt update

docker compose - 多容器应用管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 启动所有服务
docker compose up

# 后台启动所有服务
docker compose up -d

# 停止并删除所有服务
docker compose down

# 查看服务状态
docker compose ps

# 查看日志
docker compose logs [service_name]

详细配置说明请参考后面的 Docker Compose 专节

docker images - 镜像管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 列出所有镜像
docker images

# 删除镜像
docker rmi <image_id>

# 删除未使用的镜像
docker image prune

# 查看镜像详细信息
docker inspect <image_name>

# 给镜像打标签
docker tag <source_image> <target_image>

# 搜索镜像
docker search <keyword>

docker ps - 容器管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 列出运行中的容器
docker ps

# 列出所有容器(包括停止的)
docker ps -a

# 停止容器
docker stop <container_id>

# 启动容器
docker start <container_id>

# 重启容器
docker restart <container_id>

# 删除容器
docker rm <container_id>

# 删除所有停止的容器
docker container prune

docker logs - 查看日志

1
2
3
4
5
6
7
8
9
10
11
# 查看容器日志
docker logs <container_id>

# 实时查看日志
docker logs -f <container_id>

# 查看最近的日志
docker logs --tail 100 <container_id>

# 查看指定时间段的日志
docker logs --since "2024-01-01" <container_id>

docker cp - 文件复制

1
2
3
4
5
# 从容器复制文件到主机
docker cp <container_id>:/path/to/file /host/path

# 从主机复制文件到容器
docker cp /host/path <container_id>:/path/to/file

docker network - 网络管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 列出网络
docker network ls

# 创建网络
docker network create <network_name>

# 连接容器到网络
docker network connect <network_name> <container_id>

# 断开容器网络连接
docker network disconnect <network_name> <container_id>

# 查看网络详细信息
docker network inspect <network_name>

docker volume - 数据卷管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 列出数据卷
docker volume ls

# 创建数据卷
docker volume create <volume_name>

# 删除数据卷
docker volume rm <volume_name>

# 删除未使用的数据卷
docker volume prune

# 查看数据卷详细信息
docker volume inspect <volume_name>

docker system - 系统信息和清理

1
2
3
4
5
6
7
8
9
10
11
# 查看系统信息
docker system info

# 查看磁盘使用情况
docker system df

# 清理未使用的资源
docker system prune

# 清理所有未使用的资源(包括镜像)
docker system prune -a

命令参考:Docker CLI Reference

Docker Hub

Docker Hub 是 Docker 官方的公共镜像仓库,提供了丰富的预构建镜像资源,它是 Docker 中镜像(Images)的来源之一,另一来源是手动构建(docker buildDockerfile)。

主要特性

镜像资源:

  • 官方镜像:由 Docker 官方维护的基础镜像
  • 认证镜像:由知名厂商提供的官方镜像
  • 社区镜像:开发者贡献的开源镜像

核心功能:

  • 镜像托管和分发
  • 自动化构建(连接 GitHub/Bitbucket)
  • Webhook 集成
  • 访问统计和下载量

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
# 搜索镜像
docker search nginx

# 拉取官方镜像
docker pull nginx:latest

# 推送自己的镜像
docker tag my-app:latest username/my-app:latest
docker push username/my-app:latest

# 查看镜像信息
docker inspect nginx:latest

Dockerfile

Dockerfile 是构建 Docker 镜像的脚本文件,包含了一系列指令来描述如何构建镜像。

完整 Dockerfile 语法参考:Docker Dockerfile Reference

常用指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 基础镜像
FROM ubuntu:20.04

# 维护者信息
LABEL maintainer="your-email@example.com"

# 设置工作目录
WORKDIR /app

# 复制文件
COPY . /app
ADD https://example.com/file.tar.gz /app/

# 运行命令
RUN apt-get update && \
    apt-get install -y python3 python3-pip && \
    rm -rf /var/lib/apt/lists/*

# 设置环境变量
ENV NODE_ENV=production
ENV PATH="/app/bin:${PATH}"

# 暴露端口
EXPOSE 8080

# 设置用户
USER 1000

# 挂载点
VOLUME ["/data"]

# 启动命令
CMD ["python3", "app.py"]
# 或者使用 ENTRYPOINT
ENTRYPOINT ["python3", "app.py"]

实用镜像模板

这里所谓的模板实际是 Dockerfile 中的代码片段,不可直接使用,需要嵌入到你的 Dockerfile 中使用。仅供参考

设置时区为上海时区

由于许多镜像默认是 UTC 时区,这导致看日志时时间显示不太友好,可以通过以下片段将镜像的默认时区改为上海时区:

1
2
# Set the timezone to Shanghai
RUN echo 'Asia/Shanghai' > /etc/timezone && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

清华镜像加速

众所周知,Ubuntu 默认的 apt 资源、rosdep 资源等通常较慢,建议使用国内镜像以加速,清华镜像在这方面做得非常好,比较推荐。以下是相应代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Update the apt sources to use Tsinghua University's mirror
RUN mv /etc/apt/sources.list /etc/apt/sources.list.bak
RUN <<EOF cat > /etc/apt/sources.list
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse
EOF

# Add ROS 2 apt repository
RUN apt-get install curl gnupg2 -y && curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
RUN mv /etc/apt/sources.list.d/ros2.sources /etc/apt/sources.list.d/ros2.sources.bak && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] https://mirrors.tuna.tsinghua.edu.cn/ros2/ubuntu jammy main" | tee /etc/apt/sources.list.d/ros2.list > /dev/null

# 手动模拟 rosdep init
RUN mkdir -p /etc/ros/rosdep/sources.list.d/ && curl -o /etc/ros/rosdep/sources.list.d/20-default.list -L https://mirrors.tuna.tsinghua.edu.cn/github-raw/ros/rosdistro/master/rosdep/sources.list.d/20-default.list

# set rosdep mirror to Tsinghua University
ENV ROSDISTRO_INDEX_URL=https://mirrors.tuna.tsinghua.edu.cn/rosdistro/index-v4.yaml

注意以上是 ROS2 humble 的例子,其他版本需要做相应调整。

设置网络代理

由于 Ubuntu 中的常常会下载 Github 资源,而因为某些原因,国内从 Github 下载非常慢,使用代理可以加速:

1
2
3
4
5
6
7
# Set proxy host and port
ARG HTTP_PROXY_HOST=host.docker.internal
ARG HTTP_PROXY_PORT=7890

ENV http_proxy=http://$HTTP_PROXY_HOST:$HTTP_PROXY_PORT
ENV https_proxy=$http_proxy
ENV no_proxy=localhost,127.0.0.1,::1,192.168.0.0/16

注意其中的 ARG 是 Dockerfile 的参数,构建时可传入自定义值,详见 Dockerfile ARG 官方说明

此外 host.docker.internal 是一个特殊的域名,指向主机。详见 Networking | Docker Docs

代理环境变量配置

允许 sudo 命令继承代理相关的环境变量以及 DISPLAY 等环境变量:

1
RUN sed -i '/Defaults\s*env_reset/a Defaults env_keep = "http_proxy https_proxy ftp_proxy no_proxy DISPLAY XAUTHORITY"' /etc/sudoers

SSH 远程访问配置

为容器配置 SSH 服务,支持远程连接:

1
2
3
4
RUN mkdir /var/run/sshd
EXPOSE 22
USER root
CMD ["/usr/sbin/sshd", "-D"]

用户管理配置

方式一:简单用户创建

创建用户 ubuntu,密码 9841

1
2
RUN useradd -m -s /bin/bash -G sudo ubuntu -p '$6$Ri8lP7vRgVxNpBTC$RelZVvhFDpdWkkJSCVQY/WQ7tI36pmrctvJDEdYIAnGp48fBLZnmH/Z0gwDsLF6aOhUuhNwy0Dqs1exKCW0XX1'
USER ubuntu
方式二:灵活用户配置

支持自定义用户名和 UID/GID(修改其中的 bob):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ARG USERNAME=bob
ARG USER_UID=1000
ARG USER_GID=$USER_UID

# Delete user if it exists in container (e.g Ubuntu Noble: ubuntu)
RUN if id -u $USER_UID ; then userdel `id -un $USER_UID` ; fi

# Create the user
RUN groupadd --gid $USER_GID $USERNAME \
    && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \
    #
    # [Optional] Add sudo support. Omit if you don't need to install software after connecting.
    && apt-get update \
    && apt-get install -y sudo \
    && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
    && chmod 0440 /etc/sudoers.d/$USERNAME

这不仅会添加用户,还会设置 UID 和 GID,并配置 sudo 权限,且 sudo 不需要输入密码。

SSH 公钥认证配置

1
2
3
4
5
RUN mkdir $HOME/.ssh && chmod 700 $HOME/.ssh 

RUN <<EOF cat > $HOME/.ssh/authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEArWM+lwt05DEKKUwrAyFbW6CYocRAJot7hLA4RmQemIyzy5Dg1o+r8DdBfo8glZ3Ka54tKSmeDSCxpN1p3TOlfTODrCKxHYxp9OP0qHa7ZffMrfBq2gdGJF7rdv1yUflAkR2dd0VodpRqVRgQdrWAIMKvMg3R8Npurzku0djSGqmVU4Dht0qMnGE7l9iKhmiDkjDRpUK4fuQkhR8IcOYDtb0wcrg7o8qUI1eSxj5BrtfsJ22vut6dkNw/qrvGrJuJrG76zv1ZUtZEBQS6kC8JEbXHwtuZ3YKPlST7T5Jhy4jT+gyiQZ0f/kK1nQjcftURjjBoGZw4ViWhSp3YSEHFyQ== rsa-key-20180602
EOF

注意其中的<<EOF的用法,和 bash 中略有不同。相关资源:

Clangd 最新版本安装

自动下载并安装最新版本的 clangd 到 /usr/local/bin/clangd

1
2
3
4
5
6
7
# 安装最新版本的 clangd
RUN CLANGD_VERSION=$(curl -s https://api.github.com/repos/clangd/clangd/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') && \
    curl -L -o /tmp/clangd-linux.zip "https://github.com/clangd/clangd/releases/download/${CLANGD_VERSION}/clangd-linux-${CLANGD_VERSION}.zip" && \
    unzip /tmp/clangd-linux.zip -d /tmp/clangd-linux && \
    cp -r /tmp/clangd-linux/*/{bin,lib} /usr/local && \
    chmod +x /usr/local/bin/clangd && \
    rm -rf /tmp/clangd-linux.zip /tmp/clangd-linux

注意:以上命令要求的 Shell 为 Bash,而 Dockerfile 中默认使用的 Shell 为 sh,所以在上述代码前应放置以下代码:

1
SHELL ["/bin/bash", "-c"]

详见 Dockerfile SHELL 官方说明

最佳实践

避免不必要的系统升级

在 Dockerfile 中通常不需要执行 apt upgrade,原因如下:

  1. 基础镜像已优化:官方镜像通常已包含必要的安全更新
  2. 影响构建效率:显著增加构建时间,特别是频繁构建时
  3. 增加镜像体积:可能安装不必要的包
  4. 影响可重现性:避免升级有助于保持构建一致性

推荐做法:

  • 只执行 apt update 和安装具体需要的包
  • 需要最新更新时,选择更新的基础镜像版本
  • 生产环境定期更新基础镜像而非在 Dockerfile 中升级

例外情况:

  • 有特定安全需求时
  • 需要修复已知关键漏洞时

正确使用 apt-get update

建议在同一个 RUN 命令中组合 apt-get update、包安装和缓存清理操作,以确保镜像层不包含过时的包列表:

1
2
3
RUN apt-get update && \
    apt-get install -y package1 package2 && \
    rm -rf /var/lib/apt/lists/*

善用多阶段构建

1
2
3
4
5
6
7
8
9
10
# 使用多阶段构建
FROM node:16-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:16-alpine AS runtime
COPY --from=builder /app/node_modules ./node_modules
COPY . .
CMD ["npm", "start"]

细节优化

例如:

1
2
3
4
# 先复制依赖文件,再复制源码
COPY package*.json ./
RUN npm install
COPY . .

使用 .dockerignore 排除不必要文件

由于 docker build 时会输入构建上下文(build context),通常是当前目录(docker build .),如果当前目录下有无关文件如 .git 等就会降低效率,此时可以使用 .dockerignore 来忽略这些文件,这样一来就不会传输多余的文件到 docker daemon,从而提高效率。

.dockerignore 示例:

1
2
3
4
5
6
node_modules
.git
.gitignore
README.md
Dockerfile
.dockerignore

详见 Build context | Docker Docs

Docker Compose

详细配置参考:Docker Compose File Reference

Docker Compose 用于定义和运行多容器 Docker 应用程序,通过 YAML 文件 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
20
21
22
23
24
25
26
27
28
29
30
31
32
33
version: '3.8'

services:
  web:
    build: .
    ports:
      - "8080:80"
    environment:
      - NODE_ENV=production
    depends_on:
      - db
    volumes:
      - ./app:/app
    networks:
      - app-network

  db:
    image: postgres:13
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - app-network

volumes:
  db-data:

networks:
  app-network:
    driver: bridge

常用配置项说明

服务配置:

  • build:构建配置,可以是路径或详细配置
  • image:使用的镜像名称
  • ports:端口映射
  • environment:环境变量
  • volumes:卷挂载
  • depends_on:服务依赖关系
  • networks:网络配置

与 Dockerfile 的关系:

  • Dockerfile 定义如何构建单个镜像
  • docker-compose.yaml 定义如何运行多个容器服务
  • 可以在 compose 文件中引用 Dockerfile 进行构建

环境变量文件 (.env)

.env 文件用于存储 Docker Compose 的环境变量,提供配置的灵活性。

使用方式

.env 文件示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 应用配置
APP_NAME=myapp
APP_VERSION=1.0.0
NODE_ENV=production

# 数据库配置
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp
DB_USER=user
DB_PASSWORD=secret123

# 代理配置
HTTP_PROXY=http://proxy:8080
HTTPS_PROXY=http://proxy:8080

在 docker-compose.yaml 中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
version: '3.8'

services:
  web:
    image: ${APP_NAME}:${APP_VERSION}
    environment:
      - NODE_ENV=${NODE_ENV}
      - DB_HOST=${DB_HOST}
      - DB_PORT=${DB_PORT}
    
  db:
    image: postgres:13
    environment:
      - POSTGRES_DB=${DB_NAME}
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}

官方文档:Environment Variables in Compose

Dev Container

Dev Container 是 VS Code 的一项功能,利用 Docker 容器作为完整的开发环境,确保团队成员拥有一致的开发环境。

基本配置

.devcontainer/devcontainer.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
  "name": "Node.js Development",
  "image": "node:16",
  "features": {
    "ghcr.io/devcontainers/features/git:1": {}
  },
  "customizations": {
    "vscode": {
      "extensions": [
        "ms-vscode.vscode-typescript-next",
        "esbenp.prettier-vscode"
      ]
    }
  },
  "forwardPorts": [3000],
  "postCreateCommand": "npm install",
  "remoteUser": "node"
}

使用 Dockerfile:

1
2
3
4
5
6
7
8
9
10
11
{
  "name": "Custom Development Environment",
  "build": {
    "dockerfile": "Dockerfile",
    "context": ".."
  },
  "workspaceFolder": "/workspace",
  "mounts": [
    "source=${localWorkspaceFolder},target=/workspace,type=bind"
  ]
}

运行环境 vs 开发环境

运行环境:

  • 由 Dockerfile、docker-compose.yaml、.env 等文件定义
  • 专注于应用程序的运行和部署
  • 通常精简化,只包含运行时依赖

开发环境:

  • Dev Container + Docker 配置的组合
  • 包含开发工具、调试器、代码编辑器插件等
  • 提供完整的开发体验

详细指南:VS Code Dev Containers

专项应用指南

针对特定开发场景的详细配置指南:

问题总结与解决方案

网络和连接问题

1. Docker 网络子网冲突

1
2
3
# 问题:Docker 默认网络与本地网络冲突
# 解决:修改 Docker 默认网络配置
sudo vim /etc/docker/daemon.json
1
2
3
4
5
6
7
8
{
  "default-address-pools": [
    {
      "base": "172.17.0.0/12",
      "size": 24
    }
  ]
}

存储和磁盘问题

1. 磁盘空间不足

1
2
3
4
5
6
7
8
9
10
11
# 查看 Docker 磁盘使用
docker system df

# 清理未使用的资源
docker system prune -a --volumes

# 清理特定类型资源
docker image prune -a
docker container prune
docker volume prune
docker network prune

2. overlay 目录占用过大

1
2
3
# 问题:/var/lib/docker/overlay 占用大量空间
# 解决:定期清理未使用的镜像和容器
docker system prune -a

3. 数据卷权限问题

1
2
3
# 问题:容器内文件权限不正确
# 解决:使用用户映射
docker run -u $(id -u):$(id -g) -v $(pwd):/data <image>

镜像构建问题

1. 构建缓存问题

1
2
3
4
5
# 强制重新构建(不使用缓存)
docker build --no-cache -t <image_name> .

# 清理构建缓存
docker builder prune

2. 多平台构建问题

1
2
3
4
# 问题:在 M1 Mac 上构建 x86 镜像
# 解决:使用 buildx
docker buildx create --use
docker buildx build --platform linux/amd64,linux/arm64 -t <image> .

3. 基础镜像拉取失败

1
2
3
# 问题:网络原因无法拉取镜像
# 解决:配置镜像加速器或者使用代理
vim /etc/docker/daemon.json
1
2
3
4
5
6
{
  "registry-mirrors": [
    "https://docker.mirrors.ustc.edu.cn",
    "https://hub-mirror.c.163.com"
  ]
}

容器运行问题

1. 容器退出状态异常

1
2
3
4
5
6
7
8
# 查看容器退出原因
docker logs <container_id>

# 查看容器详细信息
docker inspect <container_id>

# 进入运行中的容器调试
docker exec -it <container_id> /bin/bash

Docker 容器跨主机通信?

如果主机都是 Linux,那么这个问题很好解决,直接使用 Docker 网络中的 host driver

如果一台主机是 Windows,另一台是 Linux,那么就很难解决,核心原因是 windows docker 不支持 host 网络模式。下面是相关链接:

Docker WSL2 方式支持容器中使用 GPU 吗?

NVIDIA:

AMD:

总结:NVIDIA 在最前沿,几乎所有 GPU 都支持,AMD 要落后许多,仅部分支持,且可能会遇到许多难以解决的问题。故以后优先购买 NVIDIA GPU,减少麻烦。

PUTTY 无法使用 HOME 和 END 键?

这个问题不算是 Docker 相关的问题,只是在使用 putty 过程中遇到的。解决方法非常简单:在 keyboard 界面设置 The function keys and keypad 为 SCO

wsl --update 太慢?

wsl --update 命令会从 github 下载内容,所以会很慢,建议使用代理。但需要注意的是,在 pwsh 中设置代理环境变量 $env:HTTP_PROXY 并不起作用,需要设置系统代理才行。

本文由作者按照 CC BY 4.0 进行授权