Dockerfile编写
📋 概述
Dockerfile是用于构建Docker镜像的文本文件,包含了一系列指令来定义镜像的构建过程。编写高效、安全的Dockerfile是容器化应用的关键技能。
🎯 学习目标
- 掌握Dockerfile的语法和指令
- 学会编写高效的多阶段构建
- 了解镜像优化和安全最佳实践
- 掌握Node.js应用的容器化技巧
📚 Dockerfile指令详解
基础指令
FROM - 基础镜像
dockerfile
# 使用官方Node.js镜像
FROM node:18-alpine
# 使用特定版本
FROM node:18.17.0-alpine3.18
# 多阶段构建
FROM node:18-alpine AS builder
FROM node:18-alpine AS runtime
# 使用参数化基础镜像
ARG NODE_VERSION=18
FROM node:${NODE_VERSION}-alpine
WORKDIR - 工作目录
dockerfile
# 设置工作目录
WORKDIR /usr/src/app
# 相对路径(相对于之前的WORKDIR)
WORKDIR /usr/src
WORKDIR app # 等同于 /usr/src/app
COPY 和 ADD
dockerfile
# COPY - 复制文件(推荐)
COPY package*.json ./
COPY src/ ./src/
COPY . .
# 设置文件所有权
COPY --chown=node:node package*.json ./
# ADD - 复制文件(支持URL和压缩包)
ADD https://example.com/file.tar.gz /tmp/
ADD archive.tar.gz /usr/src/app/ # 自动解压
# 使用.dockerignore优化复制
COPY . .
RUN - 执行命令
dockerfile
# 单行命令
RUN npm install
# 多行命令(推荐)
RUN npm ci --only=production && \
npm cache clean --force && \
rm -rf /tmp/*
# 使用SHELL形式
RUN ["npm", "install"]
# 安装系统包
RUN apk add --no-cache \
curl \
dumb-init \
&& rm -rf /var/cache/apk/*
ENV - 环境变量
dockerfile
# 设置环境变量
ENV NODE_ENV=production
ENV PORT=3000
ENV APP_VERSION=1.0.0
# 多个变量
ENV NODE_ENV=production \
PORT=3000 \
APP_VERSION=1.0.0
# 使用ARG构建时变量
ARG BUILD_ENV=production
ENV NODE_ENV=${BUILD_ENV}
EXPOSE - 暴露端口
dockerfile
# 暴露端口(仅文档作用)
EXPOSE 3000
EXPOSE 3000/tcp
EXPOSE 53/udp
# 使用变量
ENV PORT=3000
EXPOSE $PORT
USER - 用户设置
dockerfile
# 创建用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
# 切换用户
USER nextjs
# 或使用现有用户
USER node
CMD 和 ENTRYPOINT
dockerfile
# CMD - 默认命令(可被覆盖)
CMD ["npm", "start"]
CMD ["node", "app.js"]
# ENTRYPOINT - 入口点(不可被覆盖)
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "app.js"]
# 组合使用
ENTRYPOINT ["node"]
CMD ["app.js"]
🛠 Node.js应用Dockerfile示例
基础版本
dockerfile
FROM node:18-alpine
WORKDIR /usr/src/app
# 复制依赖文件
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production
# 复制源代码
COPY . .
# 暴露端口
EXPOSE 3000
# 启动应用
CMD ["npm", "start"]
优化版本
dockerfile
FROM node:18-alpine
# 安装系统依赖
RUN apk add --no-cache \
dumb-init \
curl \
&& rm -rf /var/cache/apk/*
# 创建应用用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
WORKDIR /usr/src/app
# 复制并安装依赖(利用缓存)
COPY --chown=nextjs:nodejs package*.json ./
RUN npm ci --only=production && \
npm cache clean --force
# 复制源代码
COPY --chown=nextjs:nodejs . .
# 切换到非root用户
USER nextjs
# 设置环境变量
ENV NODE_ENV=production \
PORT=3000
# 暴露端口
EXPOSE 3000
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# 使用dumb-init处理信号
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "app.js"]
多阶段构建版本
dockerfile
# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /usr/src/app
# 复制依赖文件
COPY package*.json ./
# 安装所有依赖(包括开发依赖)
RUN npm ci
# 复制源代码
COPY . .
# 构建应用
RUN npm run build && \
npm prune --production
# 生产阶段
FROM node:18-alpine AS production
# 安装系统依赖
RUN apk add --no-cache \
dumb-init \
curl \
&& rm -rf /var/cache/apk/*
# 创建应用用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
WORKDIR /usr/src/app
# 从构建阶段复制文件
COPY --from=builder --chown=nextjs:nodejs /usr/src/app/dist ./dist
COPY --from=builder --chown=nextjs:nodejs /usr/src/app/node_modules ./node_modules
COPY --chown=nextjs:nodejs package.json ./
# 切换用户
USER nextjs
# 环境变量
ENV NODE_ENV=production \
PORT=3000
EXPOSE 3000
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/app.js"]
🔧 高级构建技巧
参数化构建
dockerfile
# 使用构建参数
ARG NODE_VERSION=18
ARG ALPINE_VERSION=3.18
FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION}
ARG BUILD_DATE
ARG VCS_REF
ARG VERSION
LABEL maintainer="your-email@example.com" \
org.label-schema.build-date=$BUILD_DATE \
org.label-schema.name="My Node App" \
org.label-schema.description="Description of my app" \
org.label-schema.url="https://example.com" \
org.label-schema.vcs-ref=$VCS_REF \
org.label-schema.vcs-url="https://github.com/user/repo" \
org.label-schema.vendor="Your Company" \
org.label-schema.version=$VERSION \
org.label-schema.schema-version="1.0"
WORKDIR /usr/src/app
# 使用构建参数
ARG NPM_TOKEN
RUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc && \
npm ci --only=production && \
rm -f .npmrc
COPY . .
USER node
ENV NODE_ENV=production
CMD ["npm", "start"]
bash
# 构建时传递参数
docker build \
--build-arg NODE_VERSION=18 \
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
--build-arg VCS_REF=$(git rev-parse --short HEAD) \
--build-arg VERSION=1.0.0 \
--build-arg NPM_TOKEN=${NPM_TOKEN} \
-t my-app:1.0.0 .
条件构建
dockerfile
# 使用构建参数控制构建流程
ARG BUILD_ENV=production
FROM node:18-alpine AS base
WORKDIR /usr/src/app
COPY package*.json ./
# 开发环境
FROM base AS development
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
# 生产环境
FROM base AS production
RUN npm ci --only=production
COPY . .
CMD ["npm", "start"]
# 根据参数选择最终阶段
FROM ${BUILD_ENV} AS final
bash
# 构建开发版本
docker build --target development -t my-app:dev .
# 构建生产版本
docker build --target production -t my-app:prod .
# 使用参数构建
docker build --build-arg BUILD_ENV=development -t my-app:dev .
缓存优化
dockerfile
FROM node:18-alpine
WORKDIR /usr/src/app
# 第一层:系统依赖(很少变化)
RUN apk add --no-cache dumb-init curl
# 第二层:创建用户(很少变化)
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
# 第三层:依赖安装(package.json变化时才重新构建)
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
# 第四层:源代码(经常变化,放在最后)
COPY --chown=nextjs:nodejs . .
USER nextjs
EXPOSE 3000
CMD ["dumb-init", "node", "app.js"]
🔒 安全最佳实践
基础安全配置
dockerfile
FROM node:18-alpine
# 更新系统包
RUN apk update && apk upgrade && \
apk add --no-cache dumb-init && \
rm -rf /var/cache/apk/*
# 创建非root用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001 -G nodejs
WORKDIR /usr/src/app
# 设置正确的文件权限
COPY --chown=nextjs:nodejs package*.json ./
RUN npm ci --only=production && npm cache clean --force
COPY --chown=nextjs:nodejs . .
# 删除不必要的文件
RUN rm -rf \
.git \
.gitignore \
.dockerignore \
Dockerfile* \
README.md \
tests \
docs
# 切换到非root用户
USER nextjs
# 限制权限
RUN chmod -R 755 /usr/src/app
ENV NODE_ENV=production
EXPOSE 3000
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "app.js"]
安全扫描集成
dockerfile
# 多阶段构建 - 安全扫描
FROM node:18-alpine AS security-scan
WORKDIR /app
COPY package*.json ./
RUN npm audit --audit-level high
# 主构建阶段
FROM node:18-alpine AS production
# 安装Trivy进行容器扫描
RUN apk add --no-cache curl && \
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# 运行安全扫描
RUN trivy fs --exit-code 1 --no-progress --severity HIGH,CRITICAL .
USER node
CMD ["npm", "start"]
📊 镜像优化
大小优化
dockerfile
# 使用Alpine Linux基础镜像
FROM node:18-alpine
# 合并RUN指令减少层数
RUN apk add --no-cache \
curl \
dumb-init \
&& addgroup -g 1001 -S nodejs \
&& adduser -S nextjs -u 1001 -G nodejs \
&& mkdir -p /usr/src/app \
&& chown nextjs:nodejs /usr/src/app
WORKDIR /usr/src/app
# 只复制必要文件
COPY --chown=nextjs:nodejs package*.json ./
# 安装生产依赖并清理
RUN npm ci --only=production \
&& npm cache clean --force \
&& rm -rf /tmp/*
# 复制源代码
COPY --chown=nextjs:nodejs src/ ./src/
COPY --chown=nextjs:nodejs public/ ./public/
USER nextjs
ENV NODE_ENV=production
EXPOSE 3000
CMD ["dumb-init", "node", "src/app.js"]
.dockerignore文件
dockerignore
# 版本控制
.git
.gitignore
.gitattributes
# 依赖目录
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# 测试和覆盖率
coverage
.nyc_output
test
tests
__tests__
*.test.js
*.spec.js
# 开发工具
.vscode
.idea
*.swp
*.swo
*~
# 文档
README.md
CHANGELOG.md
LICENSE
docs
# 构建产物
dist
build
.next
.nuxt
# 环境配置
.env*
!.env.example
# 日志
logs
*.log
# 临时文件
.tmp
.temp
*.tmp
*.temp
# Docker相关
Dockerfile*
docker-compose*.yml
.dockerignore
# CI/CD
.github
.gitlab-ci.yml
.travis.yml
Jenkinsfile
🚀 实际应用示例
Express应用完整Dockerfile
dockerfile
# 多阶段构建 - Express应用
FROM node:18-alpine AS base
# 安装系统依赖
RUN apk add --no-cache \
dumb-init \
curl \
&& rm -rf /var/cache/apk/*
# 创建应用用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S express -u 1001 -G nodejs
WORKDIR /usr/src/app
# 依赖安装阶段
FROM base AS dependencies
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
# 开发阶段
FROM dependencies AS development
RUN npm install
COPY . .
USER express
EXPOSE 3000
CMD ["npm", "run", "dev"]
# 构建阶段
FROM dependencies AS builder
COPY . .
RUN npm run build
# 生产阶段
FROM base AS production
# 复制生产依赖
COPY --from=dependencies --chown=express:nodejs /usr/src/app/node_modules ./node_modules
# 复制应用文件
COPY --from=builder --chown=express:nodejs /usr/src/app/dist ./dist
COPY --chown=express:nodejs package.json ./
COPY --chown=express:nodejs public ./public
# 设置用户和权限
USER express
# 环境变量
ENV NODE_ENV=production \
PORT=3000 \
LOG_LEVEL=info
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
EXPOSE 3000
# 启动应用
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/server.js"]
Next.js应用Dockerfile
dockerfile
FROM node:18-alpine AS base
# 安装系统依赖
RUN apk add --no-cache libc6-compat
WORKDIR /app
# 依赖安装
FROM base AS deps
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
# 构建阶段
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# 禁用遥测
ENV NEXT_TELEMETRY_DISABLED 1
RUN yarn build
# 生产镜像
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
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"]
📝 构建和测试
构建命令
bash
# 基本构建
docker build -t my-app:latest .
# 指定Dockerfile
docker build -f Dockerfile.prod -t my-app:prod .
# 传递构建参数
docker build \
--build-arg NODE_VERSION=18 \
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
-t my-app:latest .
# 构建特定阶段
docker build --target production -t my-app:prod .
# 无缓存构建
docker build --no-cache -t my-app:latest .
# 使用BuildKit
DOCKER_BUILDKIT=1 docker build -t my-app:latest .
测试Dockerfile
bash
# 运行容器测试
docker run --rm -p 3000:3000 my-app:latest
# 检查镜像大小
docker images my-app:latest
# 分析镜像层
docker history my-app:latest
# 安全扫描
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image my-app:latest
# 检查镜像漏洞
docker scout cves my-app:latest
📝 总结
编写高效的Dockerfile需要考虑:
- 性能优化:利用构建缓存和多阶段构建
- 安全实践:使用非root用户和最小权限原则
- 镜像大小:选择合适的基础镜像和清理不必要文件
- 可维护性:清晰的结构和适当的注释
掌握这些技巧能够构建出安全、高效、可维护的容器镜像。