docker —— 单机容器(构建和本地开发)
一句话定义
docker 是最早最普及的容器引擎和 CLI。K8s 1.24+ 已经移除 Docker 作 runtime(节点上是 containerd),但 docker 在本地构建镜像、本地开发、CI 构建这几个场景仍然不可替代。
典型场景
- 构建镜像:
docker build -t myapp:v1 . - 推镜像:
docker push registry/myapp:v1 - 本地跑容器测试:
docker run -p 8080:80 nginx - 进容器排错:
docker exec -it abc bash - 多阶段构建优化镜像大小
- 看本机所有镜像 / 容器:
docker images / docker ps
节点上 K8s 不用 docker——用 crictl 看节点容器。docker 主要在你的笔记本 / CI 机上。
装
# 一键脚本(开发机)
curl -fsSL https://get.docker.com | bash
# 或者按发行版
apt install -y docker.io docker-compose-v2
# macOS / Windows
# 装 Docker Desktop(自带 docker / compose / k8s 模式)
启动:
systemctl enable --now docker
docker version
把当前用户加 docker 组(避免每次 sudo):
usermod -aG docker $USER
# 重新登录生效
加 docker 组等于 root —— 不要随便给生产服务器用户 docker 组。
容器:基础动作
docker run nginx # 跑容器(前台)
docker run -d nginx # 后台
docker run -it ubuntu bash # 交互式
docker run -p 8080:80 nginx # 端口映射
docker run -v $(pwd):/app -w /app python:3.11 python script.py
# 挂载当前目录 + 设工作目录
docker run --rm alpine echo hi # 跑完自动删
docker run --name web nginx # 命名容器
docker run -e VAR=value alpine env # 环境变量
docker run --network host alpine ip a # 用宿主网络
# 看容器
docker ps # 跑着的
docker ps -a # 含已停止的
docker ps -aq # 只 ID
# 停 / 启 / 删
docker stop <container> # 停(SIGTERM)
docker kill <container> # SIGKILL
docker start <container> # 启动
docker restart <container>
docker rm <container> # 删(必须先停)
docker rm -f <container> # 强删(stop+rm)
# 进容器
docker exec -it <container> bash
docker exec <container> ls /
# 日志
docker logs <container>
docker logs -f --tail=100 <container>
# 详情
docker inspect <container>
docker inspect <container> | jq '.[0].State'
docker inspect --format='{{.NetworkSettings.IPAddress}}' <container>
镜像:基础动作
docker pull nginx:1.25 # 拉
docker push registry/myapp:v1 # 推
docker images # 列本地镜像
docker rmi <image> # 删
docker rmi $(docker images -q --filter "dangling=true") # 清没 tag 的镜像
docker image prune -a # 清所有没被引用的(慎用)
docker tag myapp:v1 myapp:latest # 重 tag
docker tag myapp:v1 registry.example.com/myapp:v1 # 加远程仓库前缀
docker save myapp:v1 -o myapp.tar # 导出 tarball
docker load -i myapp.tar # 导入
docker history myapp:v1 # 看每层是什么命令产生的
docker inspect myapp:v1 # 镜像元数据
docker build —— 构建镜像
最基础:
docker build -t myapp:v1 . # . 是 build context
docker build -t myapp:v1 -f Dockerfile.prod . # 指定 Dockerfile
docker build -t myapp:v1 --no-cache . # 不用 cache(强制全部 rebuild)
docker build -t myapp:v1 --build-arg VERSION=2.0 .
docker build -t myapp:v1 --target=production . # 多阶段构建选某 stage
docker build -t myapp:v1 --platform=linux/amd64 . # 指定架构(mac M1 必加)
Dockerfile 基础语法
FROM python:3.11-slim # 基础镜像
WORKDIR /app # 工作目录
COPY requirements.txt . # 拷文件
RUN pip install -r requirements.txt # 跑命令(生成新 layer)
COPY src/ ./src/
ENV PYTHONUNBUFFERED=1 # 环境变量
ARG VERSION=dev # 构建时变量
USER 1000 # 切非 root(**生产推荐**)
EXPOSE 8080 # 文档作用(不真开端口)
CMD ["python", "src/main.py"] # 默认命令
多阶段构建(镜像瘦身利器)
# Stage 1: builder
FROM golang:1.21 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /myapp ./cmd/myapp
# Stage 2: runtime
FROM gcr.io/distroless/static:nonroot
COPY /myapp /myapp
USER nonroot
ENTRYPOINT ["/myapp"]
跑 docker build 之后只保留 stage 2 的产物——最终镜像 5-30 MB,比单阶段 1GB+ 小得多。
生产 Go / Rust / Java 应用必用多阶段。
.dockerignore(很重要)
build 时 docker 把整个 build context 发给 daemon。不写 .dockerignore → 几 GB 的 node_modules / .git / .venv 都被发——慢,泄漏,镜像可能也大。
# .dockerignore
.git
.gitignore
node_modules
.venv
__pycache__
*.pyc
.env
*.log
.DS_Store
build/
dist/
最低限度。
镜像优化几个套路
1. 用小的 base image
FROM python:3.11-slim # 100 MB
# FROM python:3.11 # 900 MB
# FROM python:3.11-alpine # 50 MB(注意 alpine 用 musl 不是 glibc)
2. 合并 RUN
# ❌ 多层(每个 RUN 一层、不可清)
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean
# ✅ 单层 + 清缓存
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
3. 利用 layer 缓存
# ❌ 每次代码改都重新装依赖
COPY . .
RUN pip install -r requirements.txt
# ✅ 依赖变了才重装
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . . # 代码改也不影响上面缓存
频繁变的放后面、稳定的放前面。
4. 多阶段去掉构建工具
见上面 Go 例子。运行时不需要 gcc / make / node_modules / 测试文件。
docker network
docker network ls
docker network create mynet
docker run --network mynet --name db postgres
docker run --network mynet --name app myapp # 同 network 里 app 可以 host=db 访问 db
默认 bridge / host / none:
| 网络 | 含义 |
|---|---|
bridge | 默认;容器有自己的网络 |
host | 用宿主网络(端口直接暴露) |
none | 无网络 |
| 自定义 | 容器间用 DNS 名互相找 |
docker volume
docker volume create mydata
docker run -v mydata:/data alpine sh -c 'echo hello > /data/file'
docker run -v mydata:/data alpine cat /data/file # hello
docker volume ls
docker volume inspect mydata
docker volume rm mydata
docker volume prune # 清所有未引用的
bind mount 直接挂宿主目录
docker run -v /host/dir:/container/dir alpine
-v 第一个参数:
- 不含
/(如mydata) → 命名 volume - 含
/(如/host/dir) → bind mount
docker compose(多容器编排)
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- DB_HOST=db
depends_on:
- db
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: secret
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
用法:
docker compose up -d # 启动
docker compose logs -f # 看日志
docker compose ps
docker compose exec app bash
docker compose down # 停 + 删容器
docker compose down -v # 连 volume 也删
Docker Compose 是本地多服务开发首选。生产用 K8s。
排错 / 资源
docker stats # top 风格的实时资源
docker top <container> # 容器里的进程
docker system df # 磁盘占用
docker system prune -a --volumes # 清所有未用的(**核选项**)
docker info # 引擎信息
镜像层数过多
docker history myapp:v1
# 看每层、可以重写 Dockerfile 减少
容器卡住 / 不响应
docker logs <container> --tail=200
docker exec <container> ps aux
docker inspect <container> | jq '.[0].State'
docker kill <container> # SIGKILL(最后手段)
docker buildx —— 跨架构构建
M1 Mac / ARM 服务器需要构建 amd64+arm64:
docker buildx create --use --name multi-arch
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t registry/myapp:v1 \
--push .
K8s 集群有不同架构节点时必用 multi-arch 镜像。
私有 registry
docker login registry.example.com # 输用户名密码
docker tag myapp:v1 registry.example.com/myapp:v1
docker push registry.example.com/myapp:v1
凭证存 ~/.docker/config.json(base64 不是加密——小心)。
docker logout registry.example.com
K8s pull 私有镜像
kubectl create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username=user \
--docker-password=pass
# Pod spec 加:
# spec:
# imagePullSecrets:
# - name: regcred
常见踩坑
坑 1:M1 Mac 拉的镜像在 amd64 集群跑不起来
docker pull myapp:v1 # 默认拉宿主架构(arm64)
docker push ... # 推上去的是 arm64
# K8s 集群上 amd64 节点上跑:exec format error
修:用 buildx 多架构,或者明确指定:
docker build --platform=linux/amd64 -t myapp:v1 .
docker pull --platform=linux/amd64 myapp:v1
坑 2:build context 太大、build 慢
docker build -t myapp:v1 .
# Sending build context to Docker daemon 2.5GB
.dockerignore 没写好。node_modules / .git / .venv 都被发了。
坑 3:镜像越攒越大
docker system df
# RECLAIMABLE 30 GB
docker system prune -a --volumes # 清
CI / 开发机经常忘了清。定期跑 prune(每周 cron)。
坑 4:容器以 root 跑
FROM ubuntu
COPY app /
CMD ["/app"] # 默认 root
容器 root 在某些攻击场景能逃逸到宿主 root。
USER 1000 # 切非 root
K8s 里也可以 podSpec.securityContext.runAsUser=1000 强制。
坑 5:把 secret 放 ENV 或 ARG
ARG DB_PASSWORD # ❌ docker history 能看到
ENV DB_PASSWORD=$DB_PASSWORD # ❌ 容器 env 显示
docker history myapp:v1
# 显示 ARG DB_PASSWORD=secret
Build-time secret 用 BuildKit --mount=type=secret:
# syntax=docker/dockerfile:1
RUN cat /run/secrets/mysecret
docker build --secret id=mysecret,src=./secret.txt .
Runtime secret 用 K8s Secret + 环境变量 / volume。
坑 6:CMD 用 shell 形式而不是 exec 形式
CMD python app.py # shell 形式:实际跑 sh -c "python app.py"
# → 主进程是 sh、收不到 SIGTERM
CMD ["python", "app.py"] # exec 形式:直接跑 python
# → SIGTERM 能正常处理(K8s graceful shutdown 需要)
K8s graceful shutdown 要应用响应 SIGTERM。永远用 exec 形式。
坑 7:DOCKER_BUILDKIT 没开
docker build . # 老 builder 慢、缓存差
新版默认开 BuildKit,但老 Docker 不。开:
export DOCKER_BUILDKIT=1
docker build .
或者 /etc/docker/daemon.json:
{
"features": {
"buildkit": true
}
}
坑 8:用 latest tag
docker pull myapp:latest
# 每次拿到不同版本,复现问题难
生产永远固定 tag(v1.2.3、abc1234)。latest 适合临时测试。
坑 9:在 K8s 节点上误用 docker 命令
ssh m4 'docker ps'
# Cannot connect to the Docker daemon
K8s 1.24+ 节点没装 docker(用 containerd)。要看节点容器:crictl。
坑 10:把 docker.sock 挂进容器
docker run -v /var/run/docker.sock:/var/run/docker.sock alpine
容器里能控制宿主 docker daemon —— 等于宿主 root。除非你完全清楚为什么这么做,不要这样。
K8s 里类似的 CVE:容器里挂 /run/containerd/containerd.sock。
docker vs containerd vs podman
| 工具 | 用途 | K8s 场景 |
|---|---|---|
| docker | 本地开发 / 构建 | ❌ 节点不用 |
| containerd | K8s 节点 runtime | ✅ |
| podman | rootless 容器 / docker 替代 | 部分开发场景 |
| buildah | 专门构建(不需要 daemon) | CI 推荐 |
| kaniko | 在 K8s 里构建(不需要 docker) | CI on K8s |
K8s 节点用 crictl 操作 containerd。本地开发 docker。CI 构建用 docker / buildah / kaniko。