DevOps ·

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把宿主机某目录直接挂到容器里
Networkbridge / host / none / container:xxx / 用户自定义 bridge
Compose用 YAML 一次性编排启动多个容器(Web + DB + Redis…)
BuildKitDocker 23.0+ 默认构建引擎,比老 builder 快、支持缓存导出、多阶段更聪明
Layer(层)Dockerfile 每条指令生成一层,完全相同的指令会命中缓存,这就是优化构建速度的根

二、安装

2.1 macOS / Windows(桌面版)

直接装 Docker Desktophttps://www.docker.com/products/docker-desktop/

  • 自带 Docker Engine + CLI + Compose v2 + K8s 单节点 + GUI
  • macOS 用 HyperKit / Apple Virtualization;Win 用 WSL2

检查:

docker version           # 看 Client / Engine 版本
docker compose version   # 确认 Compose v2
docker info | head -30

2.2 Linux(Ubuntu / Debian 生产服务器)

# 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 即可,步骤类似。

三、第一个容器 & 常用命令

# 拉镜像
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 多阶段构建(经典案例)

# ---------------- 阶段 1:构建 ----------------
FROM node:20-alpine AS builder

WORKDIR /app

# 先只拷贝依赖描述文件,充分利用缓存
COPY package*.json ./
RUN npm ci --only=production=false --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 构建镜像 & 运行

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小 + 攻击面小
不要用 rootuseradd + 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:latestlatest 指不定今天是谁,构建不可复现
敏感信息不要用 ENV 传密码!用构建时 --secret 或运行时环境变量 / Secretdocker 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 的流量)
自定义 bridgedocker network create mynet,容器用 --net mynet 加入推荐:自带 DNS,可以用容器名直接互联

自定义网络示例:

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:

# 用一个临时容器把 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

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:

运行:

# 启动(-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 imagesdocker 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 dfdocker 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 Killeddmesg | grep -i oomdocker 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 foundDockerfile 里 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 $PATHCMDENTRYPOINT 里写的程序文件不存在 / 路径错 / 没可执行权限
no space left on device磁盘 / inode 满。docker system prune -a --volumes;检查 overlay2 大小;docker builder prune
HealthCheck: unhealthydocker inspect <ID>State.Health.Log[] 看最近一次命令真实输出和退出码
depends_on 服务起来但应用还没 readydepends_on 默认只等”容器启动”,不等”应用可用”。用 condition: service_healthy + 给依赖服务加 healthcheck

8.3 性能分析:容器卡 / 慢

  1. docker stats → 先看 CPU% / MEM 使用率 / NET I/O / DISK I/O 的实时排行
  2. 某容器居高不下 → docker top <ID> 看里面 PID
  3. 定位到进程号后,可以:
    • 宿主机 pidstat -h -u -p <PID> 1(CPU)
    • strace -c -p <PID>(系统调用)
    • perf top -p <PID>(热点函数,要符号表)
  4. 网络慢 → 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)

九、安全加固清单

  1. 不要用 :latest,明确版本号
  2. 不要以 root 运行,Dockerfile 里 USER nonroot
  3. dockerd 只监听 Unix socket,不要绑定 tcp:2375(裸 TCP 约等于给全世界 root 权限);一定要远程管理就用 TLS:2376
  4. 敏感变量别写进 ENV / 镜像层,用:
    • 运行时 --env-file /etc/default/myapp.env(文件权限 0600
    • Docker Secrets(Swarm)或 HashiCorp Vault / 云 KMS
  5. 开启内容信任(Notary / Cosign)export DOCKER_CONTENT_TRUST=1
  6. 定期镜像扫描:Trivy / Grype 扫 CVE:trivy image myapp:v1
  7. 镜像精简 + 只读根文件系统--read-only + tmpfs 给需要写的路径

参考资料

问问 AI