📘 第07课:Dockerfile基础

基础 学会编写Dockerfile,将应用打包成可复现的Docker镜像

📝 什么是Dockerfile?

Dockerfile是一个纯文本文件,包含构建Docker镜像所需的全部指令。它是基础设施即代码(IaC)在容器领域的体现——镜像的构建过程完全可追踪、可复现。

Dockerfile → Image → Container 流程: Dockerfile Docker Image Container ┌──────────────┐ docker build ┌──────────────┐ docker run ┌──────────────┐ │ FROM node:20 │ ──────────► │ Layer 1: OS │ ──────────► │ 可写层 │ │ WORKDIR /app │ │ Layer 2: Deps│ │ 运行时数据 │ │ COPY . . │ │ Layer 3: App │ │ │ │ RUN npm build│ │ Layer 4: Bld │ │ PID 1: node │ │ CMD ["node"] │ │ Meta: CMD/ENV│ │ IP: 172... │ └──────────────┘ └──────────────┘ └──────────────┘ 源码级别 只读模板 运行实例

📚 Dockerfile指令详解

FROM — 基础镜像

# 必须是第一条指令(除ARG外)✅
FROM node:20-alpine

# 多阶段构建中使用多次
FROM node:20-alpine AS builder
# ...构建阶段

FROM node:20-alpine AS runtime
# ...运行阶段

# 使用精简镜像
FROM gcr.io/distroless/nodejs20-debian12

WORKDIR — 工作目录

# 设置工作目录(目录不存在会自动创建)✅
WORKDIR /app

# 后续的RUN/CMD/ENTRYPOINT都在此目录执行
WORKDIR /app
COPY package.json .        # 复制到/app/package.json
RUN npm install             # 在/app下执行
💡 WORKDIR vs RUN cd:永远用WORKDIRRUN cd /app && npm install只在那一层有效,下一层又会回到根目录。WORKDIR是持久的,影响后续所有指令。

COPY 与 ADD — 复制文件

# COPY:基本复制(推荐)✅
COPY package.json package-lock.json ./
COPY src/ ./src/
COPY *.conf /etc/app/

# ADD:额外支持URL下载和自动解压
ADD https://example.com/app.tar.gz /opt/   # 自动下载
ADD app.tar.gz /opt/                        # 自动解压tar

# COPY --chown:复制并设置所有权
COPY --chown=node:node . .

# COPY --from:从其他阶段复制(多阶段构建)
COPY --from=builder /app/dist ./dist
特性COPYADD
基本复制
URL下载
自动解压tar
推荐程度⭐⭐⭐⭐(仅用于解压场景)

RUN — 执行命令

# shell格式(通过/bin/sh -c执行)
RUN apt-get update && apt-get install -y curl

# exec格式(直接执行)
RUN ["apt-get", "install", "-y", "curl"]

