Dockerfile定义:一个文本文件,包含构建Docker镜像的所有指令。
东巴文理解:
Dockerfile = 镜像配方
就像:做菜的菜谱
菜谱记录了:
├─ 需要什么材料(基础镜像)
├─ 怎么加工(RUN命令)
├─ 放什么调料(环境变量)
├─ 怎么装盘(文件复制)
└─ 怎么上菜(启动命令)
用菜谱可以做出无数道相同的菜(镜像)
Dockerfile示例:
# 基础镜像
FROM node:18-alpine
# 设置工作目录
WORKDIR /app
# 复制依赖文件
COPY package*.json ./
# 安装依赖
RUN npm install
# 复制应用代码
COPY . .
# 暴露端口
EXPOSE 3000
# 启动命令
CMD ["npm", "start"]
优势:
可重复构建:
├─ 相同Dockerfile生成相同镜像
├─ 版本控制友好
└─ 团队协作方便
透明可读:
├─ 文本文件,易于理解
├─ 清晰的构建步骤
└─ 便于审查和维护
灵活可定制:
├─ 支持变量和参数
├─ 支持多阶段构建
└─ 支持条件判断
自动化:
├─ CI/CD集成
├─ 自动构建
└─ 自动测试
基本格式:
# 注释
INSTRUCTION arguments
示例:
# This is a comment
FROM ubuntu:22.04
RUN apt-get update
CMD ["bash"]
结构组成:
Dockerfile结构:
├─ 基础镜像信息(FROM)
├─ 维护者信息(LABEL)
├─ 镜像操作指令(RUN、COPY、ADD等)
└─ 容器启动指令(CMD、ENTRYPOINT)
执行顺序:
从上到下依次执行
每条指令创建一个新层
两种格式:
# Shell格式(推荐)
RUN apt-get update
# Exec格式(推荐用于CMD和ENTRYPOINT)
CMD ["npm", "start"]
ENTRYPOINT ["python", "app.py"]
# Shell格式 vs Exec格式
RUN echo "hello" # Shell格式,会启动shell
RUN ["echo", "hello"] # Exec格式,不启动shell
CMD echo "hello" # Shell格式
CMD ["echo", "hello"] # Exec格式(推荐)
ENTRYPOINT echo "hello" # Shell格式
ENTRYPOINT ["echo", "hello"] # Exec格式(推荐)
格式区别:
Shell格式:
├─ 会启动shell进程
├─ 支持变量替换
├─ 支持管道等shell特性
└─ 示例:RUN echo $HOME
Exec格式:
├─ 直接执行命令,不启动shell
├─ 不支持shell特性
├─ 更安全,更高效
└─ 示例:CMD ["npm", "start"]
推荐:
✅ RUN使用Shell格式
✅ CMD和ENTRYPOINT使用Exec格式
基本语法:
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
使用示例:
# 使用最新版本
FROM ubuntu
# 使用指定版本
FROM ubuntu:22.04
# 使用摘要
FROM ubuntu@sha256:7a86f8a2e...
# 指定平台
FROM --platform=linux/arm64 ubuntu:22.04
# 多阶段构建命名
FROM node:18-alpine AS builder
基础镜像选择:
# 官方镜像(推荐)
FROM node:18-alpine
FROM python:3.11-slim
FROM nginx:alpine
# 最小化镜像
FROM alpine:3.18
FROM busybox:latest
# 特定版本(推荐)
FROM node:18.19.0-alpine
# 避免使用latest(不推荐)
FROM node:latest # 版本不确定
多阶段构建:
# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 运行阶段
FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
基本语法:
LABEL <key>=<value> <key>=<value> <key>=<value> ...
使用示例:
# 单个标签
LABEL maintainer="admin@example.com"
# 多个标签
LABEL version="1.0" \
description="This is a web application" \
author="John Doe"
# 组织信息
LABEL org.opencontainers.image.version="1.0.0"
LABEL org.opencontainers.image.authors="team@example.com"
LABEL org.opencontainers.image.source="https://github.com/user/repo"
推荐标签:
LABEL maintainer="admin@example.com"
LABEL version="1.0.0"
LABEL description="Web application based on Node.js"
LABEL vendor="Company Name"
LABEL com.example.version="1.0.0"
LABEL com.example.release-date="2024-01-15"
基本语法:
ENV <key> <value>
ENV <key>=<value> ...
使用示例:
# 单个环境变量
ENV APP_HOME /app
# 多个环境变量
ENV NODE_ENV=production \
PORT=3000 \
DEBUG=false
# 使用环境变量
ENV APP_HOME /app
WORKDIR $APP_HOME
# 在后续指令中使用
ENV VERSION=1.0.0
RUN echo "Version is ${VERSION}"
使用建议:
# 设置应用环境
ENV NODE_ENV=production
ENV APP_HOME=/app
# 设置版本信息
ENV VERSION=1.0.0
# 设置路径
ENV PATH=/app/bin:$PATH
# 设置时区
ENV TZ=Asia/Shanghai
# 组合使用
ENV APP_HOME=/app \
NODE_ENV=production \
PORT=3000
WORKDIR $APP_HOME
基本语法:
WORKDIR /path/to/workdir
使用示例:
# 设置工作目录
WORKDIR /app
# 使用相对路径
WORKDIR app # 相对于前一个WORKDIR
# 使用环境变量
ENV APP_HOME /app
WORKDIR $APP_HOME
# 创建多级目录
WORKDIR /app/src/components
使用建议:
# 推荐:使用绝对路径
WORKDIR /app
# 不推荐:使用相对路径
WORKDIR app
# 推荐:先设置环境变量
ENV APP_HOME=/app
WORKDIR $APP_HOME
# 推荐:在WORKDIR后执行命令
WORKDIR /app
COPY . .
RUN npm install
基本语法:
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
使用示例:
# 复制单个文件
COPY package.json /app/
# 复制多个文件
COPY package*.json /app/
# 复制目录
COPY src/ /app/src/
# 使用WORKDIR
WORKDIR /app
COPY . .
# 修改文件所有者
COPY --chown=node:node . /app
# 复制并重命名
COPY config.json /app/production.json
复制规则:
源路径:
├─ 相对于构建上下文
├─ 可以使用通配符
├─ 必须在构建上下文内
└─ 示例:COPY *.json /app/
目标路径:
├─ 绝对路径
├─ 相对于WORKDIR的相对路径
├─ 如果不存在会自动创建
└─ 示例:COPY . /app/
注意事项:
⚠️ 源路径必须在构建上下文内
⚠️ 不能使用../访问上级目录
⚠️ 目标路径以/结尾表示目录
⚠️ 目标路径不以/结尾表示文件
基本语法:
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
使用示例:
# 复制文件(与COPY相同)
ADD package.json /app/
# 自动解压tar文件
ADD app.tar.gz /app/
# 从URL下载文件
ADD http://example.com/file.txt /app/
# 从URL下载并解压(不支持)
ADD http://example.com/file.tar.gz /app/ # 不会自动解压
对比:
COPY指令:
├─ 只复制文件
├─ 功能简单
├─ 语义明确
└─ 推荐使用
ADD指令:
├─ 复制文件
├─ 自动解压tar文件
├─ 支持URL下载
└─ 功能复杂
推荐:
✅ 优先使用COPY
⚠️ 需要解压时使用ADD
❌ 不推荐从URL下载(使用RUN curl)
最佳实践:
# 推荐:使用COPY复制文件
COPY package.json /app/
# 推荐:使用ADD解压文件
ADD app.tar.gz /app/
# 不推荐:使用ADD从URL下载
ADD http://example.com/file.txt /app/
# 推荐:使用RUN curl下载
RUN curl -fsSL http://example.com/file.txt -o /app/file.txt
基本语法:
VOLUME ["/data"]
VOLUME /data /data2
使用示例:
# 单个卷
VOLUME /data
# 多个卷
VOLUME ["/data", "/var/log"]
# 使用环境变量
ENV DATA_DIR /data
VOLUME $DATA_DIR
# 数据库示例
FROM mysql:8.0
VOLUME /var/lib/mysql
使用建议:
VOLUME作用:
├─ 创建挂载点
├─ 持久化数据
├─ 共享数据
└─ 避免数据丢失
注意事项:
⚠️ VOLUME指令后的修改不会生效
⚠️ 运行时可以覆盖挂载点
⚠️ 匿名卷会自动创建
⚠️ 推荐在运行时使用-v指定
示例:
Dockerfile:
VOLUME /data
RUN echo "hello" > /data/file.txt # 不会生效
运行时:
docker run -v /host/data:/data myimage
基本语法:
# Shell格式
RUN <command>
# Exec格式
RUN ["executable", "param1", "param2"]
使用示例:
# Shell格式(推荐)
RUN apt-get update && apt-get install -y nginx
# Exec格式
RUN ["/bin/bash", "-c", "echo hello"]
# 多行命令
RUN apt-get update \
&& apt-get install -y \
nginx \
curl \
vim \
&& rm -rf /var/lib/apt/lists/*
# 使用管道
RUN curl -fsSL https://example.com/install.sh | bash
# 使用条件判断
RUN if [ "$NODE_ENV" = "production" ]; then \
npm install --production; \
else \
npm install; \
fi
优化建议:
# 推荐:合并多条命令
RUN apt-get update \
&& apt-get install -y nginx \
&& rm -rf /var/lib/apt/lists/*
# 不推荐:分开多条命令
RUN apt-get update
RUN apt-get install -y nginx
RUN rm -rf /var/lib/apt/lists/*
# 推荐:清理缓存
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
package1 \
package2 \
&& rm -rf /var/lib/apt/lists/*
# 推荐:使用--no-install-recommends
RUN apt-get install -y --no-install-recommends nginx
# 推荐:使用最小化基础镜像
FROM alpine:3.18
RUN apk add --no-cache nginx
基本语法:
# Exec格式(推荐)
CMD ["executable", "param1", "param2"]
# 参数格式(为ENTRYPOINT提供参数)
CMD ["param1", "param2"]
# Shell格式
CMD command param1 param2
使用示例:
# Exec格式(推荐)
CMD ["npm", "start"]
CMD ["python", "app.py"]
CMD ["nginx", "-g", "daemon off;"]
# 参数格式
ENTRYPOINT ["python"]
CMD ["app.py"]
# Shell格式
CMD npm start
CMD python app.py
# 带参数
CMD ["node", "server.js", "--port", "3000"]
使用规则:
CMD特点:
├─ 只能有一个CMD指令
├─ 多个CMD只有最后一个生效
├─ 可以被docker run参数覆盖
└─ 提供容器默认启动命令
覆盖示例:
Dockerfile:
CMD ["npm", "start"]
运行时覆盖:
docker run myimage npm run dev
实际执行:npm run dev(覆盖了CMD)
基本语法:
# Exec格式(推荐)
ENTRYPOINT ["executable", "param1", "param2"]
# Shell格式
ENTRYPOINT command param1 param2
使用示例:
# Exec格式(推荐)
ENTRYPOINT ["python", "app.py"]
ENTRYPOINT ["nginx", "-g", "daemon off;"]
# Shell格式
ENTRYPOINT python app.py
# 与CMD配合
ENTRYPOINT ["python"]
CMD ["app.py"]
# 运行时传参
ENTRYPOINT ["python", "app.py"]
# docker run myimage --port 3000
# 实际执行:python app.py --port 3000
对比:
CMD指令:
├─ 提供默认启动命令
├─ 可以被docker run参数覆盖
├─ 可以作为ENTRYPOINT的参数
└─ 示例:CMD ["npm", "start"]
ENTRYPOINT指令:
├─ 配置容器为可执行程序
├─ docker run参数会追加到ENTRYPOINT
├─ 不容易被覆盖
└─ 示例:ENTRYPOINT ["python", "app.py"]
组合使用:
ENTRYPOINT ["python"]
CMD ["app.py"]
docker run myimage # 执行:python app.py
docker run myimage test.py # 执行:python test.py
最佳实践:
# 场景1:固定命令,允许传参
ENTRYPOINT ["python", "app.py"]
# docker run myimage --port 3000
# 执行:python app.py --port 3000
# 场景2:固定程序,允许改变参数
ENTRYPOINT ["python"]
CMD ["app.py"]
# docker run myimage # 执行:python app.py
# docker run myimage test.py # 执行:python test.py
# 场景3:固定命令,不允许改变
ENTRYPOINT ["nginx", "-g", "daemon off;"]
CMD [""] # 空CMD
基本语法:
EXPOSE <port> [<port>/<protocol>...]
使用示例:
# 暴露单个端口
EXPOSE 80
# 暴露多个端口
EXPOSE 80 443
# 指定协议
EXPOSE 80/tcp
EXPOSE 53/udp
# 应用示例
FROM nginx:alpine
EXPOSE 80 443
FROM node:18-alpine
EXPOSE 3000
FROM mysql:8.0
EXPOSE 3306
理解EXPOSE:
EXPOSE作用:
├─ 声明端口
├─ 文档化用途
├─ 容器间通信
└─ 不实际发布端口
注意事项:
⚠️ EXPOSE不会发布端口到主机
⚠️ 需要docker run -p发布端口
⚠️ 用于文档和容器间通信
⚠️ 推荐声明应用使用的端口
示例:
Dockerfile:
EXPOSE 3000
运行时:
docker run -p 3000:3000 myimage # 发布端口
docker run -P myimage # 自动发布EXPOSE的端口
Dockerfile中无法指定网络模式,网络配置在运行时指定:
# bridge模式(默认)
docker run -d --name myapp myimage
# host模式
docker run -d --net=host myimage
# none模式
docker run -d --net=none myimage
# 自定义网络
docker network create mynetwork
docker run -d --net=mynetwork myimage
基本语法:
USER <user>[:<group>]
USER <UID>[:<GID>]
使用示例:
# 创建用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
# 切换用户
USER appuser
# 使用UID和GID
USER 1000:1000
# 完整示例
FROM node:18-alpine
RUN addgroup -g 1001 -S nodejs \
&& adduser -S nextjs -u 1001
USER nextjs
WORKDIR /app
COPY --chown=nextjs:nodejs . .
CMD ["node", "server.js"]
安全建议:
# 推荐:使用非root用户
FROM node:18-alpine
RUN addgroup -g 1001 -S nodejs \
&& adduser -S nextjs -u 1001
USER nextjs
# 推荐:先创建用户,再切换
RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser
# 推荐:设置文件所有者
COPY --chown=appuser:appuser . /app
# 注意:USER后的RUN命令以该用户执行
USER appuser
RUN npm install # 以appuser身份执行
基本语法:
ARG <name>[=<default value>]
使用示例:
# 定义构建参数
ARG VERSION=1.0.0
ARG NODE_ENV=production
# 使用构建参数
FROM node:${VERSION}
ARG NODE_ENV
ENV NODE_ENV=$NODE_ENV
# 构建时传递
# docker build --build-arg VERSION=18 --build-arg NODE_ENV=development -t myapp .
对比:
ARG指令:
├─ 构建时变量
├─ 只在构建过程中有效
├─ 不会保留在镜像中
├─ 可以通过--build-arg传递
└─ 示例:ARG VERSION=18
ENV指令:
├─ 环境变量
├─ 构建和运行时都有效
├─ 会保留在镜像中
├─ 可以通过-e传递
└─ 示例:ENV NODE_ENV=production
使用场景:
✅ ARG:版本号、构建时间等
✅ ENV:应用配置、运行时参数
基本语法:
# 第一阶段
FROM image AS stage1
# 构建步骤
# 第二阶段
FROM image AS stage2
COPY --from=stage1 /app/build /app
使用示例:
# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 运行阶段
FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
优势:
减小镜像体积:
├─ 只保留运行时需要的文件
├─ 不包含构建工具和依赖
└─ 示例:从1GB减小到50MB
安全性提升:
├─ 不包含源代码
├─ 不包含构建工具
└─ 减少攻击面
构建优化:
├─ 分离构建和运行环境
├─ 利用缓存加速构建
└─ 清晰的构建流程
完整示例:
# 阶段1:依赖安装
FROM node:18-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# 阶段2:构建应用
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 阶段3:运行环境
FROM node:18-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup -g 1001 -S nodejs \
&& adduser -S nextjs -u 1001
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
基础指令:
├─ FROM:指定基础镜像
├─ LABEL:添加元数据
├─ ENV:设置环境变量
└─ WORKDIR:设置工作目录
文件操作:
├─ COPY:复制文件
├─ ADD:复制文件(支持解压和URL)
└─ VOLUME:创建挂载点
执行命令:
├─ RUN:执行命令
├─ CMD:容器启动命令
└─ ENTRYPOINT:容器入口点
网络与端口:
└─ EXPOSE:声明端口
用户与权限:
└─ USER:指定运行用户
构建参数:
├─ ARG:构建参数
└─ 多阶段构建:FROM ... AS
镜像选择:
✅ 使用官方镜像
✅ 使用明确版本标签
✅ 使用最小化基础镜像
指令优化:
✅ 合并多条RUN命令
✅ 清理缓存和临时文件
✅ 利用构建缓存
✅ 使用多阶段构建
安全建议:
✅ 使用非root用户
✅ 不存储敏感信息
✅ 使用COPY而不是ADD
✅ 扫描镜像漏洞
可维护性:
✅ 添加有意义的注释
✅ 使用LABEL添加元数据
✅ 合理组织指令顺序
✅ 使用.dockerignore
下一章:Docker容器基础
将学习:
编写Dockerfile:为一个简单的Node.js应用编写Dockerfile,要求使用node:18-alpine基础镜像。
指令理解:解释CMD和ENTRYPOINT的区别,并举例说明如何配合使用。
多阶段构建:编写一个多阶段构建的Dockerfile,将一个React应用构建并部署到nginx。
FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y nodejs
RUN apt-get install -y npm
COPY . /app
WORKDIR /app
RUN npm install
CMD npm start
参数化构建:编写一个支持构建参数的Dockerfile,可以指定Node.js版本和应用环境。
安全加固:为一个Web应用编写安全的Dockerfile,要求使用非root用户运行。
完整项目:为一个完整的Web应用(前端+后端+数据库)编写Dockerfile,要求:
CI/CD集成:编写一个Dockerfile,支持在CI/CD流程中使用,要求: