Docker 是一个开源的容器化平台,通过操作系统级虚拟化技术,将应用及其依赖打包到轻量级、可移植的容器中。与虚拟机不同,Docker 容器共享宿主机的操作系统内核,无需完整的 Guest OS,从而实现了秒级启动、极低资源开销和高效部署。
本文从 Docker 的核心概念出发,系统梳理镜像管理、容器生命周期、网络、存储、Compose 编排、生产环境最佳实践以及常见问题,帮助你从入门到熟练使用 Docker。
镜像是一个轻量级、不可变的可执行软件包,包含运行应用所需的一切——代码、运行时、系统工具、系统库和设置。镜像是分层的(Layer),每一层对应 Dockerfile 中的一条指令,并利用写时复制(Copy-on-Write)机制实现高效存储。
镜像分层示例:
FROM ubuntu:22.04 # 基础层 ~80MB
RUN apt-get update && \ # 系统依赖层
apt-get install -y python3
COPY app.py /app/ # 应用代码层
CMD ["python3", "/app/app.py"]
每一层只存储与上一层的差异,拉取镜像时也只需拉取本地不存在的层。
容器是镜像的运行实例。它拥有独立的文件系统、网络栈和进程空间,但共享宿主机内核。容器的本质是一个进程,只不过被 Namespace 和 Cgroups 隔离和约束。
Image 与 Container 的本质区别:
[registry/][namespace/]repository[:tag]# 拉取镜像
docker pull ubuntu:22.04
# 列出本地镜像
docker images
# 删除镜像
docker rmi <image-name>
# 构建镜像
docker build -t my-app:1.0 .
# 镜像打标签
docker tag my-app:1.0 my-registry.com/my-app:1.0
# 推送镜像到远程仓库
docker push my-registry.com/my-app:1.0
# 查看镜像分层历史
docker history my-app:1.0
优化策略:
ubuntu:22.04 → ubuntu:22.04-slim → alpine:3.18(从 ~80MB 降到 ~5MB)RUN 链式命令合并安装步骤# 构建阶段
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o server
# 运行阶段
FROM alpine:3.18
RUN apk add --no-cache ca-certificates tzdata
COPY --from=builder /app/server /server
CMD ["/server"]
apt-get clean && rm -rf /var/lib/apt/lists/*标签策略:
latest 标签用于生产部署1.2.3、1.2、11.2.3-a1b2c3d💡 经验总结:Image 版本号可以直接升级,前提是 Image 本身是无状态的,且数据格式兼容。但有状态的服务(如数据库)升级需谨慎处理数据迁移。
# 创建并启动容器
docker run -d --name my-app -p 8080:80 nginx:latest
# 列出运行中的容器
docker ps
# 列出所有容器(包括已停止的)
docker ps -a
# 停止容器
docker stop my-app
# 启动已停止的容器
docker start my-app
# 重启容器
docker restart my-app
# 进入运行中容器的交互 Shell
docker exec -it my-app /bin/bash
# 查看容器日志
docker logs -f my-app
# 查看容器资源使用
docker stats
# 删除容器
docker rm my-app
docker rm -f my-app # 强制删除(包括运行中的)
| 特性 | 无状态容器 | 有状态容器 |
|---|---|---|
| 数据存储 | 外部 Volume 或数据库 | 容器内本地存储 |
| 扩缩容 | 随意横向扩缩 | 需考虑数据一致性 |
| 故障恢复 | 直接销毁重建 | 需挂载原有数据 |
| 典型场景 | Web 服务、API 网关 | 数据库、消息队列 |
更多参考:Stateless vs Stateful Containers
Docker 提供五种网络模式:
| 模式 | 说明 | 适用场景 |
|---|---|---|
bridge |
默认模式,容器通过 Docker 网桥(docker0)通信 | 单机多容器通信 |
host |
容器直接使用宿主机网络栈 | 性能敏感型应用 |
none |
无网络 | 安全隔离 |
overlay |
跨主机的容器通信(Swarm 模式) | 多机集群 |
macvlan |
为容器分配 MAC 地址 | 需要直连物理网络 |
# 创建网络
docker network create --driver bridge my-net
# 在自定义网络中启动容器
docker run -d --name app1 --network my-net nginx
docker run -d --name app2 --network my-net nginx
# 在自定义网络中,容器可以直接通过名称互相访问
docker exec app1 ping app2
# 查看网络
docker network ls
docker network inspect my-net
💡 最佳实践:使用自定义网络而非默认
bridge,可以启用容器 DNS 解析(通过容器名互访),并实现网络隔离。
# 宿主机 8080 → 容器 80
docker run -p 8080:80 nginx
# 随机端口
docker run -P nginx # 将容器暴露的端口映射到随机宿主机端口
将宿主机目录或文件挂载到容器中:
docker run -v /host/path:/container/path nginx
docker run --mount type=bind,source=/host/path,target=/container/path nginx
由 Docker 管理的数据存储区域(/var/lib/docker/volumes/):
# 创建数据卷
docker volume create my-data
# 挂载数据卷
docker run -v my-data:/data nginx
docker run --mount type=volume,source=my-data,target=/data nginx
# 查看数据卷
docker volume ls
docker volume inspect my-data
存储在宿主机内存中,容器停止后数据丢失:
docker run --tmpfs /tmp nginx
docker run --mount type=tmpfs,destination=/app/cache nginx
⚠️ Named Volume 不支持自定义存储位置。这是 Docker 的设计决定。
早在 2016 年就有人提出过允许 Named Volume 指定宿主机路径的功能请求[feature] Allow for named volumes to specify host mount point,但被 Docker 官方驳回。其核心理由如下:
"The 'storage location' of the volume should be considered an implementation detail, and although that location is not a secret (it's in /var/lib/docker/volumes/<name-of-volume>), these paths are not a 'public API', and thus can change in future. Interacting with those files directly from the host 'works', but doing so could potentially interfere with the docker daemon (e.g. lead to a 'filesystem in use' if another process is using those files, which can cause docker failing to remove a volume)."
要点总结:
/var/lib/docker/volumes/<volume-name>/_data替代方案:
docker cp 在宿主机和容器间传输文件# 创建未使用的数据卷
docker volume create --name app-data
# 使用数据卷的容器停止后,数据卷仍然存在
docker run -d --name db -v app-data:/var/lib/mysql mysql:8
docker stop db
docker rm db
# app-data 数据卷仍然保留
# 手动清理未使用的数据卷
docker volume prune
# 删除特定数据卷
docker volume rm app-data
💡 经验:在同一系统上生成的 Docker Volume,可以直接在另一个系统上使用吗?可以,只要将 Volume 数据复制到目标系统的
/var/lib/docker/volumes/目录下即可。但在生产环境中,更推荐使用数据备份/恢复工具或分布式存储方案。
Docker Compose 通过 YAML 文件定义多容器应用:
version: "3.8"
services:
app:
build: .
ports:
- "8080:8080"
environment:
- DB_HOST=db
- DB_PORT=3306
depends_on:
- db
volumes:
- ./app:/app
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: myapp
volumes:
- db-data:/var/lib/mysql
volumes:
db-data:
# 启动服务
docker compose up -d
# 查看日志
docker compose logs -f
# 重建特定服务
docker compose up -d --build app
# 停止并清理
docker compose down
docker compose down -v # 同时删除数据卷
# 查看服务状态
docker compose ps
# 在服务中执行命令
docker compose exec app /bin/bash
# 重新加载配置文件
docker compose restart
# docker-compose.override.yml(开发环境,自动合并)
services:
app:
volumes:
- ./src:/app/src
environment:
- DEBUG=true
# docker-compose.prod.yml(生产环境)
# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# 选择合适的基础镜像
FROM node:20-alpine AS builder
# 设置工作目录
WORKDIR /app
# 先复制依赖声明文件(利用层缓存)
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
# 再复制源码(源码变更不影响依赖缓存层)
COPY . .
# 构建
RUN yarn build
# --- 多阶段构建 ---
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
FROM、COPY package.json、RUN install)RUN 命令,但不要过度(可读性与性能平衡).dockerignore:.git/
node_modules/
*.md
.gitignore
.env
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:8080/health || exit 1
docker run --memory=512m --cpus=0.5 \
--ulimit nofile=65536:65536 \
--security-opt=no-new-privileges \
my-app
docker run --read-only --tmpfs /tmp nginxdocker run --log-opt max-size=10m --log-opt max-file=3 my-app
HEALTHCHECKcAdvisor + Prometheus + Grafana 监控容器资源docker run --restart=always my-app # 总是重启
docker run --restart=unless-stopped my-app # 除非手动停止
docker run --restart=on-failure:5 my-app # 失败时重启,最多5次
# GitLab CI 示例
build:
stage: build
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
- docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA $CI_REGISTRY_IMAGE:latest
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
- docker push $CI_REGISTRY_IMAGE:latest
# 查看容器日志
docker logs <container-id>
# 检查容器状态详情
docker inspect <container-id>
# 查看退出码
docker ps -a | grep <container-name>
# 以不同 Entrypoint 启动用于调试
docker run -it --entrypoint /bin/sh <image-name>
# 清理未使用的资源
docker system prune -a --volumes
# 查看磁盘使用情况
docker system df
# 查看各镜像和容器的磁盘占用
docker system df -v
# 测试容器间连通性
docker exec app1 ping app2
# 查看容器网络配置
docker inspect <container-id> | jq '.[].NetworkSettings'
# DNS 解析问题常见原因
# 1. 容器不在同一自定义网络
# 2. 容器名拼写错误
# 3. 服务尚未就绪(需添加 depends_on + wait-for-it)
# 容器内文件权限问题
# 在容器中创建的文件的属主通常是 root(UID 0)
# 解决方案:在 Dockerfile 中匹配宿主机 UID
RUN useradd -u 1000 appuser
USER appuser
推荐文章:如需进一步了解 Docker 在 Kubernetes 中的使用,请参考 kubectl 命令 和 Docker使用安全基线。