1. 项目概述:为什么要在Ubuntu 20.04上拥抱Docker?

如果你是一名在Ubuntu 20.04上折腾的开发或运维,大概率遇到过“环境依赖地狱”:项目A需要Python 3.8,项目B却死死依赖Python 3.6;一个微服务需要特定版本的Redis,另一个则要求完全不同的配置。更别提那些因为系统库版本差异,在测试环境跑得好好的,一到生产就崩掉的尴尬场景。Docker的出现,正是为了解决这些痛点。它通过容器化技术,将应用及其所有依赖打包成一个标准化的单元,实现了“一次构建,处处运行”的梦想。

Ubuntu 20.04 LTS(Focal Fossa)作为一个长期支持版本,以其稳定性和广泛的社区支持,成为了众多服务器和开发机的首选系统。在这个系统上部署Docker,意味着你能在一个成熟、可靠的基础设施上,构建起灵活、高效的现代化应用交付流水线。无论是想快速搭建一个MySQL测试实例,还是部署一个复杂的微服务架构,Docker都能让你像搭积木一样轻松。今天,我就以一个过来人的身份,带你从零开始,在Ubuntu 20.04上把Docker装好、配好、用起来,并分享那些官方文档里不会写的实操细节和避坑指南。

2. 核心思路与准备工作:知其然,更知其所以然

在动手敲命令之前,我们先理清几个关键概念和准备工作,这能让你在后续遇到问题时,知道从哪里着手排查,而不是盲目地复制粘贴。

2.1 Docker的核心组件与Ubuntu适配性解析

很多人以为Docker就是一个命令工具,其实它是一套引擎。在Ubuntu上,我们主要与以下组件打交道:

  1. Docker Engine(Docker守护进程) :这是核心,一个常驻后台的进程( dockerd ),负责管理容器、镜像、网络和存储卷。我们通过客户端命令行工具与它交互。
  2. Docker Client(命令行工具) :我们常用的 docker 命令就是客户端,它通过REST API与守护进程通信。
  3. Docker Registry(镜像仓库) :比如Docker Hub,是存储和分发Docker镜像的地方。默认从Docker Hub拉取,但在国内,直接访问速度堪忧,所以配置国内镜像加速是必选项。

为什么选择在Ubuntu 20.04上通过APT仓库安装,而不是下载二进制包?原因在于 依赖管理和自动更新 。APT(Advanced Package Tool)是Ubuntu的包管理器,它能自动处理Docker运行所需的所有库依赖(如 containerd.io , docker-ce-cli ),并且未来可以通过 sudo apt upgrade 一键更新Docker到新版本,省心又安全。相比之下,二进制包安装需要手动处理依赖和更新路径,更适合无法使用包管理器的特殊环境。

2.2 安装前的系统检查与必要配置

安装前的检查就像手术前的体检,能避免很多“手术中”的意外。请依次执行以下命令:

# 1. 检查系统版本,确认是Ubuntu 20.04
lsb_release -a

你应该看到类似 Description: Ubuntu 20.04.x LTS 的输出。

# 2. 更新现有软件包列表,确保获取最新的仓库信息
sudo apt update

# 3. 安装一些基础工具,后续会用到
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common gnupg lsb-release

这里解释一下这几个包的作用: apt-transport-https 允许APT通过HTTPS协议访问仓库,更安全; ca-certificates 是CA根证书,用于验证HTTPS连接; curl 用于从网络下载; software-properties-common 提供了管理软件源的工具; gnupg 用于处理GPG密钥,验证软件包签名。

# 4. (关键步骤)检查内核版本与虚拟化支持
uname -r

Docker需要Linux内核版本在3.10以上,Ubuntu 20.04默认的内核(5.4)完全满足。更关键的是虚拟化支持,尤其是如果你后续想运行非Linux系统的容器(通过 qemu ),或者使用Docker Desktop的某些高级功能。不过,对于标准的Linux容器,以下检查更为重要:

# 检查cgroups和命名空间支持,这是容器的基石
grep -E 'cgroup|namespace' /proc/self/mountinfo

如果能看到 cgroup cgroup2 namespace 相关的挂载信息,说明系统基础支持是完好的。很多人在虚拟机上安装失败,问题往往出在这里。

注意 :如果你是在虚拟机(如VMware、VirtualBox)或云服务器上操作,请务必在宿主机的BIOS/UEFI设置中,以及虚拟机软件配置里,开启CPU的虚拟化支持(Intel VT-x / AMD-V)。对于物理机,如果 kvm-ok 命令提示不支持,也需要进入BIOS开启。这是很多“Docker启动失败”问题的根源。

