简介:一份面向 Docker 零基础开发者和运维人员的 PDF 入门指南,系统讲解容器化技术与传统虚拟机在启动速度、资源占用、隔离性等方面的差异,并围绕 Ubuntu 环境覆盖卸载旧版本、添加软件源、安装 Docker Engine 与 Compose 插件、配置用户组权限,以及镜像拉取与构建、容器启停删除、日志查看和进入容器调试等核心命令。后续通过部署 WordPress 的实战案例,展示 docker-compose.yml 的编写与后台启动方式,并介绍数据卷和绑定挂载两种持久化方案,以及容器安全最佳实践与官方进阶学习路径。整个资源包共 1 个文件,为 PDF 格式,大小约 760KB,内容以可操作的命令和配置片段为主,包含 Dockerfile 构建、Compose 编排、容器安全等模块,方便读者边读边练。截至目前已有 1057 人学习下载,对想快速搭建 Docker 知识体系的新手而言,是一份紧凑实用的入门参考资料。
1. Docker 到底是什么:先甩掉虚拟机那套思维
刚接触容器化技术的人,十有八九先把 Docker 当成一台更轻的虚拟机。直到在一台 Ubuntu 服务器上手工部署 WordPress 时,被 PHP 版本、MySQL 权限、Nginx 配置反复折磨,才明白 Docker 解决的是“环境交付”的问题——把依赖打包成镜像,到哪都能秒级跑起来。这份入门指南从零讲清楚 Docker 是什么、怎么装、怎么用 compose 部署真实服务,以及数据持久化和安全怎么做。它面向新手开发者和运维,不需要你懂内核或网络底层,只要愿意跟着命令敲一遍,遇到坑知道去哪查。
2. 在 Ubuntu 上装好 Docker Engine:仓库、密钥与用户组三个细节
Ubuntu 上装 Docker,最省事的姿势是sudo apt install docker.io,但我一向不推荐这么干。仓库里的docker.io包版本偏旧,而且不带docker-compose-plugin和docker-buildx-plugin,后面写 compose 文件、构建多平台镜像时很容易卡住。指南里走的是官方软件源安装流程,先把 apt 源切到 Docker 官方仓库,再安装验证、配置用户组。整套步骤拆开看,有三个细节值得掰开讲清楚。
2.1 为什么先配官方仓库,而不是 apt 直接装
官方安装文档的第一步不是apt-get install docker-ce,而是先卸载旧版本、装依赖、导入 GPG 密钥、写软件源。这一串动作看着啰嗦,实际是给 apt 建立一条可信的下载链路,保证装到的包来自 Docker 官方,而不是第三方维护的旧包。
# 卸载旧版本(全新系统一般没有输出,可忽略错误) sudo apt-get remove docker docker-engine docker.io containerd runc # 安装依赖工具:ca-certificates 提供 CA 证书,curl 用来下载,gnupg 处理 GPG 密钥 sudo apt-get update sudo apt-get install ca-certificates curl gnupg # 创建密钥目录,权限 0755 sudo install -m 0755 -d /etc/apt/keyrings # 下载 Docker 官方 GPG 密钥并转为二进制格式 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg # 写入 apt 软件源配置文件 echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null这套命令里有两个容易忽略的参数:curl -fsSL中-f是失败时不输出 HTML 错误页,-S显示错误信息,-L跟随重定向,少了-L在部分网络环境会拿到一个空文件,后面 GPG 校验直接报错。gpg --dearmor是把文本格式的密钥转成二进制,apt 要求signed-by指向二进制密钥文件,直接拿.asc文本用会报“key is not available”之类的错。
写软件源时用了两个命令替换:$(dpkg --print-architecture)自动输出amd64或arm64,$(. /etc/os-release && echo "$VERSION_CODENAME")自动匹配当前系统的版本代号(如jammy、noble)。这样一份配置在 x86 和 ARM 的 Ubuntu 上都能用,不用手工改架构和版本名。
2.2 安装与验证:hello-world 跑通才算装完
软件源配好后,安装过程本身反而没什么玄学,一条apt-get install带上五个包名。需要理解的是这五个包各管什么,否则以后排查问题都不知道故障出在哪一层。
sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin # 验证客户端与服务端都能正常工作 sudo docker version sudo docker run hello-worlddocker-ce是守护进程(daemon),负责管理容器生命周期;docker-ce-cli是你敲的docker命令客户端;containerd.io是底层容器运行时,负责真正启动进程;docker-buildx-plugin提供多平台构建能力;docker-compose-plugin提供docker compose子命令。很多老教程只装了前三个包,后面用到docker compose时才发现没有这个命令,又回来补装。
验证时先跑docker version,看输出里有没有Server段。如果只有Client段而 Server 报错,说明守护进程没起来,先查systemctl status docker,而不是重装。docker run hello-world会先尝试从仓库拉取一个极小的测试镜像,再创建容器执行,整个过程跑通,说明镜像拉取、容器创建、日志输出整条链路都没问题。如果卡在 Pulling 阶段,多半是网络到 Docker Hub 不通,先查 DNS 和代理配置,或者给守护进程配置 registry mirror,不要一上来就怀疑安装步骤错了。
2.3 免 sudo 配置:docker 组权限的原理与边界
每次敲命令都带sudo,用不了多久就烦了。指南里给了一段标准的用户组配置,把当前用户加进docker组,目的是让用户可以直接访问 Docker 守护进程的 socket 文件,这个 socket 默认归属于root:docker组。
sudo groupadd docker # 已存在会提示,可忽略 sudo usermod -aG docker $USER # 把当前用户追加到 docker 组 newgrp docker # 刷新当前会话的用户组 docker ps # 不再需要 sudousermod -aG三个字母各有用意:-a是追加(append),不加的话会把用户从其他附加组里踢出去;-G指定要加入的组;$USER是当前登录用户的环境变量,实际执行时会展开成用户名。newgrp docker的作用是让当前终端会话立即拿到新组权限,不用退出重登。注意newgrp只对当前 shell 生效,如果你是通过 SSH 登录的,已经开着的旧会话不会刷新,需要退出重新连接。
提示:加入 docker 组等同于把 root 权限交给了该用户,因为 Docker 守护进程允许组内成员挂载宿主机目录、以任意用户执行容器命令。生产服务器建议用专门的运维账号,而不是给普通开发账号直接加组。实验机无所谓,怎么方便怎么来。
3. 核心命令三板斧:镜像、容器与日志排查
装好 Docker 之后,日常操作集中在三组命令:镜像增删查、容器生命周期、调试与日志。新手最容易犯的错是用管虚拟机的思路去管容器,以为docker run就是“开机”,docker rm就是“关机”。实际上镜像和容器是两层东西,容器删了,镜像还在;容器里的数据改动,随着容器删除一起消失。这一章把三组命令逐个拆解,顺带说明哪些参数是高频踩坑点。
3.1 镜像管理:pull 与 build 的常见误用
docker pull nginx:latest # 从仓库拉取镜像,latest 是默认标签 docker images # 列出本地镜像 docker rmi nginx:latest # 删除镜像 docker build -t myapp:v1 . # 用当前目录的 Dockerfile 构建镜像docker pull nginx:latest中,latest是标签不是版本保证。很多仓库的维护者会把最新稳定版打上latest,但也有镜像仓库长期不维护latest,拉下来是个老版本。要锁定版本,最好用明确版本号,比如nginx:1.27-alpine。docker images输出的IMAGE ID是镜像的短 ID,同一个镜像不同 tag 可能共享同一个 ID,删除时不会重复占空间。
docker rmi删除的是镜像,但前提是没有容器引用它。一个常见的翻车现场是:docker rmi报image is being used by container,新手于是去docker rmi -f强制删除,结果容器还在跑,文件系统引用悬空,状态变得很诡异。正确的顺序是先docker rm删掉引用它的容器,再删镜像。docker build -t myapp:v1 .末尾的.是构建上下文目录,不是“构建当前文件”。Docker 会把整个目录打成 tar 发给守护进程,目录里有node_modules或.git时构建会非常慢,正确做法是写.dockerignore排除掉这些目录。
3.2 容器生命周期:run 的参数决定容器的行为
docker run -d -p 80:80 --name my-nginx nginx docker ps # 只看运行中的容器 docker ps -a # 看所有容器,包括已停止的 docker stop my-nginx # 停止容器,发送 SIGTERM docker start my-nginx # 启动已停止的容器,复用原有文件系统 docker rm my-nginx # 删除容器docker run是最需要逐字拆解的命令。-d表示后台运行,不加的话容器在前台跑,Ctrl+C 会把容器停掉;-p 80:80是端口映射,宿主机 80 端口转发到容器 80 端口,如果宿主机 80 被其他服务占用,可以把左边的 80 改成 8080,容器内的端口不用动;--name my-nginx是给容器命名,不命名的话 Docker 会随机分配一个名字,日志和 inspect 都不好辨认。
docker ps -a里的-a是“all”的意思,默认ps只看运行中的容器,停止的容器不会出现。新手经常困惑“我明明跑过容器,怎么 ps 看不到”,其实就是没加-a。stop和start都作用于已有的容器,start会复用原容器的可写层,之前容器里改过的文件都还在。rm则是把容器整个删掉,可写层一并销毁。这就是容器和虚拟机的核心差异:虚拟机删了,磁盘文件还在;容器删了,没有挂载卷的数据就真的没了。这个坑在后面的持久化章节还会详细讲。
提示:
docker run如果镜像已在本地,不会重新拉取,只有本地不存在时才走 pull。这可以帮你离线跑任务,但也可能导致本地镜像长时间不更新,记得定期docker pull刷新。
3.3 调试与日志:exec、logs、inspect 怎么配合用
docker exec -it my-nginx bash # 进入容器终端,容器里没有 bash 时改用 sh docker logs -f --tail 100 my-nginx # 查看最近 100 行日志并持续跟随 docker inspect my-nginx # 输出容器完整 JSON 信息 docker inspect -f '{{.State.Status}}' my-nginx # 只提取某个字段docker exec -it里-i表示保持标准输入打开,-t分配一个伪终端。没有这两个参数,进入容器后敲命令没反应、看不到命令提示符。容器内部通常是最小化系统,Debian 系镜像大多自带bash,Alpine 镜像只有sh,进不去时先用sh试。docker logs看的是容器主进程写向 stdout/stderr 的内容,不是容器里的日志文件。某些应用把日志写进/var/log文件,docker logs什么都看不到,需要 exec 进去看文件。
docker inspect输出几百行 JSON,新手容易看懵。实际排查时只需要提取两三个字段:运行状态用docker inspect -f '{{.State.Status}}',挂载点用{{range .Mounts}}{{.Name}} -> {{.Destination}}{{end}},网络 IP 用{{.NetworkSettings.IPAddress}}。容器启动失败、状态反复重启时,先docker logs看应用报错,再看docker inspect确认环境变量和挂载是否生效,这两步能解决八成启动问题。
4. 部署 WordPress:docker-compose.yml 逐行拆解与启动验证
单容器命令玩熟后,真实项目几乎不会只有一个容器。WordPress 至少需要 PHP 应用和 MySQL 数据库两个服务,手工docker run两个容器再配网络也能跑,但每次重建都要重新输入一长串参数,环境变量还容易漏。指南里的实战案例直接上了docker-compose.yml,用声明式配置把两个服务固定下来,以后docker compose up -d一行拉起全部。
4.1 Compose 文件结构:服务、环境变量与依赖顺序
version: '3' services: db: image: mysql:8.0 volumes: - db_data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: wordpress MYSQL_USER: wpuser MYSQL_PASSWORD: wppass wordpress: image: wordpress:latest ports: - "8000:80" environment: WORDPRESS_DB_HOST: db WORDPRESS_DB_USER: wpuser WORDPRESS_DB_PASSWORD: wppass depends_on: - db volumes: db_data:version: '3'是 compose 文件的 schema 版本。新版 Docker Compose 插件已经可以不写这一行,但官方文档兼容旧版,写上不影响使用。services下面每个键都是一个容器服务,db和wordpress会被 Compose 放进同一个自定义网络,服务之间用服务名互访,所以 wordpress 的WORDPRESS_DB_HOST直接填db,不需要写 IP 地址。
MySQL 容器的environment只在数据目录为空时生效:MYSQL_ROOT_PASSWORD设置 root 密码,MYSQL_DATABASE创建默认数据库,MYSQL_USER和MYSQL_PASSWORD创建一个专用账号。这个账号是给 WordPress 用的,权限只覆盖wordpress库,比让应用直连 root 安全。有个边界要记住:如果db_data卷里已经有数据,这些环境变量不会再“重置”密码,改密码必须进 MySQL 里执行 SQL。
port: "8000:80"里的引号不是装饰,是为了防止 YAML 把8000:80解析成数字。某些旧版本解析器不带引号会报错或变成奇怪的值。depends_on只保证 db 容器先启动,不保证 MySQL 已经初始化完成。这是新手最容易踩的坑,第 5 章会专门给健康检查的配置。
| 环境变量 | 作用 | 备注 |
|---|---|---|
| MYSQL_ROOT_PASSWORD | MySQL root 密码 | 仅首次初始化生效 |
| MYSQL_DATABASE | 自动创建的数据库名 | 对应 WordPress 库 |
| MYSQL_USER / MYSQL_PASSWORD | 应用专用账号 | WordPress 连接用 |
| WORDPRESS_DB_HOST | 数据库主机名 | 填服务名 db |
| WORDPRESS_DB_USER / WORDPRESS_DB_PASSWORD | 数据库账号 | 必须与 MYSQL_USER 一致 |
4.2 启动、访问与安装验证
docker compose up -d # 后台启动全部服务 docker compose ps # 查看服务状态 docker compose logs wordpress # 查看应用日志 curl -I http://localhost:8000 # 验证 HTTP 响应执行docker compose up -d前,先确认当前目录就是docker-compose.yml所在目录,Compose 默认找当前目录的配置文件,不在目录里会报找不到文件。-d是后台运行,不加的话终端会挂着,Ctrl+C 会连带停掉服务。启动后docker compose ps查看两个容器的状态,正常应该都是Up。如果 wordpress 容器一直显示Restarting,说明应用启动失败,立刻用docker compose logs wordpress看日志。
日志里最常见的错误是Error establishing a database connection,原因一般不是账号密码错,而是 MySQL 还没初始化完,wordpress 抢先尝试连接。curl -I http://localhost:8000返回302是正常的,WordPress 会把请求重定向到安装向导页。返回200说明首页可访问,000则是连接被拒。浏览器访问时,如果这台机器是云服务器,记得安全组和防火墙放行 8000 端口,本地虚拟机则直接访问即可。
4.3 Volume 初体验:db_data 是怎么把数据留下来的
在 compose 文件底部声明volumes: db_data:,等于让 Docker 创建一个命名卷。MySQL 容器把数据写入/var/lib/mysql,这个路径被卷挂载后,实际写入位置是宿主机上 Docker 管理的卷目录,而不是容器可写层。所以容器怎么删、怎么重建,MySQL 数据都存在卷里。
docker compose down并不会删除命名卷,这是安全网。很多新手把down和rm -rf划等号,担心数据没了,放心,down只删容器和默认网络。只有docker compose down -v才会连卷一起删,操作前务必确认卷里的数据是否有备份。等第 6 章我会演示怎么验证数据真的留在卷里,以及删容器后重新拉起数据是否还在。
5. 新手最容易踩的五个坑:现象、原因与处理
这一章全是血泪经验。整理成“现象 → 原因 → 解决”三段式,每个坑都可以直接对照自己的环境排查。如果你刚跟着第 2 章装完环境,可以先跳过,等遇到问题再回来看;如果你已经部署了 WordPress,逐条核对一遍没坏处。
5.1 每次都要 sudo:permission denied 反复出现
- 现象:
docker ps报Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock。 - 原因:当前用户不在
docker组,而 Docker 守护进程的 socket 文件权限是root:docker,普通用户无权访问。 - 解决:执行
sudo usermod -aG docker $USER后退出 SSH 重新登录。注意newgrp docker只刷新当前终端会话,已经打开的旧终端不会生效;桌面系统注销重登一次。验证用id命令,输出里出现docker组就说明加成功了。
5.2 端口占用:bind: address already in use
- 现象:
docker run -p 80:80时终端报bind: address already in use,容器创建失败。 - 原因:宿主机 80 端口已被其他进程占用,最常见的是机器上原本装过 Nginx 或 Apache,也可能另一个容器已经占了同一端口。
- 解决:用
sudo netstat -tlnp | grep :80或sudo ss -tlnp | grep :80找到占用进程的 PID,停掉它,或者改端口映射。改端口时只改宿主机一侧,比如-p 8080:80,容器内应用的监听端口不用变。docker ps -a也要查一遍,可能有旧容器没删干净占着端口。
5.3 容器删了数据全没:rm 之后 MySQL 凉了
- 现象:WordPress 跑了两周,
docker rm -f删掉 db 容器后重新docker compose up -d,站点恢复但文章、主题设置全部丢失。 - 原因:MySQL 数据写在容器可写层,删除容器时可写层随容器一起销毁。没有挂载卷的情况下,Docker 不提供任何“后悔药”。
- 解决:在 compose 文件里给 db 服务挂命名卷
db_data:/var/lib/mysql。卷独立于容器生命周期,容器删了卷还在。验证方法:docker volume ls看db_data是否存在;docker volume inspect db_data查看挂载详情。习惯上我会在首次部署时就挂卷,绝不裸跑数据库容器。
5.4 WordPress 连不上数据库:depends_on 不等于数据库就绪
- 现象:
docker compose up -d后docker compose ps显示 wordpress 容器不断重启,日志里是Error establishing a database connection。 - 原因:
depends_on只控制容器启动顺序,MySQL 容器进程起来了,但 MySQL 初始化还没完成、端口未监听,wordpress 就抢先连接了。 - 解决:给 db 服务加
healthcheck,让 wordpress 等 MySQL 健康后再启动:
services: db: image: mysql:8.0 healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 5s timeout: 3s retries: 20 wordpress: depends_on: db: condition: service_healthyhealthcheck里interval: 5s表示每 5 秒检查一次,timeout: 3s是单次检查超时,retries: 20是连续失败 20 次判定为 unhealthy。mysqladmin ping返回成功说明 MySQL 已接受连接。配置后docker compose up -d会先等 db 进入 healthy 状态,再启动 wordpress,第一次部署时会多等十几秒,这是正常的。
5.5 日志磁盘爆炸与时区差 8 小时
- 现象:跑了一段时间,
df -h发现根分区快满了,du -sh /var/lib/docker占用几十 GB;进容器执行date,时间和宿主机差 8 小时。 - 原因:Docker 默认的
json-file日志驱动不做轮转,应用每打印一行日志,文件就涨一点,长期运行必然膨胀;容器镜像默认 UTC 时区,宿主机是东八区。 - 解决:在 compose 服务里限制日志大小,并设置时区环境变量:
services: wordpress: environment: TZ: Asia/Shanghai logging: driver: json-file options: max-size: "10m" max-file: "3"max-size: "10m"表示单个日志文件达到 10MB 就轮转,max-file: "3"保留最近 3 个文件,超过的自动清理。设置TZ=Asia/Shanghai对大多数镜像有效,个别应用不读这个变量,需要在 Dockerfile 里装tzdata并设置/etc/localtime软链接。这个坑不解决,日志文件会悄无声息地把磁盘填满,到时候 Docker 容器一起崩溃。
6. 从单容器走向编排:验证数据持久化与安全基线的两个动作
部署 WordPress 不是终点,验证它是否真的“持久、安全”才是进阶的开始。我的习惯是每部署一套 compose 服务,先做两个验证动作:一是确认卷挂载生效且数据能扛住容器删除,二是跑一遍安全基线检查。这两件事做完,服务才敢交给别人用。
6.1 验证 Volume 持久化:不删容器不算数
先查 db 容器的挂载信息,确认数据写进的是卷而不是容器可写层:
docker compose ps # 查真实容器名,如 wordpress-db-1 docker inspect wordpress-db-1 -f '{{range .Mounts}}{{.Name}} -> {{.Destination}}{{end}}' docker volume ls # 确认 db_data 卷存在输出里db_data -> /var/lib/mysql说明卷挂载成功。现在做一次模拟事故:docker compose down删掉所有容器,再docker compose up -d重新拉起,登录 WordPress 后台,文章和配置应该原样还在。如果把down换成down -v,卷会被一并删除,数据就真没了。生产环境这个命令要格外小心。
6.2 安全基线:非 root 运行与最小镜像
指南第 6 章的安全原则总结起来就三句话:镜像越小越好、容器不要用 root 跑、镜像要定期扫描漏洞。对应的 Dockerfile 基线长这样:
FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --only=production USER node CMD ["node", "server.js"]FROM node:18-alpine选 Alpine 版本,镜像体积比完整版小一个数量级,攻击面也更小。RUN npm ci --only=production按锁文件安装依赖、不装开发依赖,保证构建可复现。USER node让应用以非特权用户运行,容器即使被攻破,进程权限也有限。最后用docker scan扫描镜像已知漏洞,再决定是否升级依赖版本。
我第一次跑 WordPress 案例时也踩过“删容器丢数据”的坑,从那以后每次 compose 起来,第一件事就是用docker inspect拉一遍挂载点和健康状态,确认没问题才继续往里写数据。这套验证习惯帮我少走了很多弯路,希望帮到你。
本文还有配套的精品资源,点击获取