Docker 踩坑排查手册:从启动失败到数据丢失,附完整示例源码
Docker 常见问题汇总|附源码
Docker 作为当前最流行的容器化技术之一,已经广泛应用于开发、测试、部署、运维等多个环节。无论是个人开发者,还是企业级项目,Docker 都能帮助我们解决“环境不一致”“部署复杂”“依赖冲突”“服务迁移困难”等问题。
不过,很多人在使用 Docker 的过程中,经常会遇到各种各样的问题。例如:镜像拉取失败、容器无法启动、端口无法访问、数据丢失、容器之间无法通信、Dockerfile 构建失败、权限不足、磁盘空间占满等。
本文将系统整理 Docker 使用过程中常见的问题,并结合示例源码进行说明,帮助你快速定位问题、理解原因并掌握解决方案。
一、Docker 是什么?
Docker 是一种开源的容器化平台,它可以将应用程序以及运行所需的依赖、配置、系统库等打包到一个轻量级、可移植的容器中。
简单来说:
Docker 可以让你的应用在任何支持 Docker 的环境中以相同方式运行。
例如,你在本地开发了一个 Java、Node.js 或 Python 项目,部署到服务器时,经常会遇到以下问题:
- 本地可以运行,服务器运行失败;
- 本地 Node.js 是 18,服务器是 16;
- 本地 MySQL 是 8,服务器是 5.7;
- 系统库版本不同;
- 配置文件路径不一致;
- 部署步骤复杂,容易遗漏。
Docker 通过镜像和容器机制,将这些问题大幅简化。
二、Docker 常用概念
在解决问题之前,先了解几个核心概念。
1. 镜像 Image
镜像可以理解为应用运行环境的模板,里面包含了操作系统基础环境、应用代码、依赖包和启动命令等。
例如:
docker pull nginx
docker pull mysql:8.0
docker pull redis:7
这些命令会从镜像仓库拉取对应镜像。
2. 容器 Container
容器是镜像运行起来之后的实例。
例如:
docker run -d --name my-nginx nginx
这里 nginx 是镜像,my-nginx 是运行出来的容器。
3. Dockerfile
Dockerfile 是用于构建镜像的脚本文件,里面描述了镜像如何生成。
示例:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
4. Docker Compose
Docker Compose 用于管理多个容器,非常适合开发环境、微服务项目和依赖多个组件的应用。
例如一个 Web 项目可能依赖:
- Web 服务;
- MySQL;
- Redis;
- Nginx。
可以通过一个 docker-compose.yml 一次性启动。
三、Docker 安装后命令无法使用
问题现象
安装 Docker 后执行命令:
docker version
提示:
docker: command not found
或:
Cannot connect to the Docker daemon
原因分析
常见原因有:
- Docker 没有安装成功;
- Docker 服务没有启动;
- 当前用户没有权限访问 Docker;
- 环境变量没有配置好。
解决方案
1. 检查 Docker 是否安装
docker --version
如果没有输出版本信息,需要重新安装 Docker。
2. 启动 Docker 服务
Linux 系统:
sudo systemctl start docker
设置开机自启:
sudo systemctl enable docker
查看状态:
sudo systemctl status docker
3. 当前用户加入 docker 用户组
如果执行 Docker 命令需要一直加 sudo,可以执行:
sudo usermod -aG docker $USER
然后退出终端重新登录,或者执行:
newgrp docker
再次测试:
docker ps
四、Docker 镜像拉取失败
问题现象
执行:
docker pull nginx
出现:
Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled
或者拉取速度非常慢。
原因分析
这通常是由于网络环境导致访问 Docker Hub 不稳定,或者 DNS 解析异常。
解决方案
1. 配置镜像加速器
编辑 Docker 配置文件:
sudo mkdir -p /etc/docker
sudo vim /etc/docker/daemon.json
写入如下内容:
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://mirror.baidubce.com"
]
}
重启 Docker:
sudo systemctl daemon-reload
sudo systemctl restart docker
验证配置是否生效:
docker info
查看输出中是否包含 Registry Mirrors。
五、容器启动后马上退出
问题现象
执行:
docker run my-app
容器启动后立即退出。
查看容器:
docker ps -a
发现状态为:
Exited
原因分析
Docker 容器的生命周期依赖于主进程。如果容器中的主进程执行完毕,容器就会退出。
例如:
CMD ["echo", "hello docker"]
这个命令执行完后,容器自然退出。
解决方案
1. 查看日志
docker logs 容器ID或容器名
例如:
docker logs my-app
2. 检查启动命令
如果是 Node.js 项目,正确写法可能是:
CMD ["npm", "start"]
如果是 Java 项目:
CMD ["java", "-jar", "app.jar"]
3. 临时进入容器排查
docker run -it my-app sh
如果镜像基于 Ubuntu 或 Debian:
docker run -it my-app bash
六、端口映射后无法访问
问题现象
启动 Nginx:
docker run -d --name nginx-test -p 8080:80 nginx
访问:
http://服务器IP:8080
无法打开页面。
原因分析
可能原因包括:
- 容器没有正常运行;
- 端口映射写错;
- 服务器防火墙未开放端口;
- 云服务器安全组未放行端口;
- 应用监听地址错误。
排查步骤
1. 查看容器状态
docker ps
确认容器是否正在运行。
2. 查看端口映射
docker port nginx-test
输出类似:
80/tcp -> 0.0.0.0:8080
3. 本机测试
curl http://127.0.0.1:8080
如果本机可以访问,但外网不能访问,通常是防火墙或云安全组问题。
4. 开放防火墙端口
CentOS:
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
sudo firewall-cmd --reload
Ubuntu:
sudo ufw allow 8080/tcp
七、容器内无法访问宿主机服务
问题现象
容器中的应用需要访问宿主机上的 MySQL、Redis 或其他服务,但使用 127.0.0.1 连接失败。
原因分析
在容器内部,127.0.0.1 指的是容器自身,而不是宿主机。
解决方案
1. 使用 host.docker.internal
在 Docker Desktop 环境中可以使用:
host.docker.internal
例如:
mysql://host.docker.internal:3306/test
2. Linux 中添加 host 映射
Linux 环境下可以使用:
docker run --add-host=host.docker.internal:host-gateway my-app
3. 使用宿主机网关 IP
查看 Docker 默认网桥:
ip addr show docker0
通常宿主机地址为:
172.17.0.1
容器内可以访问:
172.17.0.1:3306
八、容器之间无法通信
问题现象
一个 Web 容器需要访问 MySQL 容器,但连接失败。
错误示例:
ECONNREFUSED
Unknown host mysql
Connection timed out
原因分析
容器之间如果不在同一个 Docker 网络中,默认无法通过容器名通信。
解决方案
1. 创建自定义网络
docker network create app-network
启动 MySQL:
docker run -d \
--name mysql \
--network app-network \
-e MYSQL_ROOT_PASSWORD=123456 \
mysql:8.0
启动应用:
docker run -d \
--name web \
--network app-network \
-e DB_HOST=mysql \
my-web-app
在同一个自定义网络中,应用可以通过容器名 mysql 访问数据库。
九、Docker Compose 示例源码
下面给出一个完整的 Docker Compose 示例,包含:
- Node.js Web 服务;
- MySQL 数据库;
- Redis 缓存服务。
项目目录结构如下:
docker-demo/
├── app/
│ ├── Dockerfile
│ ├── package.json
│ └── server.js
└── docker-compose.yml
1. Node.js 示例源码:server.js
const express = require("express");
const mysql = require("mysql2/promise");
const redis = require("redis");
const app = express();
const port = process.env.PORT || 3000;
const dbConfig = {
host: process.env.DB_HOST || "mysql",
user: process.env.DB_USER || "root",
password: process.env.DB_PASSWORD || "123456",
database: process.env.DB_NAME || "demo"
};
const redisClient = redis.createClient({
url: `redis://${process.env.REDIS_HOST || "redis"}:6379`
});
async function initRedis() {
redisClient.on("error", err => {
console.error("Redis error:", err);
});
await redisClient.connect();
}
app.get("/", async (req, res) => {
res.send("Hello Docker!");
});
app.get("/health", async (req, res) => {
res.json({
status: "ok",
time: new Date().toISOString()
});
});
app.get("/db", async (req, res) => {
try {
const connection = await mysql.createConnection(dbConfig);
const [rows] = await connection.execute("SELECT NOW() AS currentTime");
await connection.end();
res.json({
success: true,
data: rows
});
} catch (error) {
res.status(500).json({
success: false,
message: error.message
});
}
});
app.get("/redis", async (req, res) => {
try {
const count = await redisClient.incr("visit_count");
res.json({
success: true,
visitCount: count
});
} catch (error) {
res.status(500).json({
success: false,
message: error.message
});
}
});
app.listen(port, async () => {
await initRedis();
console.log(`Server is running on port ${port}`);
});
2. package.json
{
"name": "docker-demo",
"version": "1.0.0",
"description": "Docker common issues demo",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.18.3",
"mysql2": "^3.9.0",
"redis": "^4.6.13"
}
}
3. Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
4. docker-compose.yml
version: "3.8"
services:
web:
build:
context: ./app
container_name: docker-demo-web
ports:
- "3000:3000"
environment:
PORT: 3000
DB_HOST: mysql
DB_USER: root
DB_PASSWORD: 123456
DB_NAME: demo
REDIS_HOST: redis
depends_on:
- mysql
- redis
networks:
- demo-network
mysql:
image: mysql:8.0
container_name: docker-demo-mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: demo
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
networks:
- demo-network
redis:
image: redis:7-alpine
container_name: docker-demo-redis
restart: always
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- demo-network
networks:
demo-network:
driver: bridge
volumes:
mysql-data:
redis-data:
十、Docker Compose 启动失败
问题现象
执行:
docker compose up -d
出现错误:
services.web.depends_on contains an invalid type
或者:
docker-compose: command not found
原因分析
Docker Compose 目前存在两个常见版本:
- 老版本命令:
docker-compose - 新版本命令:
docker compose
如果环境安装的是 Docker Compose V2,推荐使用:
docker compose up -d
常用命令
启动服务:
docker compose up -d
查看服务:
docker compose ps
查看日志:
docker compose logs -f
只查看某个服务日志:
docker compose logs -f web
停止服务:
docker compose down
停止并删除数据卷:
docker compose down -v
重新构建:
docker compose up -d --build
十一、数据卷挂载后数据丢失
问题现象
MySQL 容器删除后,数据库数据丢失。
原因分析
如果没有使用 Docker Volume 或宿主机目录挂载,容器删除后,容器内的数据也会随之删除。
错误示例:
docker run -d --name mysql mysql:8.0
正确示例:
docker run -d \
--name mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
-v mysql-data:/var/lib/mysql \
mysql:8.0
查看数据卷
docker volume ls
查看数据卷详情
docker volume inspect mysql-data
删除无用数据卷
docker volume prune
注意:该命令会删除未被容器使用的数据卷,执行前务必确认。
十二、Dockerfile 构建速度慢
问题现象
执行:
docker build -t my-app .
构建时间很长,每次都重新下载依赖。
原因分析
Docker 构建镜像时会使用缓存,但 Dockerfile 顺序不合理会导致缓存失效。
错误写法:
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install
CMD ["npm", "start"]
这种写法中,只要业务代码发生变化,就会导致 npm install 重新执行。
优化写法
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
这样只要 package.json 没有变化,依赖安装步骤就可以复用缓存。
十三、镜像体积过大
问题现象
构建出来的镜像非常大,例如几百 MB 甚至几个 GB。
原因分析
常见原因:
- 使用了过大的基础镜像;
- 没有清理缓存;
- 将无关文件复制进镜像;
- 没有使用多阶段构建;
.dockerignore文件缺失。
解决方案
1. 使用 Alpine 镜像
FROM node:18-alpine
相比完整 Linux 发行版,Alpine 镜像体积更小。
2. 添加 .dockerignore
node_modules
.git
.gitignore
Dockerfile
docker-compose.yml
npm-debug.log
README.md
3. 多阶段构建示例
以 Go 项目为例:
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o server main.go
FROM alpine:3.19
WORKDIR /app
COPY --from=builder /app/server .
EXPOSE 8080
CMD ["./server"]
这样最终镜像只包含运行所需的二进制文件。
十四、容器时间不正确
问题现象
容器内时间与宿主机或实际时区不一致。
查看容器时间:
docker exec -it 容器名 date
解决方案
1. 挂载宿主机时间配置
docker run -d \
--name app \
-v /etc/localtime:/etc/localtime:ro \
-v /etc/timezone:/etc/timezone:ro \
my-app
2. 设置环境变量
docker run -d \
-e TZ=Asia/Shanghai \
my-app
在 Docker Compose 中:
environment:
TZ: Asia/Shanghai
十五、Docker 磁盘空间占满
问题现象
服务器磁盘空间不足:
df -h
发现 /var/lib/docker 占用很大。
原因分析
Docker 长期运行后会产生大量:
- 已停止容器;
- 未使用镜像;
- 构建缓存;
- 未使用网络;
- 未使用数据卷;
- 容器日志文件。
查看 Docker 占用
docker system df
清理无用资源
清理停止的容器、未使用网络、悬空镜像和构建缓存:
docker system prune
清理所有未使用镜像:
docker system prune -a
清理未使用数据卷:
docker volume prune
清理构建缓存:
docker builder prune
谨慎执行:
docker system prune -a --volumes
该命令会删除所有未使用镜像、容器、网络和数据卷,可能导致数据丢失。
十六、容器日志过大
问题现象
Docker 容器运行一段时间后,日志文件非常大,占满磁盘。
查看日志路径:
docker inspect 容器名 | grep LogPath
解决方案
配置 Docker 日志轮转。
编辑:
sudo vim /etc/docker/daemon.json
添加:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}
重启 Docker:
sudo systemctl restart docker
注意:该配置通常对新创建的容器生效,已有容器建议重建。
十七、容器权限不足
问题现象
容器内执行某些操作时报错:
Permission denied
常见于:
- 挂载宿主机目录;
- 写入文件;
- 访问设备;
- 执行脚本。
解决方案
1. 检查宿主机目录权限
ls -l /data/app
修改权限:
sudo chown -R 1000:1000 /data/app
或者:
sudo chmod -R 755 /data/app
2. 使用指定用户运行容器
docker run -u 1000:1000 my-app
3. 脚本缺少执行权限
chmod +x start.sh
Dockerfile 中可添加:
RUN chmod +x start.sh
十八、容器内无法解析域名
问题现象
容器内访问外部域名失败:
ping www.baidu.com
提示:
Temporary failure in name resolution
原因分析
DNS 配置异常,或者宿主机网络存在问题。
解决方案
1. 启动容器时指定 DNS
docker run --dns=8.8.8.8 --dns=114.114.114.114 my-app
2. 配置 Docker 全局 DNS
编辑:
sudo vim /etc/docker/daemon.json
写入:
{
"dns": ["8.8.8.8", "114.114.114.114"]
}
重启:
sudo systemctl restart docker
十九、Docker 常用排查命令汇总
1. 查看 Docker 信息
docker info
2. 查看镜像
docker images
3. 查看运行中容器
docker ps
4. 查看所有容器
docker ps -a
5. 查看容器日志
docker logs -f 容器名
6. 进入容器
docker exec -it 容器名 sh
或:
docker exec -it 容器名 bash
7. 查看容器详情
docker inspect 容器名
8. 查看容器资源占用
docker stats
9. 查看 Docker 网络
docker network ls
10. 查看数据卷
docker volume ls
二十、实战建议
1. 生产环境不要直接使用 latest 标签
不推荐:
image: mysql:latest
推荐:
image: mysql:8.0
原因是 latest 可能随时变化,导致部署结果不可控。
2. 配置健康检查
示例:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
健康检查可以帮助我们判断容器是否真正可用。
3. 敏感信息不要写死在 Dockerfile 中
不推荐:
ENV MYSQL_PASSWORD=123456
推荐使用:
- 环境变量;
.env文件;- Docker Secret;
- Kubernetes Secret;
- CI/CD 平台密钥管理。
4. 保持镜像最小化
镜像越小:
- 拉取越快;
- 启动越快;
- 安全风险越低;
- 部署效率越高。
5. 日志交给标准输出
容器化应用推荐将日志输出到标准输出和标准错误:
console.log("info message");
console.error("error message");
这样可以通过:
docker logs 容器名
统一查看。
总结
Docker 能显著提升应用交付和部署效率,但在实际使用过程中,也会遇到镜像拉取、容器启动、网络通信、数据持久化、权限、日志、磁盘空间等问题。
排查 Docker 问题时,可以遵循以下思路:
- 先看容器是否运行:
docker ps -a; - 再看日志:
docker logs; - 检查端口映射:
docker port; - 检查网络:
docker network inspect; - 检查挂载:
docker inspect; - 检查资源占用:
docker stats; - 最后检查 Dockerfile 和 Compose 配置。
只要掌握这些常见问题的原因和解决方式,Docker 的使用难度会明显降低。对于日常开发来说,推荐优先使用 Docker Compose 管理多容器项目;对于生产环境,则需要进一步结合 CI/CD、镜像仓库、日志系统、监控系统和容器编排平台进行统一管理。