3. 分步安装与配置实战

准备工作就绪,现在开始正式的安装流程。我会详细解释每一步的目的,让你不仅会操作,还明白为什么这么做。

3.1 添加Docker官方APT仓库

为什么不直接用Ubuntu自带的 docker.io 包?因为那个版本通常很旧,是Docker早期开源版本的一个分支(现在叫 moby ),缺少很多新特性和安全更新。我们要安装的是Docker官方维护的社区版(Docker CE)。

# 1. 添加Docker的官方GPG密钥
# 这一步是为了验证从Docker仓库下载的软件包是真实且未被篡改的。
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

这里用了管道符 | ,将 curl 下载的密钥直接传递给 gpg 命令处理。 -fsSL 参数让 curl 在失败时静默退出、跟随重定向、显示错误、并以列表形式输出,是安全的下载习惯。 --dearmor 是将ASCII格式的密钥转换为二进制格式。

# 2. 设置稳定版(stable)仓库
# 这里我们明确指定了Ubuntu 20.04 (focal)的amd64架构仓库。
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
  • arch=$(dpkg --print-architecture) 会自动获取你的系统架构(如amd64, arm64)。
  • signed-by= 指向我们刚刚导入的GPG密钥文件,确保来源可信。
  • $(lsb_release -cs) 会输出你的Ubuntu代号,对于20.04就是 focal
  • tee 命令将内容同时输出到屏幕和文件, > /dev/null 将屏幕输出丢弃,保持终端整洁。

3.2 安装Docker Engine及相关组件

添加仓库后,就可以安装了。

# 1. 再次更新APT包索引,使新增的Docker仓库生效
sudo apt update

# 2. 安装Docker Engine、命令行工具以及Containerd
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
  • docker-ce : Docker社区版引擎。
  • docker-ce-cli : Docker命令行工具。
  • containerd.io : 行业标准的容器运行时,Docker Engine底层依赖它来管理容器生命周期。
  • docker-compose-plugin : 这是Docker Compose V2,作为一个CLI插件安装。旧式的 docker-compose (Python编写)已不再推荐,这个插件版本性能更好,集成度更高。

安装过程会自动创建 docker 用户组,并将Docker守护进程设置为开机自启。

3.3 安装后的关键配置与验证

安装完成只是第一步,正确的配置才能让它好用又安全。

1. 管理用户权限(避免每次都sudo) 默认情况下,执行 docker 命令需要 root 权限(即加 sudo )。为了方便,可以将当前用户加入 docker 用户组。

# 将当前用户加入docker组
sudo usermod -aG docker $USER

重要提醒 :执行此命令后, 必须完全注销当前会话并重新登录 ,或者重启系统,用户组变更才会生效。仅仅新开一个终端标签页是不够的。这是新手最常忽略的一点,会导致执行 docker ps 时依然报权限错误。

2. 配置国内镜像加速器 从Docker Hub拉取镜像速度慢且不稳定。配置国内镜像加速是提升体验的关键。这里以阿里云镜像加速为例(需要注册阿里云账号获取专属地址)。

# 创建或修改Docker守护进程的配置文件
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://your_mirror_url.mirror.aliyuncs.com"],
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF
  • registry-mirrors : 将 your_mirror_url 替换为你从阿里云容器镜像服务控制台获取的专属加速器地址。也可以添加多个,如中科大、网易的镜像源。
  • exec-opts : 设置cgroup驱动为 systemd ,这与Ubuntu 20.04默认使用systemd作为初始化系统保持一致,能获得更好的稳定性。
  • log-driver log-opts : 配置日志驱动和单个日志文件最大尺寸,防止日志撑爆磁盘。
  • storage-driver : 存储驱动, overlay2 是当前推荐且性能较好的驱动,适用于Ubuntu 20.04的 ext4 xfs 文件系统。

3. 重启Docker服务并验证 应用配置后,需要重启服务。

# 重新加载systemd配置,重启Docker服务
sudo systemctl daemon-reload
sudo systemctl restart docker

# 设置Docker服务开机自启
sudo systemctl enable docker

# 运行一个测试容器,验证安装和配置是否成功
docker run --rm hello-world

如果一切顺利,你将看到一段“Hello from Docker!”的欢迎信息,并说明你的安装看起来工作正常。这个命令会从配置的镜像加速器拉取 hello-world 镜像,创建并运行一个容器,输出信息后自动删除容器( --rm 参数作用)。

4. Docker基础使用与核心概念实操

安装配置妥当,我们来真正“用”起来。理解下面几个核心操作,你就能应对80%的日常场景。

