Docker on Ubuntu:完整安装和使用指南
Docker 是一个开源容器化平台,它将应用程序及其依赖项打包到称为容器的隔离、可移植单元中。与虚拟机不同,容器共享宿主机的 OS 内核,使其更轻量、启动更快、资源利用率更高——这对于在 VPS Hosting 环境中运行工作负载的用户来说是一个关键区别,因为计算资源直接影响成本和性能。
本指南涵盖了在 Ubuntu 20.04、22.04 和 24.04 LTS 上完整的 Docker 安装流程,包括安装后的安全加固、Docker Compose 工作流,以及大多数教程所忽略的生产相关命令模式。
前提条件与系统要求
在执行任何命令之前,请验证以下内容:
- Ubuntu 版本: 20.04 LTS(Focal)、22.04 LTS(Jammy)或 24.04 LTS(Noble)。在仓库设置过程中使用的 `lsb_release -cs` 命令将自动检测您的代号。
- 架构: `amd64`、`arm64` 或 `armhf` 均受 Docker 官方仓库支持。
- 内核版本: Docker 需要 Linux 内核 3.10 或更高版本。运行 `uname -r` 进行确认。
- 用户权限: 安装和守护进程管理必须具备 `sudo` 或 root 访问权限。
- 磁盘空间: 托管 `/var/lib/docker` 的分区至少需要 2 GB 可用空间,这是 Docker 存储镜像、容器、卷和构建缓存的位置。在生产系统上,请将此目录挂载到专用分区或卷上。
安装前的关键步骤: 如果您之前从 Ubuntu 默认的 `apt` 仓库安装了 Docker(即 `docker.io` 软件包),请先将其删除,以避免与官方 Docker CE 软件包发生冲突:
“`bash
sudo apt remove docker docker-engine docker.io containerd runc
“`
步骤 1:更新系统软件包
在添加任何新仓库之前,将软件包索引和已安装的软件包更新至最新版本:
“`bash
sudo apt update
sudo apt upgrade -y
“`
这可确保 `apt` 的依赖解析器基于当前软件包元数据运行,并且您的基础系统库不会过时——这是容器化应用程序出现细微运行时错误的常见原因。
步骤 2:从官方仓库安装 Docker Engine
Ubuntu 默认的 `apt` 仓库提供了一个名为 `docker.io` 的软件包,该软件包由 Canonical 维护,通常落后于 Docker 官方版本数个版本。在生产环境中,请始终从 Docker 自己的仓库进行安装。
2.1 安装传输和验证依赖项
“`bash
sudo apt install apt-transport-https ca-certificates curl software-properties-common gnupg lsb-release -y
“`
为什么需要 `gnupg`? 从 Ubuntu 22.04 开始,`gpg` 并不总是默认存在。明确包含它可防止 GPG 密钥导入静默失败。
2.2 添加 Docker 官方 GPG 密钥
“`bash
sudo install -m 0755 -d /usr/share/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg –dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
“`
`chmod a+r` 步骤在教程中经常被跳过,但在 `apt` 以受限用户上下文运行的系统上是必要的——没有它,软件包管理器将无法读取密钥环,并在 `apt update` 期间抛出 `NO_PUBKEY` 错误。
2.3 添加 Docker 稳定版仓库
“`bash
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)` 替换对于基于 ARM 的服务器至关重要。在此处硬编码 `amd64` 是一个常见错误,会导致 ARM 实例上出现静默软件包解析失败。
2.4 安装 Docker Engine、CLI 和插件
“`bash
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
“`
软件包说明:
| 软件包 | 作用 |
|---|
| — | — |
|---|
| `docker-ce` | Docker Engine 守护进程(`dockerd`) |
|---|
| `docker-ce-cli` | 客户端 CLI(`docker` 命令) |
|---|
| `containerd.io` | 底层容器运行时(符合 OCI 标准) |
|---|
| `docker-buildx-plugin` | 扩展构建功能(多平台、BuildKit) |
|---|
| `docker-compose-plugin` | Compose V2 作为 Docker CLI 插件集成 |
|---|
关于 `containerd.io` 的说明: 这与 Ubuntu 默认仓库中的 `containerd` 软件包不同。Docker 的 `containerd.io` 是经过特定测试的 containerd 运行时版本。混用两者是守护进程启动失败的已知原因。
步骤 3:验证安装
确认守护进程处于活动状态并已启用开机自启:
“`bash
sudo systemctl status docker
sudo systemctl enable docker
“`
检查已安装的版本:
“`bash
sudo docker –version
sudo docker info
“`
`docker info` 比单独使用 `–version` 提供更多信息——它会显示正在使用的存储驱动程序(通常为 `overlay2`)、cgroup 驱动程序(`systemd` 与 `cgroupfs`),以及 Docker 可访问的 CPU 数量和内存。
运行标准冒烟测试:
“`bash
sudo docker run hello-world
“`
成功运行后会打印”Hello from Docker!”消息,并确认 Docker 守护进程、从 Docker Hub 拉取镜像以及容器执行均正常运行。
步骤 4:配置 Docker 的非 root 访问权限
默认情况下,Docker socket(`/var/run/docker.sock`)归 `root` 和 `docker` 组所有。不在 `docker` 组中的任何用户必须对每个 Docker 命令使用 `sudo`。
“`bash
sudo usermod -aG docker $USER
“`
无需注销即可应用组成员身份:
“`bash
newgrp docker
“`
验证:
“`bash
docker run hello-world
“`
安全警告: `docker` 组的成员身份实际上等同于无密码的 `sudo`。`docker` 组中的用户可以轻易地将宿主机文件系统挂载到容器中,并绕过所有文件系统级访问控制。在多租户系统或共享服务器上,请考虑改用无 root 权限的 Docker:
“`bash
dockerd-rootless-setuptool.sh install
“`
无 root 权限模式在非特权用户命名空间下运行 Docker 守护进程和容器,大幅降低攻击面。对于多个用户共享同一宿主机的任何环境,这是推荐的配置。
步骤 5:Docker 常用命令参考
镜像管理
“`bash
Pull a specific image version from Docker Hub
docker pull nginx:1.27-alpine
List locally cached images
docker images
Remove a specific image
docker rmi nginx:1.27-alpine
Remove all dangling (untagged) images to reclaim disk space
docker image prune
Remove all unused images (not just dangling)
docker image prune -a
“`
生产建议: 在任何自动化或生产工作流中,始终拉取带版本标签的镜像(例如 `nginx:1.27-alpine`),而非 `latest`。`latest` 标签是可变的——在推送到注册表后,它可能指向不同的镜像,从而破坏可重现性。
容器生命周期
“`bash
Run a container interactively with a pseudo-TTY
docker run -it ubuntu:22.04 /bin/bash
Run a container in detached mode with port mapping and a name
docker run -d -p 8080:80 –name my-nginx nginx:1.27-alpine
List running containers
docker ps
List all containers (including stopped)
docker ps -a
Stop a running container gracefully (SIGTERM, then SIGKILL after timeout)
docker stop my-nginx
Start a stopped container
docker start my-nginx
Remove a stopped container
docker rm my-nginx
Force-remove a running container (sends SIGKILL immediately)
docker rm -f my-nginx
View real-time logs
docker logs -f my-nginx
Execute a command inside a running container
docker exec -it my-nginx /bin/sh
“`
资源与系统检查
“`bash
Display real-time resource usage statistics
docker stats
Inspect detailed container metadata (JSON)
docker inspect my-nginx
Display disk usage by Docker objects
docker system df
Remove all stopped containers, unused networks, dangling images, and build cache
docker system prune
“`
`docker system prune` 是长期运行服务器最重要的维护命令之一。如果不定期清理,Docker 的构建缓存和已停止的容器可能会在活跃的开发或 CI 宿主机上占用数十 GB 的空间。
步骤 6:Docker Compose——多容器应用编排
Docker Compose V2(即之前安装的 `docker-compose-plugin`)以 `docker compose`(带空格)的形式调用,而非旧版的 `docker-compose`(带连字符)。如果您安装了该插件,两种语法均可使用,但 V2 是当前标准。
6.1 了解 Compose 文件结构
创建项目目录和 `compose.yml` 文件(Compose V2 中的首选文件名;`docker-compose.yml` 仍支持向后兼容):
“`bash
mkdir ~/my-web-app && cd ~/my-web-app
nano compose.yml
“`
一个包含 Nginx 和后端服务的生产实际示例:
“`yaml
services:
web:
image: nginx:1.27-alpine
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./html:/usr/share/nginx/html:ro
depends_on:
- app
restart: unless-stopped
app:
image: node:20-alpine
working_dir: /usr/src/app
volumes:
- ./app:/usr/src/app
command: node server.js
environment:
- NODE_ENV=production
restart: unless-stopped
“`
关键 Compose 指令说明:
- `restart: unless-stopped` — 容器在崩溃或系统重启后自动重启,除非被操作员明确停止。这是长期运行服务的正确策略;`always` 即使对于被有意停止的容器也会重启。
- `depends_on` — 控制启动顺序,但不等待服务*就绪*(例如,数据库接受连接)。如需就绪门控,请结合使用 `healthcheck` 和 `condition: service_healthy`。
- `volumes` 与 `:ro` — 将配置文件以只读方式挂载,可防止被入侵的容器进程修改自身配置。
6.2 Compose 工作流命令
“`bash
Start all services in detached mode
docker compose up -d
View logs for all services
docker compose logs -f
View logs for a specific service
docker compose logs -f web
List running Compose services
docker compose ps
Scale a specific service to multiple replicas
docker compose up -d –scale app=3
Stop services without removing containers
docker compose stop
Stop and remove containers, networks, and volumes
docker compose down –volumes
Rebuild images before starting (useful after code changes)
docker compose up -d –build
“`
6.3 验证运行中的服务
“`bash
curl -I http://localhost:8080
“`
`200 OK` 响应确认 Nginx 正常提供服务。对于在远程 VPS Hosting 实例上运行的服务,请将 `localhost` 替换为您服务器的公共 IP 地址,并确保防火墙中已开放相关端口(`ufw allow 8080/tcp`)。
步骤 7:Docker 网络基础
了解 Docker 的网络模型对于构建能够安全通信的多容器应用程序至关重要。
默认网络驱动程序:
| 驱动程序 | 使用场景 |
|---|
| — | — |
|---|
| `bridge` | 独立容器的默认选项;宿主机上的隔离网络命名空间 |
|---|
| `host` | 容器共享宿主机的网络栈;性能最高,零隔离 |
|---|
| `none` | 无网络访问;适用于批处理或对安全敏感的工作负载 |
|---|
| `overlay` | 用于 Docker Swarm 集群的多宿主机网络 |
|---|
| `macvlan` | 为容器分配 MAC 地址;在网络上显示为物理设备 |
|---|
创建并使用自定义桥接网络:
“`bash
Create an isolated network
docker network create my-app-network
Run containers attached to the custom network
docker run -d –name db –network my-app-network postgres:16-alpine
docker run -d –name api –network my-app-network my-api-image
Containers on the same custom bridge network can resolve each other by name
Inside 'api', you can connect to 'db' using the hostname 'db'
“`
自定义桥接网络通过容器名称提供容器间的自动 DNS 解析。默认的 `bridge` 网络则不具备此功能——这是一个关键区别,当开发人员假设默认网络上的容器可以通过名称相互访问时,往往会导致连接失败。
步骤 8:使用 Docker 卷实现持久化数据
容器在设计上是短暂的。写入容器文件系统内部的任何数据在容器被删除时都会丢失。对于持久化存储,请使用卷或绑定挂载。
“`bash
Create a named volume
docker volume create pgdata
Use the volume with a container
docker run -d
–name postgres-db
-e POSTGRES_PASSWORD=securepassword
-v pgdata:/var/lib/postgresql/data
postgres:16-alpine
List volumes
docker volume ls
Inspect a volume (shows mount point on host)
docker volume inspect pgdata
Remove unused volumes
docker volume prune
“`
卷与绑定挂载的对比:
| 特性 | 命名卷 | 绑定挂载 |
|---|
| — | — | — |
|---|
| 由 Docker 管理 | 是 | 否 |
|---|
| 需要宿主机路径 | 否 | 是 |
|---|
| 跨宿主机可移植 | 是(使用卷驱动程序) | 否 |
|---|
| 最适合 | 数据库数据、应用程序状态 | 开发代码、配置文件 |
|---|
| 备份机制 | `docker run –volumes-from` | 标准文件系统工具 |
|---|
步骤 9:保持 Docker 更新
Docker 的官方仓库通过标准的 `apt` 机制处理更新:
“`bash
sudo apt update
sudo apt upgrade -y
“`
仅更新 Docker 相关软件包而不升级整个系统:
“`bash
sudo apt install –only-upgrade docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
“`
在进行主要版本升级之前,请查阅 Docker 发行说明,特别是关于存储驱动程序、cgroup 版本处理或可能影响现有 `compose.yml` 文件的已弃用 API 版本的变更。
Docker 与其他容器化方案的对比
| 特性 | Docker Engine | Podman | LXC/LXD | containerd(独立) |
|---|
| — | — | — | — | — |
|---|
| 守护进程架构 | 集中式守护进程 | 无守护进程 | 基于守护进程 | 基于守护进程 |
|---|
| 无 root 权限支持 | 是(v20+) | 原生支持 | 有限 | 是 |
|---|
| Docker Compose 支持 | 原生支持 | 通过 `podman-compose` | 否 | 否 |
|---|
| OCI 合规性 | 是 | 是 | 否(LXC 格式) | 是 |
|---|
| Kubernetes 集成 | 通过 CRI-dockerd shim | 原生 CRI | 否 | 原生 CRI |
|---|
| Windows/macOS 支持 | Docker Desktop | 有限 | 否 | 否 |
|---|
| 最适合 | 通用开发和生产 | 注重安全、无 root 权限 | 系统容器、虚拟机 | Kubernetes 节点 |
|---|
对于在裸机上大规模运行容器化工作负载的团队,独立服务器环境可让您完全控制内核参数、存储 I/O 调度和网络配置——这些都直接影响容器密度和性能。
生产环境安全加固清单
在生产环境中运行 Docker 之前,请解决以下问题:
守护进程配置(`/etc/docker/daemon.json`):
“`json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"storage-driver": "overlay2",
"userns-remap": "default",
"live-restore": true,
"no-new-privileges": true
}
“`
- `log-opts` 与 `max-size` 和 `max-file`: 如果不进行日志轮转,Docker 的 JSON 日志文件将填满您的磁盘。这是容器化宿主机上意外服务器中断最常见的原因之一。
- `userns-remap: "default"`: 启用用户命名空间重映射,使容器 root(UID 0)映射到宿主机上的非特权 UID。
- `live-restore: true`: 允许容器在 Docker 守护进程崩溃或在升级期间重启时继续运行——这对于零停机维护至关重要。
- `no-new-privileges: true`: 防止容器进程通过 `setuid` 或 `setgid` 二进制文件获取额外权限。
网络和防火墙注意事项:
Docker 默认直接操作 `iptables` 并绕过 `ufw` 规则。即使 `ufw deny 8080` 已设置,带有已发布端口(`-p 8080:80`)的容器仍可从互联网访问。要在 Docker 的 `iptables` 操作之上强制执行 `ufw` 规则,请将 `"iptables": false` 添加到 `daemon.json` 并手动管理路由,或使用 Docker 的 `–network host` 配合明确的 `ufw` 规则。
对于需要 HTTPS 终止的项目,请将您的容器化应用程序与正确配置的反向代理(Nginx 或 Traefik)以及有效证书配合使用。SSL 证书是在容器化堆栈后面运行的任何生产 Web 服务的先决条件。
如果您的工作负载涉及容器内的机器学习推理、模型服务或 GPU 加速数据处理,NVIDIA Container Toolkit 可直接与 Docker Engine 集成。GPU Hosting 提供这些工作负载所需的底层硬件。
对于使用基于 Web 的容器管理来管理多个项目的团队,带 cPanel 的 VPS 提供了一个托管控制面板环境,可以为更简单的应用程序堆栈补充基于 Docker 的部署。
关键技术要点与决策矩阵
何时在 VPS 上使用 Docker,何时使用独立服务器:
- 对于开发环境、预发布环境以及容器密度为 10–50 个容器的中低流量生产工作负载,请使用 VPS。
- 当容器密度超过 50 个实例、需要可预测的 I/O 性能(无嘈杂邻居效应),或工作负载需要内核参数调优(`sysctl`)时,请使用独立服务器。
上线前的操作清单:
- 在 `daemon.json` 中配置日志轮转(`max-size`、`max-file`)
- 启用 `live-restore` 以在守护进程重启时不中断容器运行
- 对有状态服务数据使用命名卷,而非绑定挂载
- 在所有 `compose.yml` 文件中固定镜像版本——生产环境中绝不使用 `latest`
- 在多租户宿主机上启用 `userns-remap` 或运行无 root 权限的 Docker
- 在 Docker 安装后审计 `iptables` 规则,确认防火墙策略未被绕过
- 为所有长期运行的服务设置 `restart: unless-stopped`
- 在定时 cron 任务中运行 `docker system prune` 以防止磁盘耗尽
- 对所有容器间通信使用自定义桥接网络——切勿依赖默认桥接网络进行服务发现
常见问题
Ubuntu 上的 Docker 默认使用 `systemd` 还是 `cgroupfs` 作为 cgroup 驱动程序?
自 Docker Engine 20.10 起,基于 `systemd` 的系统(包括所有现代 Ubuntu LTS 版本)上的默认 cgroup 驱动程序为 `systemd`。这与 Kubernetes 要求保持一致,并避免了同时运行两个 cgroup 管理器所导致的不稳定性。您可以通过 `docker info | grep -i cgroup` 进行验证。
`docker compose down` 和 `docker compose stop` 有什么区别?
`docker compose stop` 会停止正在运行的容器,但保留容器及其关联的网络。`docker compose down` 会停止容器,然后删除容器以及 Compose 创建的网络。将 `–volumes` 添加到 `down` 还会删除 Compose 文件中定义的命名卷——在生产环境中使用此标志时请谨慎,因为它会永久删除持久化数据。
为什么 Docker 会绕过 Ubuntu 上的 `ufw` 防火墙规则?
Docker 在 `DOCKER` 和 `DOCKER-USER` 链中插入自己的 `iptables` 规则,这些规则在 `ufw` 的 `INPUT` 链规则之前被评估。这意味着使用 `-p` 发布的端口无论 `ufw` 策略如何,都可以从互联网访问。正确的缓解措施是直接向 `DOCKER-USER` 链添加规则,或者在不需要外部访问时将已发布的端口绑定到特定接口(例如 `-p 127.0.0.1:8080:80`)。
如何限制 Docker 容器可以消耗的 CPU 和内存?
在运行时使用资源约束标志:`docker run –memory="512m" –cpus="1.5" my-image`。在 Compose 中,在 `deploy.resources` 键(Compose V2)或顶级 `mem_limit` 和 `cpus` 键下设置这些内容。如果没有限制,单个失控的容器可能会耗尽宿主机资源,并导致同一宿主机上的所有其他容器崩溃。
我可以在 Docker 容器内运行 Docker(Docker-in-Docker)吗?
可以,但强烈不建议在生产环境中使用。CI 流水线的常见模式是将宿主机 Docker socket(`-v /var/run/docker.sock:/var/run/docker.sock`)挂载到 CI 容器中,这会赋予容器对宿主机 Docker 守护进程的完全控制权——这是一个重大安全风险。更安全的替代方案是使用 BuildKit 的 `–allow security.insecure` 标志,或使用 Kaniko、Buildah 等专用工具,这些工具无需 Docker 守护进程即可构建 OCI 镜像。
