简介一份面向 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 ar /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表示后台运行不加的话容器在前台跑CtrlC 会把容器停掉-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 系镜像大多自带bashAlpine 镜像只有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. 部署 WordPressdocker-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_PASSWORDMySQL root 密码仅首次初始化生效MYSQL_DATABASE自动创建的数据库名对应 WordPress 库MYSQL_USER / MYSQL_PASSWORD应用专用账号WordPress 连接用WORDPRESS_DB_HOST数据库主机名填服务名 dbWORDPRESS_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是后台运行不加的话终端会挂着CtrlC 会连带停掉服务。启动后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 每次都要 sudopermission 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: 3max-size: 10m表示单个日志文件达到 10MB 就轮转max-file: 3保留最近 3 个文件超过的自动清理。设置TZAsia/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 --onlyproduction USER node CMD [node, server.js]FROM node:18-alpine选 Alpine 版本镜像体积比完整版小一个数量级攻击面也更小。RUN npm ci --onlyproduction按锁文件安装依赖、不装开发依赖保证构建可复现。USER node让应用以非特权用户运行容器即使被攻破进程权限也有限。最后用docker scan扫描镜像已知漏洞再决定是否升级依赖版本。我第一次跑 WordPress 案例时也踩过“删容器丢数据”的坑从那以后每次 compose 起来第一件事就是用docker inspect拉一遍挂载点和健康状态确认没问题才继续往里写数据。这套验证习惯帮我少走了很多弯路希望帮到你。本文还有配套的精品资源点击获取