4.1 镜像(Image)操作:应用的模板

镜像是只读的模板,包含了运行应用所需的代码、运行时、库、环境变量和配置文件。可以把它理解成面向对象编程中的“类”。

# 搜索镜像,例如搜索Ubuntu镜像
docker search ubuntu

# 拉取镜像,从仓库下载到本地。不指定标签则默认为latest。
docker pull ubuntu:20.04
docker pull nginx:alpine # 拉取更轻量的nginx alpine版本

# 列出本地已下载的镜像
docker images
# 或者使用更强大的格式
docker image ls --format "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedAt}}\t{{.Size}}"

# 删除本地镜像
docker rmi <image_id或image_name:tag>
# 强制删除(即使有容器基于它)
docker rmi -f <image_id>
# 清理所有未被使用的镜像(悬空镜像)
docker image prune

4.2 容器(Container)操作:镜像的运行实例

容器是镜像的可运行实例。你可以创建、启动、停止、移动或删除容器。它是“类”的“对象”。

# 1. 运行一个新容器
# -d: 后台运行
# --name: 给容器起个名字,方便管理
# -p: 端口映射,将宿主机的8080端口映射到容器的80端口
docker run -d --name my-nginx -p 8080:80 nginx:alpine

# 2. 查看容器状态
docker ps # 查看运行中的容器
docker ps -a # 查看所有容器(包括已停止的)

# 3. 与容器交互
# 查看容器日志
docker logs my-nginx
# 实时查看日志(类似 tail -f)
docker logs -f my-nginx

# 进入正在运行的容器内部执行命令(打开一个交互式终端)
docker exec -it my-nginx /bin/sh
# 退出容器终端,输入 exit 或按 Ctrl+D

# 4. 容器的生命周期管理
docker stop my-nginx # 停止容器
docker start my-nginx # 启动已停止的容器
docker restart my-nginx # 重启容器

# 5. 删除容器
docker rm my-nginx # 删除已停止的容器
docker rm -f my-nginx # 强制删除运行中的容器

4.3 数据持久化与网络:让容器更实用

默认情况下,容器内创建的文件只在容器生命周期内存在,容器删除,数据就没了。网络方面,容器有独立的网络命名空间。

数据卷(Volume) :是持久化容器数据的首选机制,完全由Docker管理,与宿主机生命周期解耦。

# 创建一个名为`my-data`的数据卷
docker volume create my-data

# 运行一个容器,并将数据卷挂载到容器内的`/app/data`目录
docker run -d --name my-app -v my-data:/app/data some-image

# 查看所有数据卷
docker volume ls

# 查看数据卷详情(如挂载点)
docker volume inspect my-data

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

绑定挂载(Bind Mount) :将宿主机上的一个特定目录或文件挂载到容器中。适合开发时同步代码。

# 将宿主机的`/home/user/project`目录挂载到容器的`/code`目录
docker run -d -v /home/user/project:/code some-dev-image

网络(Network) :默认情况下,Docker会创建三种网络: bridge (默认)、 host none 。你可以创建自定义网络,让容器间通过容器名通信。

# 创建一个自定义的桥接网络
docker network create my-network

# 运行两个容器,并加入同一个自定义网络
docker run -d --name web --network my-network nginx
docker run -it --name client --network my-network alpine ping web # 在client容器内可以直接ping通`web`这个主机名

5. 使用Docker Compose编排多容器应用

当你的应用需要多个容器协同工作时(比如一个Web应用需要Web服务器、数据库、缓存),手动管理每个容器非常繁琐。Docker Compose就是用来定义和运行多容器Docker应用的工具。我们安装的是V2插件版本,命令是 docker compose (注意中间没有横线)。

5.1 编写docker-compose.yml文件

在一个项目目录下,创建一个名为 docker-compose.yml 的文件。下面是一个经典的WordPress + MySQL的编排示例:

version: '3.8' # 指定Compose文件格式版本

services:
  # 数据库服务
  db:
    image: mysql:8.0 # 使用MySQL 8.0镜像
    container_name: wordpress_db
    restart: always # 总是重启,保证服务可用性
    environment: # 设置环境变量,这里是数据库的root密码和创建的数据库名、用户
      MYSQL_ROOT_PASSWORD: your_strong_root_password
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: your_wordpress_db_password
    volumes:
      - db_data:/var/lib/mysql # 使用命名卷持久化数据库数据
    networks:
      - wp-network

  # WordPress服务
  wordpress:
    image: wordpress:latest
    container_name: wordpress_app
    restart: always
    depends_on: # 依赖关系,确保db服务先启动
      - db
    ports:
      - "8080:80" # 将宿主机的8080端口映射到容器的80端口
    environment:
      WORDPRESS_DB_HOST: db:3306 # 这里直接用服务名`db`,Compose网络会自动解析
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: your_wordpress_db_password
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wp_data:/var/www/html # 持久化WordPress网站文件
    networks:
      - wp-network

