简介:这份 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 安装 Docker:Ubuntu 和 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_PASSWORD=YourStrongPass123 \ -e MYSQL_DATABASE=appdb \ -e MYSQL_USER=appuser \ -e MYSQL_PASSWORD=AppUserPass456 \ -v mysql-data:/var/lib/mysql \ -v /etc/localtime:/etc/localtime:ro \ --restart unless-stopped \ mysql:8.0 \ --character-set-server=utf8mb4 \ --collation-server=utf8mb4_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就说明初始化成功。如果报 “Can't 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 只接 frontend,api 同时接两个。这样 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 的位置不对。这个检查花不了几秒钟,但能省下后面无数次等待。希望帮到你。
本文还有配套的精品资源,点击获取