Docker 入门:镜像、容器、网络与日常排障
Docker 已经成为现代软件交付的标准。本文从安装、Dockerfile 最佳实践、镜像构建缓存、网络/存储模型、Compose 编排,到线上 10+ 常见报错、性能分析逐一整理。
Docker 入门:镜像、容器、网络与日常排障
Docker 把程序 + 依赖 + 运行环境打包进一个”镜像”,在任何 Linux / macOS / Windows 机器上拉下来就能跑。它解决了”我机器上能跑啊”的千年难题,也是整个云原生世界的基础。
二、核心概念速览
| 概念 | 一句话说明 |
|---|---|
| Image(镜像) | 只读的”模板”,比如 nginx:1.27-alpine,由一层层文件系统叠加组成 |
| Container(容器) | 镜像”跑起来”的实例,基于写时复制(Copy-on-Write)有独立可写层 + 进程空间 |
| Registry | 镜像仓库:Docker Hub、阿里云 ACR、Harbor、GitHub GHCR |
| Dockerfile | 描述如何一步步构建镜像的文本文件 |
| Volume | 容器数据持久化方案,绕开 Union FS,直接写宿主机 |
| Bind Mount | 把宿主机某目录直接挂到容器里 |
| Network | bridge / host / none / container:xxx / 用户自定义 bridge |
| Compose | 用 YAML 一次性编排启动多个容器(Web + DB + Redis…) |
| BuildKit | Docker 23.0+ 默认构建引擎,比老 builder 快、支持缓存导出、多阶段更聪明 |
| Layer(层) | Dockerfile 每条指令生成一层,完全相同的指令会命中缓存,这就是优化构建速度的根 |
三、安装
2.1 macOS / Windows(桌面版)
直接装 Docker Desktop:https://www.docker.com/products/docker-desktop/
自带 Docker Engine + CLI + Compose v2 + K8s 单节点 + GUI
macOS 用 HyperKit / Apple Virtualization;Win 用 WSL2
检查:
bash docker version # 看 Client / Engine 版本 docker compose version # 确认 Compose v2 docker info | head -30
2.2 Linux(Ubuntu / Debian 生产服务器)
`bash
1) 卸载旧版本
for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do sudo apt remove -y $pkg done
2) 加官方源
sudo apt update sudo apt install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg —dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg echo
“deb [arch=$(dpkg —print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu
$(. /etc/os-release && echo $VERSION_CODENAME) stable” | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
3) 安装
sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
4) 让非 root 也能跑 docker(重新登录生效)
sudo usermod -aG docker $USER
5) (可选) 国内镜像加速
sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json << ‘EOF’ { “registry-mirrors”: [ “https://docker.1ms.run”, “https://docker.m.daocloud.io” ], “log-driver”: “json-file”, “log-opts”: { “max-size”: “100m”, “max-file”: “5” }, “storage-driver”: “overlay2” } EOF sudo systemctl daemon-reload sudo systemctl enable —now docker
验证
docker run —rm hello-world `
CentOS / Rocky / RHEL 换源用 download.docker.com/linux/centos 即可,步骤类似。
四、第一个容器 & 常用命令
`bash
拉镜像
docker pull nginx:1.27-alpine
看本地镜像
docker images
启动容器(-d 后台 -p 端口映射 —name 起名 -v 挂载)
docker run -d —name my-nginx -p 8080:80
-v /data/html:/usr/share/nginx/html:ro
—restart=unless-stopped
nginx:1.27-alpine
看运行中的容器
docker ps
看所有(包括已停止)
docker ps -a
日志
docker logs my-nginx docker logs -f —tail 200 my-nginx # 实时跟 最新200行
进容器(推荐用 exec -it,别用 attach)
docker exec -it my-nginx /bin/sh # alpine 没 bash
停止 / 启动 / 删除
docker stop my-nginx docker start my-nginx docker rm my-nginx # 运行中的要加 -f docker rm -f $(docker ps -aq) # 危险:删所有容器
磁盘占用大起底
docker system df docker system prune -a # 清理停止的容器、无用镜像、虚悬镜像 docker builder prune —all # 清构建缓存 docker volume prune # 清没被使用的 volume `
五、Dockerfile 最佳实践
4.1 模板:Node.js 多阶段构建(经典案例)
`dockerfile
---------------- 阶段 1:构建 ----------------
FROM node:20-alpine AS builder
WORKDIR /app
先只拷贝依赖描述文件,充分利用缓存
COPY package*.json ./
—include=dev 确保装齐 devDependencies(用于 TS 编译/前端构建);npm 7+ 旧写法 —only=production=false 已废弃
RUN npm ci —include=dev —no-audit —no-fund
再拷贝源码(源码经常变,放后面避免每次装依赖)
COPY . .
前端构建 / 编译 TS 等
RUN npm run build
---------------- 阶段 2:运行 ----------------
FROM node:20-alpine AS runner
ENV NODE_ENV=production
PORT=3000
不要用 root 跑容器
RUN addgroup -S nodeapp && adduser -S nodeapp -G nodeapp WORKDIR /app RUN chown -R nodeapp:nodeapp /app USER nodeapp
从 builder 阶段拷贝需要的东西,dev 依赖全丢掉
COPY —from=builder —chown=nodeapp:nodeapp /app/dist ./dist COPY —from=builder —chown=nodeapp:nodeapp /app/package*.json ./ RUN npm ci —omit=dev —no-audit —no-fund
EXPOSE 3000
健康检查(可选但推荐)
HEALTHCHECK —interval=30s —timeout=5s —retries=3
CMD wget -qO- http://127.0.0.1:3000/healthz || exit 1
用 exec 形式(JSON 数组),保证信号正确传递给 node
CMD [“node”, “dist/server.js”] `
4.2 构建镜像 & 运行
bash docker build -t myapp:v1.0.0 . docker run -d -p 3000:3000 --name myapp myapp:v1.0.0
4.3 Dockerfile 优化规则
| 原则 | 怎么做 | 原因 |
|---|---|---|
| 命中缓存 | 变更频率低的指令放上面(apt install / npm ci / pip install) | 一层没改,下面的缓存就都命中 |
| 多阶段构建 | 构建工具全放 builder,最终 runner 只含运行时 | 镜像体积从 1GB 能减到 100MB |
| 用 slim/alpine 基础镜像 | node:20-alpine > node:20 > node:20-bookworm | 小 + 攻击面小 |
| 不要用 root | useradd + USER xxx | 容器被攻破也拿不到宿主机 root |
| 合并 RUN 指令 | RUN apt update && apt install -y ... && rm -rf /var/lib/apt/lists/* | 每层分开存,写在一个 RUN 里才会把缓存包清干净 |
| 忽略无关文件 | 写 .dockerignore,排除 node_modules、.git、日志等 | 构建上下文(build context)越小传得越快 |
| 明确版本 | node:20.17.0-alpine3.20 而不是 node:latest | latest 指不定今天是谁,构建不可复现 |
| 敏感信息 | 不要用 ENV 传密码!用构建时 --secret 或运行时环境变量 / Secret | docker history 能看到 ENV 的明文 |
一个好的 .dockerignore:
.git .gitignore node_modules npm-debug.log Dockerfile .dockerignore README.md .env.* dist coverage .idea .vscode
六、网络模型
| 驱动 | 说明 | 典型场景 |
|---|---|---|
| bridge(默认) | 容器通过一个网桥(docker0)出网,互相通过 IP 访问 | 单机多容器互相访问 |
| host | 容器和宿主机共享 Network Namespace,端口不冲突就行 | 想让容器网络性能跟宿主机一样(比如压测、Nginx 大流量) |
| none | 没有网卡 | 安全隔离(容器完全断网) |
| container:id | 跟另一个容器共享网络栈 | Sidecar 模式(比如 envoy 代理 app 的流量) |
| 自定义 bridge | docker network create mynet,容器用 --net mynet 加入 | 推荐:自带 DNS,可以用容器名直接互联 |
自定义网络示例:
`bash docker network create mynet -d bridge docker run -d —name db —net mynet -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0 docker run -d —name web —net mynet -p 8080:80 nginx
web 里直接 ping / 连 “db:3306” 就行,不用查 IP
`
七、持久化:Volume vs Bind Mount
| 方式 | 命令 | 适用场景 |
|---|---|---|
| Volume(推荐) | -v mydata:/var/lib/mysql | 数据库、独立于容器生命周期的数据。Docker 管理,portable |
| Bind Mount | -v /srv/mysql:/var/lib/mysql | 宿主机上现成的目录要给容器用(开发时挂源码、配置文件) |
备份 volume:
`bash
用一个临时容器把 volume 打成 tar
docker run —rm -v mydata:/data -v $(pwd):/backup alpine
tar czf /backup/mydata-$(date +%Y%m%d).tar.gz -C /data . `
八、Docker Compose
compose.yml:
`yaml services: web: build: . image: myapp:v1 ports: - “8080:3000” environment: NODE_ENV: production DB_HOST: db DB_PORT: 3306 DB_PASSWORD: ${DB_PASSWORD} # 从 .env 文件或环境变量读 depends_on: db: condition: service_healthy redis: condition: service_started restart: unless-stopped healthcheck: test: [“CMD”, “wget”, “-qO-”, “http://127.0.0.1:3000/healthz”] interval: 30s retries: 3
db: image: mysql:8.0 volumes: - dbdata:/var/lib/mysql - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro environment: MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} healthcheck: test: [“CMD”, “mysqladmin”, “ping”, “-uroot”, “-p${DB_PASSWORD}”] interval: 10s timeout: 5s retries: 10 restart: unless-stopped
redis: image: redis:7-alpine command: redis-server —appendonly yes volumes: - redisdata:/data restart: unless-stopped
volumes: dbdata: redisdata: `
运行:
`bash
启动(-d 后台)
docker compose up -d
看日志
docker compose logs -f web
扩缩容(只能扩无状态服务)
docker compose up -d —scale web=3
停 + 删容器
docker compose down
连同 volume 一起删(注意数据会丢)
docker compose down -v `
九、日常排障手册
8.1 调试工具箱
| 需求 | 命令 |
|---|---|
| 容器起不来,到底在报什么 | docker ps -a 找到 ID → docker logs <ID>;看之前的日志不要只看最后 |
| 容器内进程 / 端口 / 文件 | docker exec -it <ID> sh + ps auxf / ss -lntp / du -sh /(镜像里没这些命令的话用 nsenter 或者 docker debug) |
| 镜像多大,哪层肥 | docker images;docker history myapp:v1 --no-trunc;更推荐 dive 工具 |
| 容器网络不通 | 1. docker inspect <ID> | jq '.[0].NetworkSettings.Networks' 看 IP/网关2. docker run --rm --net container:<ID> nicolaka/netshoot ss -lntp / ping / curl 工具容器3. 宿主机 iptables FORWARD 链是否 DROP |
| 磁盘莫名满 | docker system df → docker system prune -a → 看 overlay2 哪层大 → du -sh /var/lib/docker/overlay2/* | sort -hr | head |
| 退出码是什么意思 | docker inspect <ID> --format '{{.State.ExitCode}}',137=OOMKilled,143=SIGTERM,1=应用自身报错 |
| OOM Killed | dmesg | grep -i oom;docker inspect ... MemoryLimit 设小了;--memory=1g 调大或优化程序 |
| 配置文件改了容器里没生效 | bind mount 的文件如果宿主用 mv 覆盖(比如 sed -i),inode 会变。要么重启容器,要么 sed --in-place=... 改同一个文件 |
| 宿主机访问容器端口不通 | ① docker port <container> 端口映射有没有;② 防火墙 iptables/firewalld;③ SELinux;④ 容器里程序是不是只绑了 127.0.0.1(要绑 0.0.0.0) |
8.2 常见报错对照表
| 报错关键字 | 根因 / 排查 |
|---|---|
permission denied while trying to connect to the Docker daemon socket at unix:///... | 当前用户不在 docker 组。sudo usermod -aG docker $USER + 重新登录(或 newgrp docker) |
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? | dockerd 没起来。sudo systemctl start docker;检查 daemon.json 语法错误 |
Error response from daemon: pull access denied for xxx, repository does not exist or may require 'docker login' | 私有镜像要先 docker login <registry>,或者镜像名写错了 |
Error: No such container: xxx | 容器名 / ID 写错,或者已经 rm 了。docker ps -a 看看 |
port is already allocated | 宿主机端口被占了。ss -lntp | grep <PORT> 查谁占了,或者改映射 |
ERROR: failed to solve: failed to compute cache key: failed to calculate checksum of ref ...: "/xxx": not found | Dockerfile 里 COPY xxx yyy 的 xxx 在 build context 里不存在 / 被 .dockerignore 排除了 |
COPY failed: file not found in build context or excluded by .dockerignore | 同上,检查路径 / .dockerignore |
exec /bin/sh: exec format error | 平台架构不匹配:Mac M 系列(arm64)构建的镜像跑到 Linux x86_64 上了。用 docker buildx build --platform linux/amd64 . 跨平台构建 |
Temporary failure resolving 'deb.debian.org' (RUN apt update) | 容器内 DNS 不通。daemon.json 加 "dns": ["223.5.5.5","8.8.8.8"];或宿主机 /etc/resolv.conf 配置问题 |
OCI runtime create failed: container_linux.go:380: starting container process caused: exec: "xxx": executable file not found in $PATH | CMD 或 ENTRYPOINT 里写的程序文件不存在 / 路径错 / 没可执行权限 |
no space left on device | 磁盘 / inode 满。docker system prune -a --volumes;检查 overlay2 大小;docker builder prune |
HealthCheck: unhealthy | docker inspect <ID> 里 State.Health.Log[] 看最近一次命令真实输出和退出码 |
depends_on 服务起来但应用还没 ready | depends_on 默认只等”容器启动”,不等”应用可用”。用 condition: service_healthy + 给依赖服务加 healthcheck |
8.3 性能分析:容器卡 / 慢
docker stats→ 先看 CPU% / MEM 使用率 / NET I/O / DISK I/O 的实时排行某容器居高不下 →
docker top <ID>看里面 PID定位到进程号后,可以:
宿主机
pidstat -h -u -p <PID> 1(CPU)strace -c -p <PID>(系统调用)perf top -p <PID>(热点函数,要符号表)
网络慢 →
docker run --rm --net host nicolaka/netshoot iperf3 .../mtr测带宽/丢包
8.4 构建慢怎么办
使用 BuildKit(默认了,但 CI 里确认
DOCKER_BUILDKIT=1)RUN —mount=type=cache,target=/root/.npm:把包管理器缓存持久化
COPY package.json先于COPY .Registry Mirror:国内源拉基础镜像
多平台并行构建:
buildx build --platform linux/amd64,linux/arm64 -t repo/image --push .CI 里导出/导入缓存:
--cache-from type=gha --cache-to type=gha(GitHub Actions)
十、安全加固清单
不要用
:latest,明确版本号不要以 root 运行,Dockerfile 里
USER nonrootdockerd只监听 Unix socket,不要绑定tcp:2375(裸 TCP 约等于给全世界 root 权限);一定要远程管理就用 TLS:2376敏感变量别写进
ENV/ 镜像层,用:运行时
--env-file /etc/default/myapp.env(文件权限0600)Docker Secrets(Swarm)或 HashiCorp Vault / 云 KMS
开启内容信任(Notary / Cosign):
export DOCKER_CONTENT_TRUST=1定期镜像扫描:Trivy / Grype 扫 CVE:
trivy image myapp:v1镜像精简 + 只读根文件系统:
--read-only+tmpfs给需要写的路径
参考资料
Docker 官方文档:https://docs.docker.com/
Dockerfile 最佳实践:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
Trivy 镜像安全扫描:https://github.com/aquasecurity/trivy
Dive(镜像层分析):https://github.com/wagoodman/dive
netshoot(网络排障瑞士军刀镜像):https://github.com/nicolaka/netshoot