Docker 新手最容易踩的坑,一篇讲清楚怎么避开
Docker 使用避坑指南|零基础可学
在学习后端开发、运维部署、微服务、云原生等技术时,Docker 几乎是绕不开的工具。它可以把应用和运行环境一起打包,让程序在不同机器上尽可能保持一致,解决“我电脑上能跑,服务器上跑不了”的经典问题。
不过,对于零基础用户来说,Docker 刚开始看似简单:docker run 一条命令就能启动服务。但真正使用时,很容易遇到镜像拉不下来、容器数据丢失、端口冲突、容器删了服务就没了、权限不足、磁盘爆满等问题。
本文将从零基础角度出发,系统介绍 Docker 的核心概念、常用命令、实际使用中的常见坑,以及对应的避坑方法,帮助你少走弯路。
一、先搞懂 Docker 到底是什么
Docker 是一种容器化技术。你可以把它理解为一个“轻量级运行环境打包工具”。
传统部署方式通常是:
- 在服务器安装操作系统;
- 安装运行环境,比如 Java、Node.js、Python、MySQL;
- 配置环境变量;
- 上传代码;
- 启动服务。
这个过程中,任何一个环境差异都可能导致程序运行失败。
Docker 的思路是:
把应用程序、依赖环境、配置文件等打包成一个“镜像”,然后基于镜像启动“容器”。这样应用在哪里运行,都尽量保持一致。
二、Docker 的几个核心概念
学习 Docker,最容易混淆的是镜像、容器、仓库、数据卷这些概念。先把它们搞清楚,后面会轻松很多。
1. 镜像 Image
镜像可以理解为“模板”或“安装包”。
比如:
nginx:latest
mysql:8.0
redis:7
ubuntu:22.04
这些都是镜像。
镜像本身是静态的,不能直接提供服务。你需要基于镜像启动容器。
2. 容器 Container
容器是镜像运行起来后的实例。
举个例子:
mysql:8.0是镜像;- 你通过它启动了一个 MySQL 服务,这个正在运行的 MySQL 就是容器。
一个镜像可以启动多个容器。就像一个安装包可以在多台电脑上安装出多个程序一样。
3. 仓库 Registry
仓库是存放镜像的地方。
最常见的是 Docker Hub,类似于 GitHub,只不过 GitHub 存代码,Docker Hub 存镜像。
当你执行:
docker pull nginx
Docker 就会从远程镜像仓库拉取 Nginx 镜像到本地。
4. 数据卷 Volume
数据卷用于持久化保存容器中的数据。
这是 Docker 使用中最重要的概念之一。
很多新手会以为:容器里产生的数据会一直存在。
实际上,如果你删除容器,容器内部的数据通常也会随之消失。
所以运行 MySQL、Redis、Nginx、应用日志等服务时,一定要考虑数据持久化。
三、Docker 安装后的第一个检查
安装 Docker 后,可以先执行:
docker version
查看 Docker 客户端和服务端版本。
再执行:
docker info
查看 Docker 运行状态。
如果出现权限问题,比如:
permission denied while trying to connect to the Docker daemon socket
说明当前用户没有权限访问 Docker 服务。
在 Linux 中可以执行:
sudo usermod -aG docker $USER
然后退出终端重新登录。
注意:把用户加入 docker 用户组等同于赋予较高系统权限,不要随便给不可信用户开放。
四、常用命令入门
1. 拉取镜像
docker pull nginx
指定版本:
docker pull nginx:1.25
如果不指定版本,默认使用 latest 标签。
2. 查看本地镜像
docker images
3. 启动容器
docker run nginx
这样会以前台方式运行,终端会被占用。
更常用的是后台运行:
docker run -d nginx
4. 查看正在运行的容器
docker ps
查看所有容器,包括已经停止的:
docker ps -a
5. 停止容器
docker stop 容器ID或容器名
6. 删除容器
docker rm 容器ID或容器名
如果容器正在运行,需要先停止再删除,或者强制删除:
docker rm -f 容器ID或容器名
7. 删除镜像
docker rmi 镜像ID或镜像名
如果镜像正在被容器使用,需要先删除相关容器。
五、新手最容易踩的坑
下面是 Docker 使用中非常常见的问题,也是本文的重点。
坑 1:以为容器删除后数据还在
这是 Docker 新手最容易踩的坑。
例如你运行 MySQL:
docker run -d \
--name mysql-demo \
-e MYSQL_ROOT_PASSWORD=123456 \
mysql:8.0
此时 MySQL 数据默认存储在容器内部。
如果你执行:
docker rm -f mysql-demo
容器被删除后,里面的数据库数据也可能一起消失。
正确做法:使用数据卷
docker run -d \
--name mysql-demo \
-e MYSQL_ROOT_PASSWORD=123456 \
-v mysql-data:/var/lib/mysql \
mysql:8.0
这里的:
-v mysql-data:/var/lib/mysql
表示把 MySQL 的数据目录挂载到 Docker 数据卷 mysql-data。
即使容器删除,数据卷仍然存在。下次重新创建容器时,继续挂载这个数据卷即可恢复数据。
查看数据卷:
docker volume ls
删除数据卷:
docker volume rm mysql-data
避坑建议:凡是数据库、中间件、上传文件、日志文件,都要提前规划数据持久化。
坑 2:不知道端口映射,服务启动了却访问不了
很多新手启动 Nginx:
docker run -d nginx
然后打开浏览器访问服务器 IP,发现访问不了。
原因是容器内部的 80 端口没有映射到宿主机。
正确做法:使用 -p 参数
docker run -d \
--name nginx-demo \
-p 8080:80 \
nginx
含义是:
宿主机端口:容器端口
也就是说,访问宿主机的 8080 端口,会转发到容器的 80 端口。
浏览器访问:
http://服务器IP:8080
即可看到 Nginx 页面。
注意端口冲突
如果你看到类似错误:
Bind for 0.0.0.0:8080 failed: port is already allocated
说明宿主机的 8080 端口已经被占用。
可以换一个端口:
-p 8081:80
坑 3:盲目使用 latest 标签
很多教程里会写:
docker pull mysql
或者:
docker run nginx:latest
latest 看似代表最新版本,但它不一定适合生产环境。
问题在于:
- 镜像版本可能变化;
- 依赖行为可能变化;
- 重启或重新部署后可能出现不兼容;
- 排查问题困难。
正确做法:指定明确版本
例如:
docker run -d nginx:1.25
MySQL:
docker run -d mysql:8.0
Redis:
docker run -d redis:7.2
避坑建议:学习阶段可以用
latest,生产环境一定要固定版本。
坑 4:容器名称不固定,后续管理困难
如果你直接运行:
docker run -d nginx
Docker 会自动生成一个随机容器名,比如:
romantic_morse
刚开始可能无所谓,但容器多了之后就很难管理。
正确做法:使用 --name
docker run -d \
--name nginx-demo \
-p 8080:80 \
nginx
之后你可以直接使用容器名操作:
docker stop nginx-demo
docker start nginx-demo
docker logs nginx-demo
坑 5:容器异常退出后不知道如何排查
有时执行 docker run 后,容器马上退出。新手常常不知道发生了什么。
查看容器状态
docker ps -a
如果看到状态是 Exited,说明容器已经停止。
查看日志
docker logs 容器名或容器ID
实时查看日志:
docker logs -f 容器名或容器ID
查看最后 100 行:
docker logs --tail=100 容器名或容器ID
大多数启动失败问题,都可以通过日志找到原因。
坑 6:进入容器后修改配置,却忘了容器不是虚拟机
你可以通过下面命令进入容器:
docker exec -it 容器名 bash
有些镜像没有 bash,可以用 sh:
docker exec -it 容器名 sh
但新手容易犯一个错误:
进入容器后手动安装软件、修改配置,然后以为这些修改会长期保留。
实际上,容器应当被视为“可替换”的运行实例。
如果删除容器再重新创建,手动改动通常都会丢失。
正确做法
- 配置文件使用挂载;
- 程序依赖写进 Dockerfile;
- 数据使用数据卷;
- 容器只负责运行服务,不负责长期保存手动修改。
例如挂载 Nginx 配置:
docker run -d \
--name nginx-demo \
-p 8080:80 \
-v /opt/nginx/nginx.conf:/etc/nginx/nginx.conf \
nginx
坑 7:分不清 COPY 和挂载目录
构建镜像时经常用到 Dockerfile。
示例:
FROM nginx:1.25
COPY ./html /usr/share/nginx/html
这里的 COPY 是在构建镜像时,把本地文件复制进镜像。
而运行容器时的 -v 是挂载目录:
docker run -d \
-v /opt/html:/usr/share/nginx/html \
nginx
二者区别:
| 方式 | 发生时间 | 是否改变镜像 | 适合场景 |
|---|---|---|---|
| COPY | 构建镜像时 | 是 | 打包应用代码 |
| -v 挂载 | 运行容器时 | 否 | 配置、数据、开发调试 |
避坑建议
- 生产部署:通常把代码打包进镜像;
- 配置和数据:通常使用挂载;
- 开发调试:可以挂载本地代码目录。
坑 8:不清理镜像和容器,导致磁盘爆满
Docker 用久了会产生很多停止的容器、无用镜像、构建缓存和数据卷。
查看 Docker 占用空间:
docker system df
清理停止的容器、无用网络、悬空镜像等:
docker system prune
如果要清理更多未使用镜像:
docker system prune -a
清理未使用数据卷:
docker volume prune
注意:清理数据卷前一定要确认没有重要数据,否则可能造成数据丢失。
坑 9:生产环境不设置重启策略
如果服务器重启,或者容器异常退出,默认情况下容器不一定会自动恢复。
正确做法:添加重启策略
docker run -d \
--name nginx-demo \
--restart=always \
-p 8080:80 \
nginx
常见策略:
| 参数 | 含义 |
|---|---|
| no | 默认,不自动重启 |
| always | 总是自动重启 |
| unless-stopped | 除非手动停止,否则自动重启 |
| on-failure | 失败时重启 |
一般生产环境常用:
--restart=unless-stopped
坑 10:不会使用 Docker Compose,命令越来越长
当你只有一个 Nginx 容器时,docker run 还可以接受。
但如果你需要同时运行:
- 后端应用;
- MySQL;
- Redis;
- Nginx;
- 消息队列;
命令会越来越长,也难以维护。
这时应该使用 Docker Compose。
示例:docker-compose.yml
services:
nginx:
image: nginx:1.25
container_name: nginx-demo
ports:
- "8080:80"
restart: unless-stopped
启动:
docker compose up -d
停止:
docker compose down
查看日志:
docker compose logs -f
Docker Compose 的优势是:
把容器配置写进文件,方便保存、复制、部署和版本管理。
六、一个完整的 MySQL 示例
下面是一个比较规范的 MySQL 启动示例:
docker run -d \
--name mysql8 \
--restart=unless-stopped \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=your_password \
-e TZ=Asia/Shanghai \
-v mysql8-data:/var/lib/mysql \
mysql:8.0
说明:
| 参数 | 作用 |
|---|---|
--name mysql8 |
指定容器名称 |
--restart=unless-stopped |
自动重启 |
-p 3306:3306 |
映射端口 |
-e MYSQL_ROOT_PASSWORD |
设置 root 密码 |
-e TZ=Asia/Shanghai |
设置时区 |
-v mysql8-data:/var/lib/mysql |
持久化数据 |
mysql:8.0 |
使用指定版本镜像 |
查看运行状态:
docker ps
查看日志:
docker logs -f mysql8
进入容器:
docker exec -it mysql8 bash
七、一个完整的 Redis 示例
docker run -d \
--name redis7 \
--restart=unless-stopped \
-p 6379:6379 \
-v redis7-data:/data \
redis:7.2 \
redis-server --appendonly yes
这里的:
redis-server --appendonly yes
表示开启 AOF 持久化,避免 Redis 数据在重启后丢失。
八、Dockerfile 入门示例
假设你有一个简单的 Node.js 项目,可以写一个 Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
解释一下:
| 指令 | 作用 |
|---|---|
| FROM | 指定基础镜像 |
| WORKDIR | 设置工作目录 |
| COPY | 复制文件到镜像 |
| RUN | 构建时执行命令 |
| EXPOSE | 声明容器端口 |
| CMD | 容器启动时执行命令 |
构建镜像:
docker build -t my-node-app:1.0 .
运行容器:
docker run -d \
--name my-node-app \
-p 3000:3000 \
my-node-app:1.0
九、Docker 使用安全建议
Docker 很方便,但不能忽视安全问题。
1. 不要随便运行陌生镜像
镜像中可能包含恶意脚本、后门程序或挖矿程序。优先选择官方镜像或可信来源镜像。
2. 不要把敏感信息写死在镜像里
例如不要在 Dockerfile 中写:
ENV PASSWORD=123456
生产环境应使用环境变量、配置中心、密钥管理工具等方式管理敏感信息。
3. 避免容器使用特权模式
不要轻易使用:
--privileged
它会让容器拥有过高权限,增加安全风险。
4. 不要直接暴露数据库端口到公网
比如 MySQL 的 3306、Redis 的 6379,不建议直接暴露到公网。
如果必须访问,应配置防火墙、安全组、强密码和访问白名单。
十、新手推荐学习路线
如果你是零基础,可以按照这个顺序学习:
- 理解镜像、容器、仓库、数据卷;
- 掌握
docker run、docker ps、docker stop、docker logs; - 学会端口映射和目录挂载;
- 学会运行 Nginx、MySQL、Redis;
- 学会编写简单 Dockerfile;
- 学会 Docker Compose;
- 学会查看日志、清理资源、备份数据;
- 再进一步学习网络、镜像优化和生产部署。
不要一开始就急着研究 Kubernetes。
Docker 基础打牢之后,再学习容器编排会轻松很多。
十一、常用命令速查表
| 操作 | 命令 |
|---|---|
| 查看 Docker 版本 | docker version |
| 查看 Docker 信息 | docker info |
| 拉取镜像 | docker pull 镜像名 |
| 查看镜像 | docker images |
| 启动容器 | docker run |
| 查看运行中容器 | docker ps |
| 查看所有容器 | docker ps -a |
| 停止容器 | docker stop 容器名 |
| 启动已停止容器 | docker start 容器名 |
| 删除容器 | docker rm 容器名 |
| 删除镜像 | docker rmi 镜像名 |
| 查看日志 | docker logs 容器名 |
| 进入容器 | docker exec -it 容器名 bash |
| 查看数据卷 | docker volume ls |
| 查看空间占用 | docker system df |
| 清理无用资源 | docker system prune |
十二、总结
Docker 的核心价值是让应用和环境一起交付,提升部署一致性和效率。对于零基础学习者来说,刚开始不需要追求复杂架构,只要先掌握几个关键点:
- 镜像是模板,容器是运行实例;
- 容器删除后,内部数据可能丢失;
- 数据库等服务必须使用数据卷;
- 访问容器服务需要端口映射;
- 生产环境不要盲目使用
latest; - 容器异常时先看
docker logs; - 多容器项目建议使用 Docker Compose;
- 定期清理无用资源,避免磁盘爆满。
只要理解这些基本原则,Docker 就不再神秘。它不是只能由运维工程师使用的复杂工具,而是每个开发者都应该掌握的基础能力。
真正的 Docker 学习,不是背命令,而是理解容器的运行方式。掌握了镜像、容器、端口、挂载、日志和 Compose,你就已经能够完成大多数日常开发和部署任务。