
简介这份 PDF 面向零基础或缺乏容器化经验的开发者与运维人员系统梳理 Docker 从概念到实战的完整入门路径。内容先对比容器与传统虚拟机的差异讲清镜像、容器、仓库三大核心概念再以 Ubuntu 为例演示 Docker Engine 安装、用户组配置等环境搭建步骤。核心操作部分覆盖镜像管理、容器生命周期与日志调试命令并通过 docker-compose.yml 部署 WordPress 的实战案例串联所学知识数据管理章节讲解数据卷与绑定挂载两种持久化方式安全章节则给出容器安全原则与关键配置末尾附官方资源、进阶方向与推荐书籍。资源包为 1 个 PDF 文件约 760KB篇幅精炼、步骤与示例代码配套适合跟随动手练习。目前已有 1057 人学习可作为快速上手 Docker 的实践型参考。1. Docker 新手入门指南从零开始掌握容器化技术很多人第一次装 Docker Desktop卡在 “Virtualization support not detected” 这个报错上折腾一晚上连容器长什么样都没见着。这不是你笨是容器化这件事本身有一层“看不见的基础设施”门槛它依赖内核特性、网络栈、文件系统分层任何一环没对齐Docker 就起不来。这份指南要解决的就是这个问题——让一个从没碰过容器的人在本地把 Docker 跑起来拉下第一个镜像跑起第一个容器并且知道每一步背后发生了什么。适合后端开发、测试、运维新手以及想用 Docker 部署 MySQL、Redis、微服务但一直被环境问题劝退的人。读完你能独立完成 Docker 安装、镜像管理、容器生命周期操作、数据卷挂载和自定义网络配置并且遇到常见报错知道往哪个方向查。2. 容器化技术到底解决了什么问题从进程隔离到镜像分层2.1 容器不是轻量级虚拟机新手最容易把容器当成“小虚拟机”这个误解会导致后面所有操作都走偏。虚拟机的做法是在物理硬件上跑一个 Hypervisor再在 Hypervisor 上装完整的 Guest OS每个虚拟机都有自己的内核。容器不走这条路——它直接复用宿主机内核通过 Linux 的 Namespace 做资源隔离通过 Cgroups 做资源限制。Namespace 让容器里的进程看不到宿主机上其他进程Cgroups 让容器最多只能用指定量的 CPU 和内存。这意味着两件事。第一容器启动速度是毫秒级因为不需要引导操作系统。第二容器里不能跑和宿主机内核不兼容的东西——比如你在 Linux 宿主机上跑不了 Windows 容器除非开虚拟机。Windows 上装 Docker Desktop 之所以要开 WSL2 或 Hyper-V就是因为 Windows 内核没有 Linux 那套 Namespace 和 Cgroups必须套一层 Linux 环境。镜像分层是另一个核心概念。一个 Docker 镜像由多个只读层叠加而成每执行一条 Dockerfile 指令就生成一层。拉取镜像时如果本地已经有某一层Docker 不会重复下载。这就是为什么docker pull第二次拉同一个镜像几乎瞬间完成。容器启动时Docker 在镜像最上面加一个可写层所有修改都写在这一层删容器时这一层也没了。理解这一点后面数据卷挂载的必要性就自然清楚了。2.2 安装 DockerUbuntu 和 Windows 两条路径Ubuntu 上安装 Docker 最稳的方式是用官方仓库不要用apt install docker.io那个版本它往往落后好几个大版本。先卸载旧版本再添加官方 GPG key 和仓库# 卸载可能存在的旧版本 sudo apt remove docker docker-engine docker.io containerd runc # 安装依赖 sudo apt update sudo apt install ca-certificates curl gnupg lsb-release # 添加 Docker 官方 GPG key sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg # 添加仓库 echo deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null # 安装 Docker Engine sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin装完之后执行sudo docker run hello-world看到 “Hello from Docker!” 就说明引擎正常。这里有个高频坑不加sudo会报 “permission denied while trying to connect to the Docker daemon socket”。解决办法是把当前用户加入 docker 组sudo usermod -aG docker $USER newgrp dockernewgrp让组变更在当前会话立即生效不然你得注销重登。注意加入 docker 组等于给了这个用户 root 级别的权限生产环境要谨慎。Windows 上走 Docker Desktop。下载安装包后安装向导会问用 WSL2 还是 Hyper-V。现在默认推荐 WSL2性能更好文件系统互通也更顺。装完后如果启动报 “Virtualization support not detected”去 BIOS 里开 Intel VT-x 或 AMD-V然后在 Windows 功能里确认 “虚拟机平台” 和 “适用于 Linux 的 Windows 子系统” 两项都勾上。如果报 “docker desktop failed to start because virtualization support not detected”基本就是这两个开关没开全。WSL2 还需要在 PowerShell 里执行wsl --update确保内核版本够新。2.3 镜像和容器的基本操作拉取镜像用docker pull查看本地镜像用docker images。运行容器用docker run这个命令参数最多先掌握几个核心的# 拉取 nginx 镜像 docker pull nginx:latest # 后台运行一个 nginx 容器把宿主机 8080 映射到容器 80 docker run -d --name my-nginx -p 8080:80 nginx:latest # 查看运行中的容器 docker ps # 查看所有容器包括已停止的 docker ps -a # 进入容器内部 docker exec -it my-nginx /bin/bash # 停止并删除容器 docker stop my-nginx docker rm my-nginx-d是后台运行--name给容器起名字方便后续引用-p 8080:80是端口映射格式是宿主机端口:容器端口。docker exec -it里的-i保持标准输入打开-t分配伪终端两个一起用才能交互。如果容器里没有 bash可以换成/bin/sh。这里有个新手常翻车的地方docker run和docker start的区别。docker run是从镜像创建一个新容器并启动docker start是启动一个已经存在的、之前停止的容器。你用docker run跑了一个 nginx停了之后想再跑应该用docker start my-nginx而不是再docker run一次——后者会报名字冲突。3. 用 Docker 部署 MySQL 和 Redis数据持久化与网络配置3.1 MySQL 8.0 容器化部署的完整参数直接docker run mysql能跑起来但数据存在容器可写层里容器一删数据全没。正确做法是挂数据卷。下面这条命令是生产环境可用的最小配置docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDYourStrongPass123 \ -e MYSQL_DATABASEappdb \ -e MYSQL_USERappuser \ -e MYSQL_PASSWORDAppUserPass456 \ -v mysql-data:/var/lib/mysql \ -v /etc/localtime:/etc/localtime:ro \ --restart unless-stopped \ mysql:8.0 \ --character-set-serverutf8mb4 \ --collation-serverutf8mb4_unicode_ci逐项说明。-e MYSQL_ROOT_PASSWORD是必须的不设这个变量容器会直接退出。MYSQL_DATABASE会在初始化时自动建库MYSQL_USER和MYSQL_PASSWORD会创建一个普通用户并授权访问那个库。-v mysql-data:/var/lib/mysql用的是命名卷Docker 会在/var/lib/mysql之外管理这个卷比绑定宿主机目录更干净。--restart unless-stopped让容器在宿主机重启后自动拉起除非你手动 stop 过它。最后两行是传给 mysqld 的参数把字符集设成 utf8mb4避免中文乱码。如果你要挂宿主机目录而不是命名卷写成-v /data/mysql:/var/lib/mysql但要确保宿主机目录权限对——MySQL 容器里 mysql 用户的 UID 通常是 999宿主机目录如果归 root 且权限 755容器会报 “Permission denied” 起不来。解决办法是chown -R 999:999 /data/mysql。验证 MySQL 是否正常docker exec -it mysql8 mysql -uroot -pYourStrongPass123 -e SHOW DATABASES;能看到appdb就说明初始化成功。如果报 “Cant connect to local MySQL server through socket”等几秒再试MySQL 首次初始化需要时间。3.2 Redis 主从复制的容器编排Redis 主从用 Docker 做很直观。先起主节点docker run -d --name redis-master -p 6379:6379 redis:7 --requirepass MasterPass123再起从节点指向主节点docker run -d --name redis-slave -p 6380:6379 redis:7 \ --requirepass SlavePass123 \ --masterauth MasterPass123 \ --replicaof redis-master 6379这里有个关键点从节点要能解析redis-master这个主机名。默认的 bridge 网络里容器之间只能用 IP 互访不能用名字。所以需要先创建一个自定义网络把两个容器都接进去docker network create redis-net docker run -d --name redis-master --network redis-net -p 6379:6379 redis:7 --requirepass MasterPass123 docker run -d --name redis-slave --network redis-net -p 6380:6379 redis:7 \ --requirepass SlavePass123 \ --masterauth MasterPass123 \ --replicaof redis-master 6379自定义网络自带 DNS 解析容器名就是主机名。验证主从状态docker exec -it redis-master redis-cli -a MasterPass123 INFO replication输出里connected_slaves:1就说明从节点连上了。如果master_link_status是down检查--masterauth的密码和主节点--requirepass是否一致。3.3 Docker 网络不通的排查思路“docker 网络不通” 是高频问题表现是容器之间 ping 不通或者容器访问不了外网。排查按这个顺序走第一步确认容器在哪个网络。docker inspect 容器名 | grep -A 20 NetworkSettings看它接的是 bridge 还是自定义网络。默认 bridge 网络没有 DNS 解析容器名不能用。第二步从容器内部测连通性。docker exec -it 容器名 ping 目标。如果 ping 不通但 IP 能通是 DNS 问题如果 IP 也不通是网络隔离或防火墙问题。第三步检查宿主机 iptables。Docker 会自动加 NAT 规则但如果宿主机上跑了 firewalld 或 ufw可能把 Docker 的规则覆盖掉。Ubuntu 上常见的是 ufw 默认 forward 策略是 DROP导致容器出不了网。解决方法是编辑/etc/default/ufw把DEFAULT_FORWARD_POLICY改成ACCEPT然后sudo ufw reload。第四步如果容器访问外网慢或超时检查 DNS。Docker 默认用宿主机的/etc/resolv.conf如果里面是内网 DNS 且不稳定可以在docker run时加--dns 8.8.8.8指定。4. Docker 避坑指南从权限错误到镜像拉取失败4.1 权限错误permission denied 的三种场景现象一执行任何 docker 命令都报 “Got permission denied while trying to connect to the Docker daemon socket”。原因是当前用户不在 docker 组。解决sudo usermod -aG docker $USER然后重新登录。注意newgrp docker只对当前终端生效新开终端还是不行。现象二容器挂载宿主机目录后启动失败日志报 “Permission denied”。原因是容器内进程的 UID 和宿主机目录属主不匹配。解决要么chown宿主机目录给对应 UID要么在docker run时加--user $(id -u):$(id -g)让容器以当前用户身份跑。后者适合开发环境生产环境还是老老实实对齐 UID。现象三docker exec进容器后操作文件报权限不足。这是因为docker exec默认以容器内 root 用户进入但如果容器 Dockerfile 里用了USER指令切了用户exec 进去就是那个用户。加-u root可以强制以 root 进入docker exec -u root -it 容器 /bin/bash。4.2 镜像拉取慢和拉取失败“docker 镜像下载慢” 在国内是常态。Docker Hub 的 CDN 节点在境外拉一个几百 MB 的镜像可能要十几分钟甚至超时。解决办法是配置镜像加速器。编辑/etc/docker/daemon.json{ registry-mirrors: [ https://mirror.ccs.tencentyun.com, https://docker.mirrors.ustc.edu.cn ] }改完执行sudo systemctl daemon-reload sudo systemctl restart docker。注意加速器只对 Docker Hub 的官方镜像生效如果你拉的是ghcr.io或quay.io的镜像加速器不生效得另想办法。拉取失败还有一种情况是镜像 tag 不存在。比如docker pull mysql:8.0.99如果这个版本没发布过会报 “manifest unknown”。去 Docker Hub 页面确认 tag 是否存在或者用docker search查。4.3 容器启动失败exit code 排查法容器起来又立刻退出docker ps看不到docker ps -a能看到状态是Exited (1)或Exited (0)。排查第一步是看日志docker logs 容器名。日志为空的话用docker inspect 容器名 | grep -A 5 State看 ExitCode 和 Error。ExitCode 0 通常意味着容器主进程正常结束了。比如你docker run ubuntu不带任何命令ubuntu 镜像默认执行/bin/bash但没有-it分配终端bash 发现没有输入就退出了。解决是加-it或者指定一个持续运行的命令。ExitCode 1 是通用错误看日志。ExitCode 137 是 OOM 被杀说明容器内存超了。ExitCode 139 是段错误。ExitCode 143 是收到 SIGTERM通常是docker stop正常停止。4.4 数据卷挂载的四个边界坑第一个坑挂载空目录覆盖容器内容。比如-v /host/empty:/etc/nginx宿主机目录是空的挂进去之后容器里/etc/nginx原有的配置文件全被覆盖nginx 起不来。解决要么先往宿主机目录放一份配置要么用命名卷让 Docker 自己管理。第二个坑Windows 和 Linux 换行符问题。在 Windows 上编辑的配置文件挂进 Linux 容器CRLF 换行会导致某些程序解析失败。解决编辑器里设成 LF或者用dos2unix转。第三个坑SELinux 导致的挂载拒绝。CentOS 上 SELinux 开启时挂载宿主机目录需要加:z或:Z标签-v /data:/app:z。不加会报 “Permission denied” 但权限看起来是对的。第四个坑命名卷删除后数据不可恢复。docker volume rm会直接删掉卷里所有数据没有回收站。生产环境删卷前先docker run --rm -v 卷名:/data -v $(pwd):/backup alpine tar czf /backup/volume-backup.tar.gz -C /data .备份。5. 进阶技巧用 Docker Compose 编排多容器应用5.1 从 docker run 到 compose 的迁移当你需要同时跑 MySQL、Redis、后端 API 和 Nginx 时四条docker run命令管理起来很痛苦——网络要手动建启动顺序要手动控制环境变量散落在 shell 历史里。Docker Compose 用一个 YAML 文件描述所有服务一条docker compose up -d全部拉起。下面是一个典型的微服务项目 compose 文件services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: RootPass123 MYSQL_DATABASE: appdb volumes: - mysql-data:/var/lib/mysql networks: - backend healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 10s timeout: 5s retries: 5 redis: image: redis:7 networks: - backend api: build: ./api environment: DB_HOST: mysql REDIS_HOST: redis depends_on: mysql: condition: service_healthy redis: condition: service_started networks: - backend - frontend nginx: image: nginx:latest ports: - 80:80 volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro depends_on: - api networks: - frontend volumes: mysql-data: networks: backend: frontend:关键点在depends_on配合healthcheck。单纯写depends_on: mysql只保证 mysql 容器启动了不保证 MySQL 服务已经能接受连接。加上condition: service_healthy后compose 会等 healthcheck 通过再启动 api。healthcheck 里的mysqladmin ping是 MySQL 自带的探活命令。网络划分上mysql 和 redis 只接 backend 网络nginx 只接 frontendapi 同时接两个。这样 nginx 访问不到 mysql减少了攻击面。5.2 用 compose 做环境隔离和滚动更新开发、测试、生产三套环境用同一个 compose 文件通过-f叠加不同 override 文件实现# 开发环境 docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d # 生产环境 docker compose -f docker-compose.yml -f docker-compose.prod.yml up -ddocker-compose.dev.yml里可以挂载源码目录做热更新docker-compose.prod.yml里设资源限制和重启策略。这样基础配置只维护一份。滚动更新用docker compose up -d --no-deps --build api只重建 api 服务不动数据库。--no-deps防止 compose 顺带重启依赖服务。更新完用docker compose ps确认新容器状态是 healthy。5.3 镜像构建的层缓存优化Dockerfile 写得好不好直接影响构建速度。核心原则是把变化频率低的指令放前面变化频率高的放后面。比如FROM python:3.11-slim WORKDIR /app # 先复制依赖文件单独安装依赖 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 再复制源码源码改动不会触发依赖重装 COPY . . CMD [python, main.py]如果反过来先COPY . .再pip install每次改一行代码都会重新装一遍依赖构建时间从几秒变成几分钟。--no-cache-dir让 pip 不保留下载缓存减小镜像体积。还有一个技巧是合并 RUN 指令。每条 RUN 生成一层层数越多镜像越大。把多个apt install合并成一条并在同一条里清理 apt 缓存RUN apt update apt install -y curl git rm -rf /var/lib/apt/lists/*rm -rf /var/lib/apt/lists/*必须在同一条 RUN 里执行分开写的话删除操作只是在下一层标记删除上一层的数据还在镜像里。我自己的习惯是每次写完 Dockerfile 先docker build一次然后改一行源码再 build 一次看第二次构建有没有命中缓存。如果所有层都重新跑了说明 COPY 的位置不对。这个检查花不了几秒钟但能省下后面无数次等待。希望帮到你。本文还有配套的精品资源点击获取