# 定义数据卷,供上面的服务使用
volumes:
  db_data:
  wp_data:

# 定义网络,让两个服务在同一个隔离网络中通信
networks:
  wp-network:
    driver: bridge

5.2 使用Compose命令管理应用

在包含 docker-compose.yml 文件的目录下,执行以下命令:

# 1. 启动所有服务(在后台运行)
docker compose up -d

# 2. 查看当前目录下Compose应用的状态
docker compose ps

# 3. 查看服务的日志(可以指定服务名,如`db`)
docker compose logs
docker compose logs -f wordpress # 实时跟踪wordpress服务的日志

# 4. 停止并移除所有容器、网络(默认不会移除数据卷)
docker compose down

# 5. 停止并移除所有容器、网络,同时移除数据卷(数据会丢失!)
docker compose down -v

# 6. 在运行中的服务上执行命令,例如进入db容器的bash
docker compose exec db bash

通过Compose,你用一个YAML文件和几条简单的命令,就管理起了一个完整的多服务应用栈,极大地简化了部署复杂度。

6. 常见问题排查与性能优化心得

即使按照步骤操作,你也可能会遇到一些问题。这里记录了几个我踩过的坑和解决方案。

6.1 Docker服务启动失败与权限问题

问题现象 :执行 sudo systemctl status docker 发现服务状态不是 active (running) ,或者执行 docker ps 时提示 Cannot connect to the Docker daemon

排查思路

  1. 检查服务状态与日志

    sudo systemctl status docker
    sudo journalctl -xu docker --no-pager -n 50 # 查看Docker服务的详细日志
    

    日志是定位问题的第一手资料。常见错误包括:

    • iptables 相关错误:可能是防火墙规则冲突。可以尝试暂时禁用 ufw ( sudo ufw disable ) 或调整Docker的iptables配置。
    • cgroup 挂载错误:检查 /proc/self/mountinfo 中是否有cgroup相关条目。确保系统已正确启用cgroup。
    • 存储驱动错误 :如果日志提到 overlay2 xfs 相关错误,可能是文件系统不支持 d_type 。用 xfs_info / 检查 ftype 是否为1。如果不是,可能需要重新格式化分区。
  2. 检查用户组权限 : 如果你已经将用户加入 docker 组但仍然需要 sudo ,请确认你是否已经 重新登录 了会话。可以运行 groups 命令查看当前用户所属组是否包含 docker 。也可以直接测试: docker run --rm hello-world 如果不加 sudo 能运行,说明权限正确。

  3. 检查端口冲突 : 如果Docker能启动,但运行容器时端口映射失败(如 Error starting userland proxy: listen tcp4 0.0.0.0:8080: bind: address already in use ),说明宿主机8080端口已被占用。使用 sudo netstat -tulpn | grep :8080 查找占用进程并停止它,或者为容器映射另一个端口。

6.2 镜像拉取缓慢或失败

问题现象 docker pull 速度极慢,或出现 net/http: TLS handshake timeout 等超时错误。

解决方案

  1. 确认镜像加速器配置正确 :检查 /etc/docker/daemon.json 文件中的 registry-mirrors 地址是否有效且格式正确(JSON格式要求严格,最后一个项后不能有逗号)。修改后务必执行 sudo systemctl restart docker
  2. 使用多个镜像源 :可以在 registry-mirrors 数组中配置多个地址,Docker会依次尝试。
    "registry-mirrors": [
      "https://hub-mirror.c.163.com",
      "https://mirror.baidubce.com",
      "https://docker.mirrors.ustc.edu.cn"
    ]
    
  3. 直接使用国内镜像仓库 :对于一些常用镜像(如 ubuntu , nginx , mysql ),可以直接从阿里云、腾讯云的镜像仓库拉取,例如 docker pull registry.cn-hangzhou.aliyuncs.com/library/ubuntu:20.04

6.3 容器磁盘空间占用过大

问题现象 :运行一段时间后, /var/lib/docker 目录(Docker默认数据根目录)体积暴涨,导致系统磁盘空间不足。

清理策略 : Docker占用的空间主要包括:未使用的镜像、停止的容器、构建缓存、数据卷。定期清理是必要的。

