dockerfile-optimizer
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDockerfile Optimizer
Dockerfile 优化指南
Build optimized, secure, and cache-efficient Docker images following production best practices.
遵循生产环境最佳实践,构建经过优化、安全且缓存高效的Docker镜像。
Core Workflow
核心工作流程
- Analyze current Dockerfile: Identify optimization opportunities
- Implement multi-stage builds: Separate build and runtime
- Optimize layer caching: Order instructions efficiently
- Minimize image size: Use slim base images and cleanup
- Add security hardening: Non-root user, minimal permissions
- Configure health checks: Ensure container health monitoring
- 分析现有Dockerfile:识别优化机会
- 实现多阶段构建:分离构建与运行环境
- 优化层缓存:合理排序指令
- 最小化镜像体积:使用轻量基础镜像并清理冗余内容
- 添加安全加固:使用非root用户、最小权限
- 配置健康检查:确保容器健康监控
Base Image Selection
基础镜像选择
Image Size Comparison
镜像体积对比
| Base Image | Size | Use Case |
|---|---|---|
| ~1GB | Development only |
| ~200MB | General production |
| ~130MB | Size-critical production |
| ~120MB | Maximum security |
| 基础镜像 | 体积 | 使用场景 |
|---|---|---|
| ~1GB | 仅用于开发环境 |
| ~200MB | 通用生产环境 |
| ~130MB | 对体积要求严格的生产环境 |
| ~120MB | 最高安全级别场景 |
Recommendations by Language
按语言推荐
dockerfile
undefineddockerfile
undefinedNode.js
Node.js
FROM node:20-alpine
FROM node:20-alpine
Python
Python
FROM python:3.12-slim
FROM python:3.12-slim
Go
Go
FROM golang:1.22-alpine AS builder
FROM scratch AS runtime # Or gcr.io/distroless/static
FROM golang:1.22-alpine AS builder
FROM scratch AS runtime # Or gcr.io/distroless/static
Rust
Rust
FROM rust:1.75-alpine AS builder
FROM alpine:3.19 AS runtime
FROM rust:1.75-alpine AS builder
FROM alpine:3.19 AS runtime
Java
Java
FROM eclipse-temurin:21-jdk-alpine AS builder
FROM eclipse-temurin:21-jre-alpine AS runtime
undefinedFROM eclipse-temurin:21-jdk-alpine AS builder
FROM eclipse-temurin:21-jre-alpine AS runtime
undefinedMulti-Stage Builds
多阶段构建
Node.js Application
Node.js 应用
dockerfile
undefineddockerfile
undefined==================== Build Stage ====================
==================== Build Stage ====================
FROM node:20-alpine AS builder
WORKDIR /app
FROM node:20-alpine AS builder
WORKDIR /app
Install dependencies first (cache layer)
Install dependencies first (cache layer)
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts
Copy source and build
Copy source and build
COPY . .
RUN npm run build
COPY . .
RUN npm run build
Prune dev dependencies
Prune dev dependencies
RUN npm prune --production
RUN npm prune --production
==================== Production Stage ====================
==================== Production Stage ====================
FROM node:20-alpine AS production
FROM node:20-alpine AS production
Security: Create non-root user
Security: Create non-root user
RUN addgroup -g 1001 -S nodejs &&
adduser -S nextjs -u 1001
adduser -S nextjs -u 1001
WORKDIR /app
RUN addgroup -g 1001 -S nodejs &&
adduser -S nextjs -u 1001
adduser -S nextjs -u 1001
WORKDIR /app
Copy only necessary files
Copy only necessary files
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./
Security: Switch to non-root user
Security: Switch to non-root user
USER nextjs
USER nextjs
Health check
Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
EXPOSE 3000
CMD ["node", "dist/index.js"]
undefinedHEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
EXPOSE 3000
CMD ["node", "dist/index.js"]
undefinedNext.js Application
Next.js 应用
dockerfile
undefineddockerfile
undefined==================== Dependencies ====================
==================== Dependencies ====================
FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
==================== Builder ====================
==================== Builder ====================
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
Disable telemetry during build
Disable telemetry during build
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
==================== Runner ====================
==================== Runner ====================
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs &&
adduser --system --uid 1001 nextjs
adduser --system --uid 1001 nextjs
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs &&
adduser --system --uid 1001 nextjs
adduser --system --uid 1001 nextjs
Copy static assets
Copy static assets
COPY --from=builder /app/public ./public
COPY --from=builder /app/public ./public
Set correct permissions for prerender cache
Set correct permissions for prerender cache
RUN mkdir .next && chown nextjs:nodejs .next
RUN mkdir .next && chown nextjs:nodejs .next
Copy build output
Copy build output
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
ENV HOSTNAME="0.0.0.0"
HEALTHCHECK --interval=30s --timeout=3s
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1
CMD ["node", "server.js"]
undefinedCOPY --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
ENV HOSTNAME="0.0.0.0"
HEALTHCHECK --interval=30s --timeout=3s
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1
CMD ["node", "server.js"]
undefinedPython Application
Python 应用
dockerfile
undefineddockerfile
undefined==================== Builder ====================
==================== Builder ====================
FROM python:3.12-slim AS builder
WORKDIR /app
FROM python:3.12-slim AS builder
WORKDIR /app
Install build dependencies
Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends
build-essential
&& rm -rf /var/lib/apt/lists/*
build-essential
&& rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y --no-install-recommends
build-essential
&& rm -rf /var/lib/apt/lists/*
build-essential
&& rm -rf /var/lib/apt/lists/*
Create virtual environment
Create virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
Install dependencies
Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
==================== Production ====================
==================== Production ====================
FROM python:3.12-slim AS production
WORKDIR /app
FROM python:3.12-slim AS production
WORKDIR /app
Create non-root user
Create non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser
RUN groupadd -r appuser && useradd -r -g appuser appuser
Copy virtual environment from builder
Copy virtual environment from builder
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
Copy application code
Copy application code
COPY --chown=appuser:appuser . .
USER appuser
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
undefinedCOPY --chown=appuser:appuser . .
USER appuser
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
undefinedGo Application
Go 应用
dockerfile
undefineddockerfile
undefined==================== Builder ====================
==================== Builder ====================
FROM golang:1.22-alpine AS builder
RUN apk add --no-cache git ca-certificates tzdata
WORKDIR /app
FROM golang:1.22-alpine AS builder
RUN apk add --no-cache git ca-certificates tzdata
WORKDIR /app
Download dependencies
Download dependencies
COPY go.mod go.sum ./
RUN go mod download && go mod verify
COPY go.mod go.sum ./
RUN go mod download && go mod verify
Build
Build
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
-ldflags="-w -s -X main.version=$(git describe --tags --always)"
-o /app/server ./cmd/server
-ldflags="-w -s -X main.version=$(git describe --tags --always)"
-o /app/server ./cmd/server
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
-ldflags="-w -s -X main.version=$(git describe --tags --always)"
-o /app/server ./cmd/server
-ldflags="-w -s -X main.version=$(git describe --tags --always)"
-o /app/server ./cmd/server
==================== Production ====================
==================== Production ====================
FROM scratch AS production
FROM scratch AS production
Copy CA certificates for HTTPS
Copy CA certificates for HTTPS
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
Copy binary
Copy binary
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
undefinedCOPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
undefinedLayer Caching Optimization
层缓存优化
Order Instructions by Change Frequency
按变更频率排序指令
dockerfile
undefineddockerfile
undefined✓ GOOD: Least changing → Most changing
✓ GOOD: Least changing → Most changing
FROM node:20-alpine
FROM node:20-alpine
1. System dependencies (rarely change)
1. System dependencies (rarely change)
RUN apk add --no-cache dumb-init
RUN apk add --no-cache dumb-init
2. Create user (rarely changes)
2. Create user (rarely changes)
RUN adduser -D appuser
RUN adduser -D appuser
3. Set working directory
3. Set working directory
WORKDIR /app
WORKDIR /app
4. Copy dependency files (change occasionally)
4. Copy dependency files (change occasionally)
COPY package.json package-lock.json ./
COPY package.json package-lock.json ./
5. Install dependencies (cached if package files unchanged)
5. Install dependencies (cached if package files unchanged)
RUN npm ci --production
RUN npm ci --production
6. Copy source code (changes frequently)
6. Copy source code (changes frequently)
COPY --chown=appuser:appuser . .
USER appuser
CMD ["dumb-init", "node", "index.js"]
```dockerfileCOPY --chown=appuser:appuser . .
USER appuser
CMD ["dumb-init", "node", "index.js"]
```dockerfile✗ BAD: Source code before dependencies
✗ BAD: Source code before dependencies
FROM node:20-alpine
WORKDIR /app
COPY . . # Invalidates cache on ANY file change
RUN npm install # Must reinstall every time
CMD ["node", "index.js"]
undefinedFROM node:20-alpine
WORKDIR /app
COPY . . # Invalidates cache on ANY file change
RUN npm install # Must reinstall every time
CMD ["node", "index.js"]
undefined.dockerignore
.dockerignore 配置
dockerignore
undefineddockerignore
undefinedVersion control
Version control
.git
.gitignore
.git
.gitignore
Dependencies (reinstalled in container)
Dependencies (reinstalled in container)
node_modules
.pnpm-store
node_modules
.pnpm-store
Build outputs
Build outputs
dist
build
.next
out
dist
build
.next
out
Development files
Development files
.env*.local
*.log
coverage
.nyc_output
.env*.local
*.log
coverage
.nyc_output
IDE
IDE
.idea
.vscode
*.swp
*.swo
.idea
.vscode
*.swp
*.swo
Docker
Docker
Dockerfile*
docker-compose*
.docker
Dockerfile*
docker-compose*
.docker
Documentation
Documentation
*.md
docs
*.md
docs
Tests (unless needed in container)
Tests (unless needed in container)
tests
*.test.ts
.spec.ts
jest.config.
undefinedtests
*.test.ts
.spec.ts
jest.config.
undefinedImage Size Reduction
镜像体积缩减
Clean Up in Same Layer
在同一层中清理
dockerfile
undefineddockerfile
undefined✓ GOOD: Install and clean in one layer
✓ GOOD: Install and clean in one layer
RUN apt-get update &&
apt-get install -y --no-install-recommends
curl
ca-certificates
&& rm -rf /var/lib/apt/lists/*
&& apt-get clean
apt-get install -y --no-install-recommends
curl
ca-certificates
&& rm -rf /var/lib/apt/lists/*
&& apt-get clean
RUN apt-get update &&
apt-get install -y --no-install-recommends
curl
ca-certificates
&& rm -rf /var/lib/apt/lists/*
&& apt-get clean
apt-get install -y --no-install-recommends
curl
ca-certificates
&& rm -rf /var/lib/apt/lists/*
&& apt-get clean
✗ BAD: Separate layers (cleanup doesn't reduce size)
✗ BAD: Separate layers (cleanup doesn't reduce size)
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/* # Too late, already in previous layer
undefinedRUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/* # Too late, already in previous layer
undefinedUse --no-install-recommends
使用 --no-install-recommends 参数
dockerfile
undefineddockerfile
undefined✓ Minimal installation
✓ Minimal installation
RUN apt-get install -y --no-install-recommends package-name
RUN apt-get install -y --no-install-recommends package-name
✗ Installs unnecessary recommended packages
✗ Installs unnecessary recommended packages
RUN apt-get install -y package-name
undefinedRUN apt-get install -y package-name
undefinedAlpine Package Management
Alpine 包管理
dockerfile
undefineddockerfile
undefinedAlpine uses apk, not apt
Alpine uses apk, not apt
RUN apk add --no-cache
curl
git
&& rm -rf /var/cache/apk/*
curl
git
&& rm -rf /var/cache/apk/*
undefinedRUN apk add --no-cache
curl
git
&& rm -rf /var/cache/apk/*
curl
git
&& rm -rf /var/cache/apk/*
undefinedSecurity Hardening
安全加固
Non-Root User
非root用户
dockerfile
undefineddockerfile
undefinedCreate user in Debian/Ubuntu
Create user in Debian/Ubuntu
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
Create user in Alpine
Create user in Alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
Set ownership and switch user
Set ownership and switch user
COPY --chown=appuser:appgroup . .
USER appuser
undefinedCOPY --chown=appuser:appgroup . .
USER appuser
undefinedRead-Only Filesystem
只读文件系统
dockerfile
undefineddockerfile
undefinedIn docker-compose.yml or docker run
In docker-compose.yml or docker run
services:
app:
read_only: true
tmpfs:
- /tmp
- /var/run
undefinedservices:
app:
read_only: true
tmpfs:
- /tmp
- /var/run
undefinedSecurity Scanning
安全扫描
dockerfile
undefineddockerfile
undefinedAdd labels for security scanning
Add labels for security scanning
LABEL org.opencontainers.image.source="https://github.com/org/repo"
LABEL org.opencontainers.image.description="Application description"
LABEL org.opencontainers.image.licenses="MIT"
undefinedLABEL org.opencontainers.image.source="https://github.com/org/repo"
LABEL org.opencontainers.image.description="Application description"
LABEL org.opencontainers.image.licenses="MIT"
undefinedMinimal Capabilities
最小权限配置
yaml
undefinedyaml
undefineddocker-compose.yml
docker-compose.yml
services:
app:
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # Only if binding to port < 1024
security_opt:
- no-new-privileges:true
undefinedservices:
app:
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # Only if binding to port < 1024
security_opt:
- no-new-privileges:true
undefinedHealth Checks
健康检查
HTTP Health Check
HTTP 健康检查
dockerfile
HEALTHCHECK \
CMD curl -f http://localhost:3000/health || exit 1dockerfile
HEALTHCHECK \
CMD curl -f http://localhost:3000/health || exit 1Without curl (smaller image)
不使用curl(更小镜像)
dockerfile
undefineddockerfile
undefinedNode.js
Node.js
HEALTHCHECK --interval=30s --timeout=3s --retries=3
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
HEALTHCHECK --interval=30s --timeout=3s --retries=3
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
Python
Python
HEALTHCHECK --interval=30s --timeout=3s --retries=3
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
HEALTHCHECK --interval=30s --timeout=3s --retries=3
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
wget (Alpine)
wget (Alpine)
HEALTHCHECK --interval=30s --timeout=3s --retries=3
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
undefinedHEALTHCHECK --interval=30s --timeout=3s --retries=3
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
undefinedEnvironment Variables
环境变量
dockerfile
undefineddockerfile
undefinedBuild-time arguments
Build-time arguments
ARG NODE_ENV=production
ARG APP_VERSION=unknown
ARG NODE_ENV=production
ARG APP_VERSION=unknown
Runtime environment variables
Runtime environment variables
ENV NODE_ENV=$NODE_ENV
ENV APP_VERSION=$APP_VERSION
ENV NODE_ENV=$NODE_ENV
ENV APP_VERSION=$APP_VERSION
Don't include secrets in Dockerfile
Don't include secrets in Dockerfile
Use docker run --env-file or secrets management
Use docker run --env-file or secrets management
undefinedundefinedDocker Compose for Development
开发环境Docker Compose配置
yaml
undefinedyaml
undefineddocker-compose.yml
docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
target: development # Multi-stage target
volumes:
- .:/app
- /app/node_modules # Anonymous volume for node_modules
ports:
- "3000:3000"
environment:
- NODE_ENV=development
command: npm run dev
app-prod:
build:
context: .
dockerfile: Dockerfile
target: production
ports:
- "3000:3000"
environment:
- NODE_ENV=production
healthcheck:
test: ["CMD", "wget", "--spider", "http://localhost:3000/health"]
interval: 30s
timeout: 3s
retries: 3
undefinedversion: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
target: development # Multi-stage target
volumes:
- .:/app
- /app/node_modules # Anonymous volume for node_modules
ports:
- "3000:3000"
environment:
- NODE_ENV=development
command: npm run dev
app-prod:
build:
context: .
dockerfile: Dockerfile
target: production
ports:
- "3000:3000"
environment:
- NODE_ENV=production
healthcheck:
test: ["CMD", "wget", "--spider", "http://localhost:3000/health"]
interval: 30s
timeout: 3s
retries: 3
undefinedCI/CD Integration
CI/CD 集成
GitHub Actions Build
GitHub Actions 构建配置
yaml
undefinedyaml
undefined.github/workflows/docker.yml
.github/workflows/docker.yml
name: Docker Build
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Scan for vulnerabilities
uses: aquasecurity/trivy-action@master
with:
image-ref: ghcr.io/${{ github.repository }}:${{ github.sha }}
format: 'table'
exit-code: '1'
severity: 'CRITICAL,HIGH'undefinedname: Docker Build
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Scan for vulnerabilities
uses: aquasecurity/trivy-action@master
with:
image-ref: ghcr.io/${{ github.repository }}:${{ github.sha }}
format: 'table'
exit-code: '1'
severity: 'CRITICAL,HIGH'undefinedCommon Optimizations
常见优化技巧
Pin Versions
固定版本
dockerfile
undefineddockerfile
undefined✓ Pinned versions for reproducibility
✓ Pinned versions for reproducibility
FROM node:20.11.0-alpine3.19
FROM node:20.11.0-alpine3.19
✗ Latest tags can break builds
✗ Latest tags can break builds
FROM node:latest
FROM node:20-alpine
undefinedFROM node:latest
FROM node:20-alpine
undefinedUse COPY Instead of ADD
使用 COPY 而非 ADD
dockerfile
undefineddockerfile
undefined✓ COPY is explicit and preferred
✓ COPY is explicit and preferred
COPY package.json .
COPY package.json .
✗ ADD has extra features rarely needed
✗ ADD has extra features rarely needed
ADD package.json . # Only use for URLs or tar extraction
undefinedADD package.json . # Only use for URLs or tar extraction
undefinedCombine RUN Commands
合并 RUN 命令
dockerfile
undefineddockerfile
undefined✓ Single layer, smaller image
✓ Single layer, smaller image
RUN apt-get update &&
apt-get install -y package1 package2 &&
rm -rf /var/lib/apt/lists/*
apt-get install -y package1 package2 &&
rm -rf /var/lib/apt/lists/*
RUN apt-get update &&
apt-get install -y package1 package2 &&
rm -rf /var/lib/apt/lists/*
apt-get install -y package1 package2 &&
rm -rf /var/lib/apt/lists/*
✗ Multiple layers, larger image
✗ Multiple layers, larger image
RUN apt-get update
RUN apt-get install -y package1
RUN apt-get install -y package2
undefinedRUN apt-get update
RUN apt-get install -y package1
RUN apt-get install -y package2
undefinedDebugging
调试方法
Inspect Image Layers
检查镜像层
bash
undefinedbash
undefinedView layer history
View layer history
docker history image-name
docker history image-name
Analyze image with dive
Analyze image with dive
docker run --rm -it
-v /var/run/docker.sock:/var/run/docker.sock
wagoodman/dive:latest image-name
-v /var/run/docker.sock:/var/run/docker.sock
wagoodman/dive:latest image-name
undefineddocker run --rm -it
-v /var/run/docker.sock:/var/run/docker.sock
wagoodman/dive:latest image-name
-v /var/run/docker.sock:/var/run/docker.sock
wagoodman/dive:latest image-name
undefinedBuild with Progress
带进度信息构建
bash
undefinedbash
undefinedDetailed build output
Detailed build output
docker build --progress=plain -t myapp .
docker build --progress=plain -t myapp .
Build specific stage
Build specific stage
docker build --target builder -t myapp:builder .
undefineddocker build --target builder -t myapp:builder .
undefinedBest Practices
最佳实践
- Use multi-stage builds: Separate build and runtime environments
- Order layers by change frequency: Maximize cache hits
- Use .dockerignore: Exclude unnecessary files
- Run as non-root: Always create and use a non-root user
- Pin base image versions: Ensure reproducible builds
- Clean up in same layer: Reduce image size
- Add health checks: Enable container orchestration
- Scan for vulnerabilities: Use Trivy, Snyk, or similar
- Use slim/alpine bases: Minimize attack surface
- Don't store secrets: Use runtime injection
- 使用多阶段构建:分离构建与运行环境
- 按变更频率排序层:最大化缓存命中率
- 配置.dockerignore:排除不必要的文件
- 以非root用户运行:始终创建并使用非root用户
- 固定基础镜像版本:确保构建可复现
- 在同一层中清理:减小镜像体积
- 添加健康检查:支持容器编排
- 扫描漏洞:使用Trivy、Snyk等工具
- 使用轻量/Alpine基础镜像:最小化攻击面
- 不存储敏感信息:使用运行时注入方式
Output Checklist
输出检查清单
Every optimized Dockerfile should include:
- Multi-stage build separating build and runtime
- Slim or Alpine base image
- Pinned base image version
- Layer caching optimization (deps before source)
- Non-root user configuration
- Health check defined
- .dockerignore file
- No secrets in image
- Minimal installed packages
- Cleanup in same layer as install
- Labels for metadata
- Security scanning in CI
每个优化后的Dockerfile应包含:
- 分离构建与运行环境的多阶段构建
- 轻量或Alpine基础镜像
- 固定版本的基础镜像
- 层缓存优化(依赖文件在源代码之前)
- 非root用户配置
- 定义健康检查
- .dockerignore文件
- 镜像中无敏感信息
- 最小化安装的包
- 安装与清理在同一层
- 元数据标签
- CI中的安全扫描