# 最佳实践:合并RUN减少层数 ✅
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
      curl \
      git \
      vim && \
    rm -rf /var/lib/apt/lists/*

# 注意:每层独立,下行清理不会缩减上层数据!
# ❌ 错误示范:
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*    # 这只删除了本层,上两层的数据还在!

CMD 与 ENTRYPOINT

# CMD:容器默认命令(可被docker run参数覆盖)
CMD ["node", "server.js"]          # exec格式(推荐)
CMD node server.js                  # shell格式

# ENTRYPOINT:容器入口点(不会被覆盖,参数会追加)
ENTRYPOINT ["node"]
CMD ["server.js"]                   # 默认参数

# docker run myapp → 执行: node server.js
# docker run myapp worker.js → 执行: node worker.js
组合docker run myappdocker run myapp arg
只有CMD执行CMDarg替换CMD
只有ENTRYPOINT执行ENTRYPOINTarg追加到ENTRYPOINT
ENTRYPOINT + CMDCMD作为默认参数arg替换CMD参数

ENV 与 ARG

# ENV:运行时环境变量(持久到容器)✅
ENV NODE_ENV=production
ENV APP_PORT=3000

# ARG:构建时变量(只在构建期有效)✅
ARG VERSION=1.0.0
RUN echo "Building version ${VERSION}"

# ARG配合ENV使用
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-alpine

# 运行时覆盖ENV
docker run -e NODE_ENV=staging my-app

EXPOSE 与 VOLUME

# EXPOSE:声明端口(文档作用,不实际映射)
EXPOSE 3000
EXPOSE 80/tcp 443/tcp

# VOLUME:声明数据卷目录
VOLUME ["/data"]
VOLUME /var/lib/postgresql/data

HEALTHCHECK — 健康检查

# 定义健康检查 ✅
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

# 禁用健康检查
HEALTHCHECK NONE

USER — 用户切换

# 创建并切换到非root用户 ✅
RUN addgroup -g 1001 appgroup && \
    adduser -u 1001 -G appgroup -s /bin/sh -D appuser
USER appuser

# 使用已有用户
USER node   # node:alpine镜像自带node用户

🧪 实战:构建Node.js应用镜像

# 项目结构
my-app/
├── Dockerfile
├── package.json
├── src/
│   └── index.js
└── .dockerignore

# Dockerfile ✅
FROM node:20-alpine

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci --only=production

COPY src/ ./src/

ENV NODE_ENV=production
EXPOSE 3000

USER node

HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

CMD ["node", "src/index.js"]

# .dockerignore ✅
node_modules
npm-debug.log
.git
.env
Dockerfile

# 构建镜像 ✅
docker build -t my-app:v1.0 .

# 运行容器 ✅
docker run -d -p 3000:3000 --name my-app my-app:v1.0

# 查看健康状态
docker inspect my-app --format='{{.State.Health.Status}}'

🔧 构建命令详解

# 基本构建 ✅
docker build -t my-app:v1.0 .

# 指定Dockerfile路径
docker build -f Dockerfile.prod -t my-app:v1.0 .

# 传入构建参数 ✅
docker build --build-arg VERSION=2.0.0 -t my-app:v2.0 .

# 不使用缓存
docker build --no-cache -t my-app:v1.0 .

# 多平台构建
docker buildx build --platform linux/amd64,linux/arm64 -t my-app:v1.0 .

# 查看构建历史
docker history my-app:v1.0

❓ 常见问题

❓ .dockerignore有什么用? 类似.gitignore,指定构建上下文中要排除的文件。最重要的排除node_modules——如果包含会导致:①构建上下文巨大 ②COPY . .会覆盖npm install的结果 ③可能泄露.env等敏感文件。
❓ CMD和ENTRYPOINT到底用哪个? 大多数场景只用CMD。需要固定入口程序时用ENTRYPOINT。典型模式:ENTRYPOINT ["node"] + CMD ["server.js"],这样docker run my-app worker.js可以灵活改变参数。
❓ 为什么RUN清理没用? Docker镜像是分层存储的。每一层是前一层的差异。如果RUN apt-get install写入了100MB,然后下一层RUN rm删除了80MB,最终镜像还是多了100MB——因为删除只是在新层"标记删除",旧层数据还在。解决方案:在同一个RUN指令中安装和清理。

🏆 第07课成就

下一课:学习多阶段构建——让镜像更小更安全!

📚 深度补充:Dockerfile基础进阶要点

【容器基础阶段】生产环境注意事项

要点说明最佳实践
资源规划根据业务负载合理分配CPU/内存先压测再上线,设置requests和limits
监控告警设置关键指标阈值和告警规则Prometheus + AlertManager,5分钟P99延迟告警
备份策略定期备份关键数据和配置自动化备份脚本 + 异地存储 + 定期恢复演练
灰度发布新版本逐步放量降低风险金丝雀发布5%→20%→50%→100%
回滚预案部署前确认回滚方案和步骤保留前一版本镜像,数据库迁移向前兼容
文档更新配置变更必须同步更新文档GitOps管理配置,变更即文档
安全基线遵循CIS Docker Benchmark非root运行、只读FS、最小能力
日志规范结构化日志,统一格式JSON格式日志,包含traceId

常见误区与避坑指南

  1. 过度配置:不是所有服务都需要高可用,根据实际需求选择架构复杂度,避免过度工程
  2. 忽略日志:日志是排障的关键,确保日志格式统一、级别合理、采集完整
  3. 资源超卖:容器资源限制不是摆设,超卖会导致性能下降甚至OOM Kill
  4. 安全忽视:默认配置不等于安全配置,生产环境必须加固(非root、只读FS、最小能力)
  5. 监控缺失:没有监控等于盲飞,至少要有基础的健康检查和资源监控
  6. 手动运维:能自动化的绝不手动,手动操作容易出错且不可追溯
  7. 忽略网络策略:默认所有容器互通不安全,应按最小权限原则配置网络

进阶阅读与参考

Docker命令速查卡

# 容器生命周期
docker create/start/stop/restart/rm/pause/unpause
docker logs/top/stats/inspect/exec diff

# 镜像构建
docker build/pull/push/tag/rmi/images/history
docker save/load/import/manifest

# 网络与存储
docker network create/ls/inspect/connect/disconnect/rm/prune
docker volume create/ls/inspect/rm/prune

# Docker Compose
docker compose up/down/ps/logs/build/exec
docker compose config/stop/start/scale/top/cp

# 系统维护
docker system df/prune/info
docker builder prune
docker container/prune/image prune

# K8s常用命令
kubectl get/describe/logs/exec/apply/delete
kubectl rollout status/undo/history
kubectl scale/autoscale/set

本阶段知识脉络

容器基础阶段知识体系: 核心概念实操演练最佳实践故障排查 每一课都遵循:概念讲解 → 命令实操 → 代码示例 → 常见问题 → 练习巩固 确保学完即能上手,理论实践并重。