# 1. 查看Docker磁盘使用概况
docker system df

# 2. 一键清理所有未使用的资源(镜像、容器、网络、构建缓存),但**不清理数据卷**。
docker system prune -a

# 3. 谨慎清理数据卷(会丢失数据!)
docker volume prune

# 4. 清理所有停止的容器
docker container prune

# 5. 清理所有悬空镜像(未被任何容器引用的镜像)
docker image prune

实操心得 :建议将 docker system prune 加入定时任务(如cron每周执行一次),但务必注意生产环境,避免误删正在使用的中间镜像或缓存。对于数据卷,清理前一定要确认其数据是否已备份或可丢弃。

6.4 容器内应用时区不对

问题现象 :在容器内运行 date 命令,显示的是UTC时间,而不是东八区(北京时间)。

解决方案 :在运行容器时,将宿主机的时区文件挂载到容器内。

docker run -d --name some-app \
  -v /etc/localtime:/etc/localtime:ro \
  -v /etc/timezone:/etc/timezone:ro \
  your-image

或者,在构建自己的Docker镜像时,在Dockerfile中设置时区环境变量:

FROM ubuntu:20.04
RUN apt-get update && apt-get install -y tzdata
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

7. 进阶使用:构建自定义镜像与Dockerfile编写

拉取现成镜像只是开始,更多时候我们需要定制自己的镜像。Dockerfile是一个文本文件,包含了一条条构建镜像所需的指令。

7.1 一个完整的Dockerfile示例

假设我们要为一个简单的Python Flask应用构建镜像。

# 第一阶段:构建阶段,使用较大的基础镜像包含构建工具
FROM python:3.9-slim as builder

WORKDIR /app

# 将依赖文件复制到工作目录
COPY requirements.txt .
# 使用清华PyPI镜像加速,安装依赖到/usr/local
RUN pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt

# 第二阶段:运行阶段,使用更小的基础镜像
FROM python:3.9-alpine
# 设置时区
RUN apk add --no-cache tzdata && \
    cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
    echo "Asia/Shanghai" > /etc/timezone && \
    apk del tzdata

WORKDIR /app

# 从构建阶段仅复制安装好的Python包
COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
# 复制应用代码
COPY . .

# 声明容器运行时监听的端口
EXPOSE 5000

# 设置环境变量,指定Flask应用入口
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0

# 定义容器启动时执行的命令
CMD ["flask", "run"]

7.2 构建与运行自定义镜像

在包含 Dockerfile app.py requirements.txt 的目录下执行:

# 构建镜像,-t 参数给镜像打标签,格式为 name:tag
docker build -t my-flask-app:1.0 .

# 查看构建好的镜像
docker images | grep my-flask-app

# 运行容器
docker run -d -p 5000:5000 --name my-flask-container my-flask-app:1.0

7.3 Dockerfile编写最佳实践与心得

  1. 使用多阶段构建 :如上例所示,可以显著减小最终镜像体积。第一阶段安装编译依赖、构建代码;第二阶段只复制构建产物到一个小体积的基础镜像中。
  2. 合理利用构建缓存 :Docker会缓存每一层。把变化频率低的指令(如安装系统包、依赖)放在Dockerfile前面,把变化频率高的指令(如复制源代码)放在后面。
  3. 使用 .dockerignore 文件 :类似于 .gitignore ,可以避免将本地不必要的文件(如 __pycache__ , .git , .env )复制到镜像构建上下文中,加速构建过程并避免泄露敏感信息。
  4. 一个容器只运行一个进程 :这是微服务的最佳实践。如果需要运行多个关联进程(如Web服务器和后台Worker),应考虑使用Supervisor等进程管理工具,或者更好的方式是拆分为多个容器,用Docker Compose编排。
  5. 以非root用户运行容器 :在Dockerfile中创建并使用一个非root用户,增强安全性。
    RUN groupadd -r appuser && useradd -r -g appuser appuser
    USER appuser
    
  6. 标签(Tag)管理 :为镜像使用有意义的标签,如版本号( v1.2.3 )、Git提交哈希( git-abc123 ),避免过度依赖 latest 标签,这有助于回滚和追踪问题。

在Ubuntu 20.04上搭建好Docker环境,并掌握了这些核心操作和问题排查技巧后,你就拥有了一个强大且一致的开发、测试和部署环境。无论是个人项目还是团队协作,容器化都能极大地提升效率和可靠性。记住,关键不在于记住所有命令,而在于理解其背后的原理(镜像、容器、数据卷、网络),这样无论遇到什么新工具或复杂场景,你都能从容应对。

更多推荐