说实话,我第一次接触 Docker 是被“逼”的。当时领导丢给我一个老项目,说另一台服务器环境崩了,让我一天内把服务重新跑起来。我对着安装文档装了整整半天 Python、MySQL、Redis、Nginx,各种版本冲突,最后连启动脚本都是乱的。同事看我一脸生无可恋,随手甩了三行命令,十分钟后服务就起来了。那三行命令里全是 docker。那一刻我就意识到,容器化不是“高 P 才需要学的东西”,而是每个开发、运维、甚至测试迟早都要补的必修课。
这篇内容就是照着“零基础”来写的。不堆术语,不扯底层原理,我会把镜像、容器、数据卷这些概念用最土的大白话说清楚,再带着你完成 Windows / Linux 下的安装,跑通 Nginx、MySQL 8.0、Redis 主从,最后再讲 Compose 编排和常见排错。你不需要任何 Docker 基础,只需要一台能上网的电脑和半小时耐心。
1. Docker 到底是什么:用一次“搬家”理解镜像、容器和仓库
1.1 镜像、容器、仓库的通俗理解
很多人一上来就被“镜像”“容器”“仓库”这仨词吓住了,其实它们之间的关系,拿装修房子来类比特别合适。
镜像(Image)就是一张“装修图纸 + 全套建材清单”。它把操作系统的一部分、运行时、依赖库、配置文件全部打成一个只读的模板,谁拿到这个模板,都能盖出完全一样的房子。容器(Container)就是按照这张图纸实际装修出来的房间,你可以住进去、改家具、写文件,但房间本身是从图纸复制出来的。仓库(Registry)就像是房产平台或者图纸库,你可以在上面下载别人上传的“全套装修方案”,也可以把自己做的方案传上去分享。
还有一个关键概念叫 Dockerfile。某种意义上,它就是“图纸的生产过程记录”。假设你需要在某个基础镜像上装一堆依赖、改一堆配置,这些操作写成 Dockerfile 后,每次构建都会按同样的流程重新执行一遍,结果就是一份可重复的镜像。这也是 Docker 能保证“换台机器结果一样”的根本原因。
从技术角度说,镜像是由一层一层的只读文件系统叠加起来的,容器启动时会在这些只读层上挂一层可写层。你改容器里的文件,改的只是可写层,底层镜像没动。这也是为什么容器被删了以后,你改过的东西也一起没了——这一点后面讲数据卷时会重点展开。
1.2 三个让你离不开 Docker 的真实场景
第一个场景是“环境一致性”。“在我电脑上是好的”大概是开发圈流传最广的梗。我之前有一个项目,本地跑得好好的,部署到服务器上就报缺库、缺模块,折腾一晚上发现是服务器上某个系统库版本太新,把兼容性搞坏了。用 Docker 之后,开发、测试、生产都是同一个镜像,连基础系统都锁死在同一个版本里,这类“灵异事件”直接消失。
第二个场景是“快速交付与复现”。新同事入职第一天,与其让他照着几十页文档手动装环境,不如直接给他一个 compose 文件。一条命令拉起来,整个项目的依赖环境全齐。换服务器也一样,docker save 导出镜像,拷过去 docker load 导入,十分钟完成“搬家”。我自己后来给公司的项目做迁移,基本不再碰“手动装环境”这条路。
第三个场景是“资源隔离和多版本共存”。同一个服务器上同时跑 MySQL 5.7 和 MySQL 8.0、Redis 6 和 Redis 7,在传统方式下是件头疼事,端口、目录、配置全要小心处理。但用容器就好办了,每个容器有自己独立的文件系统、网络命名空间和配置环境,只要你把端口映射错开,它们就能井水不犯河水地共存。
1.3 先明确:零基础需要掌握到什么程度
很多新手容易用力过猛,一上来就啃《Docker 实战》、研究容器网络底层原理、学 Kubernetes,结果半个月下来连镜像都没跑起来。我的建议是,如果你只是想把 Docker 用到日常项目和部署中,先掌握这些东西就够了:
- 会从仓库拉取镜像、运行和删除容器;
- 理解端口映射、数据卷、环境变量这三个核心参数;
- 会用 docker logs 和 docker exec 看日志、进容器排查;
- 会写简单的 Dockerfile 和 docker-compose.yml。
至于容器网络底层、镜像分层优化、安全加固、K8s 调度,这些都是后话。先把上面四件事练熟,你已经能解决 80% 的实际问题了。基础打牢之后你会发现,那些所谓的高深内容,不过是在这四件事上做延伸而已。
2. 第一道坎:在不同操作系统上把 Docker 装起来
2.1 Windows 10/11:Docker Desktop + WSL2 完整安装流程
Windows 上最省事的方式是用 Docker Desktop,但前提是 WSL2 和 Windows 虚拟机平台都打开。建议按下面的顺序操作:
第一步,检查虚拟化是否开启。打开“任务管理器 -> 性能”,看左下角“CPU -> 虚拟化”这一栏,必须是“已启用”。如果显示“已禁用”,需要进 BIOS 开启 Intel VT-x 或 AMD-V。很多新手卡在“virtualization support not detected”这个报错,九成都是这一步没做。
第二步,安装 WSL2。以管理员身份打开 PowerShell 或 CMD,执行wsl --install。如果系统比较旧,也可以手动开启“适用于 Linux 的 Windows 子系统”和“虚拟机平台”两个功能,然后重启。装完后建议执行wsl --set-default-version 2,确保用的是 WSL2 而不是 WSL1。
第三步,下载并安装 Docker Desktop。安装过程中它会自动检测 WSL2,装完以后首次启动可能会提示需要更新 WSL 内核,按提示操作即可。启动后右下角托盘会出现 Docker 图标,等鲸鱼图标稳定下来,说明引擎起来了。
这里要提醒一句:如果你启动 Docker Desktop 后立刻在终端敲 docker 命令,很可能会遇到 “failed to connect to the docker api at npipe:////./pipe/dockerDeskto…” 这类报错。这通常只是 Docker Desktop 还没完全启动,等几秒钟再敲,或者彻底退出重开一次。
验证是否成功,打开 PowerShell 或 CMD,执行:
docker --version docker run hello-world如果能看到一堆英文说明,最后出现 “Hello from Docker!”,说明安装成功了。
2.2 Ubuntu/Debian:用 apt 快速安装
Ubuntu 上安装 Docker 有两类方式。一类是直接用系统自带源:
sudo apt update sudo apt install -y docker.io sudo systemctl enable --now docker这种方式最快,但镜像版本可能不是最新的,对小白反而友好,因为稳定、少坑。如果想要最新版 Docker Engine,需要先配置官方源,再安装 docker-ce。不过对零基础来说,不是非要追求最新版,能用、稳定才是第一位的。
装完以后先验证一下:
sudo docker run hello-world如果权限没配置,每条命令前面都要加 sudo。不想每次加 sudo,可以执行下面两条命令,把当前用户加进 docker 组:
sudo usermod -aG docker $USER newgrp docker退出终端重新登录后,就不用再敲 sudo 了。这一步一定要做,不然后面使用体验会非常难受。
2.3 CentOS/RHEL:安装 Docker CE 并配置开机自启
CentOS 的安装方式和 Ubuntu 不太一样,老版本默认源里没有 docker,需要配置 docker-ce 的软件源。先装基础工具:
sudo yum install -y yum-utils然后添加仓库并安装 docker-ce:
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install -y docker-ce docker-ce-cli containerd.io启动并设置开机自启:
sudo systemctl start docker sudo systemctl enable docker验证方式同样是sudo docker run hello-world。需要说明的是,CentOS 7 默认内核版本比较老,有些新版 Docker 功能可能需要升级内核或者检查兼容性。和 Ubuntu 一样,装完后建议立刻把当前用户加入 docker 组,避免后续所有命令都得带 sudo。
2.4 安装后的基础自检:hello-world 到底在干什么
docker run hello-world看似简单,背后其实走了一整条完整流程。你可以把这条命令拆成四步理解:
- 本地有没有 hello-world 这个镜像?没有,去仓库拉取;
- 拉下来以后,Docker 根据镜像创建一个容器;
- 容器启动后,执行镜像内置的打印程序,输出提示文字;
- 程序执行完毕,容器状态变成 Exited,生命周期结束。
这条命令同时验证了你本地的 Docker 客户端能连上引擎、网络能访问仓库、容器运行链路是通的。如果这一步失败,先不要急着往下学,优先排查安装和网络问题。
顺手可以敲几个热身命令感受一下:
docker images # 查看本地已有镜像 docker ps -a # 查看所有容器,包括已经退出的 docker system df # 查看磁盘占用概览这些命令属于日常最高频的“老三样”,后面所有章节基本都离不开它们。
3. 第一次实操:用三条命令跑起 Nginx、MySQL 8.0 和 Redis 主从
3.1 Nginx:理解端口映射、后台运行与日志
跑一个 Nginx 容器是理解 Docker 运行机制最好的切片。执行:
docker run -d --name nginx-test -p 8080:80 nginx:latest拆开看参数:
-d:后台运行,不占用当前终端。如果少了这个参数,容器会以前台方式运行,Ctrl+C 一停容器就停了;--name nginx-test:给容器起个名字,方便后面管理;-p 8080:80:把宿主机的 8080 端口映射到容器内的 80 端口。因为容器有自己独立的网络空间,宿主机的 80 端口不一定空闲,映射成 8080 可以避免冲突。访问http://localhost:8080就能看到 Nginx 默认页面;nginx:latest:指定镜像名和标签,latest 表示默认最新版。
这里解释一下为什么容器内是 80。Nginx 镜像的默认配置就是监听 80 端口,容器内部不用改,你只需要决定外面的哪个端口对应进来。这个“宿主端口:容器端口”的映射格式,后面会反复出现,建议一下记牢。
接下来可以看日志:
docker logs nginx-test浏览器访问一次后,再执行docker logs nginx-test,你会在日志里看到 200 状态码和访问记录。查问题先看日志,这是容器时代最重要的调试习惯。
如果你想把自己的网页挂进去,可以用-v参数做目录挂载:
docker run -d --name nginx-site -p 8081:80 -v /my-web:/usr/share/nginx/html nginx这里把宿主机/my-web目录映射到容器内 Nginx 的默认网站目录,以后改宿主机文件,容器里立刻生效,不用重新构建镜像。
3.2 MySQL 8.0:数据卷与容器重建后的数据保留
跑数据库容器,最核心的一条规则是:数据必须通过数据卷持久化。先看一条典型命令:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -v mysql8-data:/var/lib/mysql \ mysql:8.0-e MYSQL_ROOT_PASSWORD是环境变量,用来初始化 root 密码,首次启动时必填。-v mysql8-data:/var/lib/mysql这一步最关键,它声明了一个叫mysql8-data的具名数据卷,把容器内部的 MySQL 数据目录映射到卷里。MySQL 8.0 镜像默认把数据存在/var/lib/mysql,如果不挂卷,你删掉容器后数据就跟着烟消云散;挂了卷,哪怕容器被删,只要卷还在,用同样的配置重新建一个容器,数据就还在。
这个“容器可以被随意删除重建,但数据卷必须长命百岁”的认知,是使用 Docker 跑有状态服务的前提。
进入容器执行 MySQL 命令也很简单:
docker exec -it mysql8 mysql -uroot -p输密码后就能进入 MySQL 的交互界面。docker exec的意思是在容器内部执行命令,-it表示进入交互模式,后面会经常用到。
额外提醒一个 8.0 的小坑:MySQL 8.0 默认认证插件是caching_sha2_password,有些老版本的客户端和可视化工具连不上去。遇到Authentication plugin 'caching_sha2_password' cannot be loaded之类的报错,可以在容器内执行下面这条 SQL 修改认证方式:
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';3.3 Redis 主从:自定义网络与容器间通信
Redis 主从是理解容器网络的好素材。如果直接用默认 bridge 网络,两个容器之间的访问得靠 IP 或者额外配置,但 Docker 提供了自定义网络,同一网络内的容器可以直接用容器名互相访问,这个特性比 IP 方便得多,也符合微服务时代的服务发现思路。
先创建一个自定义网络:
docker network create redis-net启动主节点:
docker run -d --name redis-master --network redis-net -p 6379:6379 redis:7 redis-server --appendonly yes启动一个从节点,通过--replicaof指向主节点:
docker run -d --name redis-slave --network redis-net -p 6380:6379 redis:7 redis-server --replicaof redis-master 6379注意看--replicaof redis-master 6379,这里填的不是 IP,而是主节点的容器名。Docker 内置的 DNS 会帮我们解析到对应容器。把宿主机的 6380 端口映射到从节点的 6379,这样从外部也能访问。
验证主从状态:
docker exec -it redis-master redis-cli info replication输出里connected_slaves:1,表示从节点已经连上来。这套“创建网络 -> 起主节点 -> 起从节点”的流程,其实也是后面用 Compose 编排多服务的基础模板。
3.4 容器生命周期管理:停止、删除、进入容器
跑起来三个容器之后,你已经算半只脚入门了。剩下这些高频命令值得记熟:
docker stop nginx-test # 停止容器 docker start nginx-test # 启动已停止的容器 docker restart nginx-test # 重启容器 docker ps # 查看运行中的容器 docker ps -a # 查看所有容器(含已停止) docker rm nginx-test # 删除容器(需要先停止)这里容易混淆的是镜像和容器:docker images查的是镜像,docker ps查的是容器。镜像不会被删掉,除非你主动执行docker rmi;容器只是镜像的实例,可以随手删、随手建。
调试或改配置时,经常需要进容器里看一眼:
docker exec -it nginx-test bash容器里可能没有 vim、curl 这些工具,这是正常现象。毕竟镜像追求最小化,缺什么临时装,或者把容器删了换个带调试工具的镜像。这种“用完即走、重建如新”的理念,一开始可能不习惯,但用顺手之后会觉得特别清爽。
4. 镜像加速、镜像仓库与镜像瘦身
4.1 拉镜像太慢?配置 registry mirror
新手最容易碰到的问题就是拉镜像特别慢,尤其是大镜像,下载动不动就卡住。Docker 默认从 Docker Hub 拉取,在国内网络环境下速度不太理想。解决办法是给 Docker 配置镜像加速地址,也就是 registry mirror。
在 Linux 上编辑/etc/docker/daemon.json:
{ "registry-mirrors": ["https://你的加速地址"] }这里不推荐直接抄网上别人随便写的公共地址,因为这类公共镜像源本身不稳定,而且可能存在供应链风险。比较稳妥的方式是去主流云厂商的容器镜像服务控制台,申请一个个人镜像加速地址,每家都会有,按文档配置即可。你可以在“容器镜像服务”页面里找到“镜像加速器”,把里面分配的 HTTPS 地址填进去。
改完以后重启 Docker:
sudo systemctl daemon-reload sudo systemctl restart docker再用docker info查看 Registry Mirrors 一栏,确认配置生效。说实话,加速地址这个东西真的属于“用过就回不去”的配置,没配置之前拉一个镜像五分钟,配置之后经常几秒钟就完事。
4.2 离线环境下导出与导入镜像
有些服务器在内网环境,无法直接访问 Docker Hub,这时候需要用离线迁移的办法。先在一台能上网的机器上拉好镜像,导出成 tar 包:
docker save -o nginx.tar nginx:latest把 tar 包拷贝到目标机器后,导入:
docker load -i nginx.tardocker save和docker load是一对组合,适合镜像迁移、内网部署、交付到客户现场这些场景。需要注意的是,save 归档的是整个镜像,体积可能很大,建议配合压缩使用,比如导出后用 gzip 压一道。还有一个小细节:别人导出的镜像导入后,需要确认标签是否正常,必要时用docker tag重新打标签,避免后续运行时因为标签不对找不到镜像。
顺带提一下,把自己构建的镜像推送到仓库,通常是先打标签再推送:
docker tag my-app:v1 your-registry.com/my-app:v1 docker push your-registry.com/my-app:v1如果用的是 Docker Hub,登录后推到自己账号名下即可;如果是公司内网仓库,把地址换成内网域名端口就行。
4.3 Dockerfile 构建私有镜像与多阶段构建
跑别人现成的镜像解决了“使用”问题,但很多时候你需要封装自己的应用。这时候要写 Dockerfile。以一个最简单的 Python 应用为例:
FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD ["python", "app.py"]构建命令:
docker build -t my-python-app:v1 .docker build会按 Dockerfile 里的步骤逐层构建。这里有一个经验:COPY requirements.txt和COPY . .分成了两步,而不是一次性把整个目录拷贝进去。原因是 Docker 会利用构建缓存,以后只要 requirements.txt 没变,pip install 这一层就不会重新执行,构建速度会快非常多。
业务代码变大以后,我强烈建议用多阶段构建来瘦身。很多前端 + 后端项目,构建工具体积很大,但运行时根本不需要这些工具。多阶段构建的思路是:先用一个带编译环境的镜像完成构建,再把产物拷贝到一个小体积的运行镜像里。以 Node.js 项目为例:
# 阶段一:构建 FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # 阶段二:运行 FROM nginx:alpine COPY --from=builder /app/dist /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]这样最终镜像里只剩 Nginx 和静态文件,体积可以从几个 GB 缩小到几十 MB。还有一点不要忘:写一个.dockerignore文件,把node_modules、.git、dist这些目录排除在构建上下文之外,否则COPY . .会把这些无关文件也拷进去,既拖慢构建又撑大镜像。
5. Docker Compose:从“一条命令一个容器”到“一条命令一个项目”
5.1 docker run 太散?用 Compose 描述整个项目
零基础跑通单个容器以后,很快会撞上一堵墙:项目一复杂,docker run 命令就变得又长又散。启动一个服务要敲一行带一堆参数的命令,多个服务之间还得保证顺序、网络、数据卷正确,手动操作非常容易出错。
Docker Compose 就是来解决这个问题的。它把一个项目涉及的所有服务、网络、数据卷写进一个docker-compose.yml文件,之后一键启动、一键停止。你可以把 compose 文件理解成“项目的部署说明书”,别人拿到这份文件,不用看你敲过什么命令,一条命令就能把整套环境拉起来。
目前新版的 Docker Desktop 和 docker-compose v2 直接内置了 compose 能力,Linux 上如果用的是 docker.io,可能需要单独安装docker-compose-plugin或docker-compose,装好后用docker compose version验证一下。
5.2 一个完整的 web + mysql 示例
直接上手看一个结构比较典型的 compose 文件:
version: "3.9" services: app: build: . ports: - "8080:8080" depends_on: mysql: condition: service_healthy environment: DB_HOST: mysql DB_PORT: 3306 DB_USER: root DB_PASSWORD: secret mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: secret MYSQL_DATABASE: myblog volumes: - mysql-data:/var/lib/mysql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 5s timeout: 3s retries: 10 volumes: mysql-data:启动命令:
docker compose up -d查看状态和日志:
docker compose ps docker compose logs -f停止并清理:
docker compose stop docker compose down -v逐个解释关键点:
build: .表示 app 服务用当前目录的 Dockerfile 构建,不用提前手动 build;depends_on用来控制启动顺序。这里加了condition: service_healthy的条件,意思是 MySQL 健康检查通过之后才启动 app,能有效避免“app 启动时连不上数据库”的问题;healthcheck是健康检查,对于有依赖关系的服务非常有用;volumes用mysql-data:声明了一个具名数据卷,和-v mysql8-data:/var/lib/mysql的效果一致;-d是让 compose 在后台运行,如果去掉可以看到前台日志输出。
这一套下来,你会发现 docker run 那套经验完全平移到了 compose 里,并没有多少新概念。多用几次后,你会习惯“先看 compose 再动手”的部署方式。
5.3 把 Redis 主从也放进 Compose:项目级编排的通用套路
前面手动用 docker run 创建 Redis 主从的过程,也可以塞进 compose 里。其实一旦你理解了 5.2 的结构,把任何服务资源通过image、environment、volumes、networks四个核心字段声明进去就可以了。很多社区项目,比如 GitLab 社区版、Dify、DataX-WEB、青龙面板这类大体量项目,它们官方提供的部署方案本质上也是 compose 文件——拉镜像、配置环境变量、挂数据卷、映射端口,跑起来后你只需要关注业务本身,基础设施的事交给 Docker 管。
这里给一个最简的 Redis 主从 compose 写法,方便你感受一下“编排”的思路:
version: "3.9" services: redis-master: image: redis:7 command: ["redis-server", "--appendonly", "yes"] ports: - "6379:6379" redis-slave: image: redis:7 command: ["redis-server", "--replicaof", "redis-master", "6379"] depends_on: - redis-master当然生产环境远不止这几个字段,但底层逻辑是一样的。学会 compose 之后再接触 Kubernetes、Helm 这些编排方案,你会发现它们解决的问题虽然更深,但核心诉求——把部署过程变成可复用的描述文件——是完全一致的。
6. 排错实录:新手最容易翻车的几个现场
6.1 启动失败:virtualization support not detected / npipe 连接失败
这是 Windows 用户刚安装 Docker Desktop 时最常见的两处卡点。
第一处是 “virtualization support not detected”。这个报错分两种情况:一种是 BIOS 里的虚拟化开关没开,需要进 BIOS 开启 Intel VT-x 或 AMD-V;另一种是已经开了,但 Windows 的 Hyper-V / 虚拟机平台功能没有启用。建议先打开任务管理器确认“虚拟化”是否为“已启用”,再去“控制面板 -> 程序 -> 启用或关闭 Windows 功能”里勾选“虚拟机平台”和“适用于 Linux 的 Windows 子系统”。
第二处是开头提到的failed to connect to the docker api at npipe:////./pipe/dockerDesktopLinux。我记得第一次看到这个报错也慌了,以为是安装出问题了。其实绝大多数情况下,Docker Desktop 还处于启动加载中,后台服务没有就绪。等一下再执行命令,或者右键托盘图标重启 Docker Desktop,基本都能解决。如果真的持续报错,再考虑重置 WSL2 内核或重装 Docker Desktop。
在 Linux 上遇到 docker 服务起不来的情况,第一件事不是直接重装,而是看日志:
systemctl status docker journalctl -u docker -n 50 --no-pager日志会告诉你到底是网络问题、存储驱动问题,还是某个依赖服务没起来。盲猜方向没有意义,日志才是唯一可信的依据。
6.2 权限问题:为什么 docker 命令需要 sudo
很多人在 Ubuntu 上装完 Docker 后,执行docker ps会看到permission denied。原因其实很简单:Docker 引擎默认需要 root 权限,当前用户不在 docker 组里,所以需要 sudo 提权。解决办法就是前面提过的:
sudo usermod -aG docker $USER执行后一定要退出终端重新登录,或者执行newgrp docker,否则不会生效。这里提醒一下,给用户加 docker 组相当于授予了接近 root 的权限,因为 docker 组里的用户可以通过挂载宿主目录等方式控制宿主机。个人开发机这样做没问题,但公司多用户环境要谨慎,别随便给所有人都加上。
6.3 端口冲突与容器间网络不通
端口冲突是最常见的启动失败场景。docker run -p 8080:80报port is already allocated,说明 8080 端口已经被其他程序或容器占用。先查是谁占的:
sudo lsof -i:8080或者用 Windows 上的:
netstat -ano | findstr 8080找到进程后,要么停掉进程换一个端口,要么换一个宿主端口映射。我的习惯是尽量用不常见的端口段,比如 18080、28080,降低冲突概率。
容器间网络不通也是高频问题。如果你创建的是默认 bridge 网络,两个容器之间不能用容器名访问;你需要自定义网络。前面 Redis 主从的例子已经演示过:docker network create redis-net之后把两个容器都放入同一网络,容器名就会自动解析。排查网络问题,可以看容器到底在哪个网络里:
docker inspect redis-master | grep -A 10 "Networks" docker network inspect redis-net如果容器启动时忘了指定网络,可以用docker network connect把它补挂进去,不用重建容器。
6.4 MySQL / Redis 卷目录权限问题
跑数据库容器时,如果你用的是“绑定挂载”,也就是把宿主机某个目录直接映射进容器,很容易遇到Permission denied或 MySQL 初始化失败。原因通常是宿主机目录的属主和权限跟容器内运行用户不匹配。
具名卷(-v mysql-data:/var/lib/mysql)能绕开大多数权限问题,因为 Docker 会自动管理目录权限。如果必须用绑定挂载,最简单的办法是给目录开放足够权限,或者显式调整属主:
mkdir -p /data/mysql chmod 777 /data/mysql在生产环境更规范的做法是找到镜像里 MySQL 运行用户的 UID,用chown -R UID:GID去设置目录属主。对新手来说,记住“优先使用具名卷”这条原则,能少踩很多坑。
6.5 容器内时区不对、数据丢了怎么办
时区问题是很容易被忽视的“非功能性问题”。默认情况下,很多镜像的时区是 UTC,比北京时间慢 8 小时,日志时间看起来就“穿越”了。解决办法是在运行容器时注入时区环境变量:
docker run -d --name app -e TZ=Asia/Shanghai your-image在 compose 文件里,同样是在environment下面加一行TZ: Asia/Shanghai。如果镜像没有内置 tzdata,可能还需要在 Dockerfile 里安装对应时区包,然后设置环境变量。
数据丢失的应急处理也要了解。万一你忘了挂数据卷,容器已经被删了,不要慌,只要容器不是被docker rm -f强删后彻底不可恢复,还有救。
请记住,容器删了就没了,但容器里还残留文件时,可以用docker cp把文件从容器里拷出来再抢救。例如:
docker cp mysql8:/var/lib/mysql /backup/mysql但真正治本的办法永远是:有状态服务必须挂数据卷,并且养成定时备份的习惯。容器随时可以重建,数据卷才是你的“真金白银”。
最后分享一点我的体会
带过不少新人之后,我发现零基础学 Docker 最大的障碍其实不是命令多,而是心态上的“急于求成”。建议你按照这条路径走:先把镜像、容器、数据卷这三个概念吃透;然后照着本文的命令把 Nginx、MySQL、Redis 各跑一遍;再尝试把几个服务写进一个 compose 文件;最后遇到问题养成“先查日志、再改配置、最后重装”的排查习惯。若能做到这些,你已经可以把容器化用到日常项目和部署里了。至于更复杂的生产级设计,等你真的需要时再学也不迟——那时候你会发现,脚下的地基已经足够牢固。