Appearance
Docker 容器化 - 前端应用容器化实战
1. Docker 简介
1.1 什么是 Docker
Docker 是一个开源的容器化平台,可以将应用程序及其依赖打包到一个可移植的容器中,确保应用在任何环境中都能以相同的方式运行。
核心概念:
- 镜像(Image):应用程序的只读模板
- 容器(Container):镜像的运行实例
- Dockerfile:构建镜像的指令文件
- 仓库(Registry):存储和分发镜像的服务
- Docker Compose:定义和运行多容器应用
1.2 为什么使用 Docker
优势:
- 环境一致性:开发、测试、生产环境完全一致
- 快速部署:秒级启动,快速扩展
- 资源隔离:每个容器独立运行,互不干扰
- 版本管理:镜像版本化,易于回滚
- 跨平台:在任何支持 Docker 的平台运行
1.3 适用场景
✓ 微服务架构
✓ CI/CD 流水线
✓ 多环境部署
✓ 开发环境标准化
✓ 应用快速交付2. Docker 基础
2.1 安装 Docker
Linux
bash
# Ubuntu/Debian
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# 启动 Docker
sudo systemctl start docker
sudo systemctl enable docker
# 添加用户到 docker 组
sudo usermod -aG docker $USERmacOS
bash
# 使用 Homebrew
brew install --cask docker
# 或下载 Docker Desktop
https://www.docker.com/products/docker-desktopWindows
bash
# 下载 Docker Desktop for Windows
https://www.docker.com/products/docker-desktop
# 启用 WSL 2(推荐)
wsl --install验证安装
bash
docker --version
docker run hello-world2.2 基本命令
bash
# 镜像操作
docker images # 列出本地镜像
docker pull <image> # 拉取镜像
docker build -t <name> . # 构建镜像
docker rmi <image> # 删除镜像
docker tag <image> <new-name> # 标记镜像
# 容器操作
docker ps # 列出运行中的容器
docker ps -a # 列出所有容器
docker run <image> # 运行容器
docker start <container> # 启动容器
docker stop <container> # 停止容器
docker restart <container> # 重启容器
docker rm <container> # 删除容器
docker exec -it <container> sh # 进入容器
# 日志和监控
docker logs <container> # 查看日志
docker logs -f <container> # 实时日志
docker stats # 查看资源使用
docker inspect <container> # 查看详细信息
# 清理
docker system prune # 清理未使用的数据
docker volume prune # 清理未使用的卷
docker network prune # 清理未使用的网络3. Dockerfile 编写
3.1 基础 Dockerfile
单阶段构建
dockerfile
# Dockerfile
FROM node:18-alpine
WORKDIR /app
# 复制依赖文件
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production
# 复制源代码
COPY . .
# 构建应用
RUN npm run build
# 暴露端口
EXPOSE 3000
# 启动命令
CMD ["npm", "start"]使用示例
bash
# 构建镜像
docker build -t my-react-app .
# 运行容器
docker run -p 3000:3000 my-react-app
# 访问应用
http://localhost:30003.2 多阶段构建(推荐)
dockerfile
# Dockerfile - 多阶段构建
# 阶段 1: 构建
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 阶段 2: 生产环境
FROM nginx:alpine
# 从构建阶段复制构建产物
COPY --from=builder /app/dist /usr/share/nginx/html
# 复制 nginx 配置
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]优势:
- 减小镜像大小(只包含运行时需要的文件)
- 提高安全性(不包含构建工具)
- 加快部署速度
3.3 优化的 Dockerfile
dockerfile
# Dockerfile - 优化版本
FROM node:18-alpine AS builder
# 安装必要的构建工具
RUN apk add --no-cache libc6-compat
WORKDIR /app
# 利用缓存层,先复制依赖文件
COPY package.json package-lock.json ./
# 使用 npm ci 进行确定性安装
RUN npm ci --only=production && \
npm cache clean --force
# 复制源代码
COPY . .
# 设置环境变量
ENV NODE_ENV=production
# 构建应用
RUN npm run build
# 生产阶段
FROM nginx:alpine
# 安装 curl 用于健康检查
RUN apk add --no-cache curl
# 复制构建产物
COPY --from=builder /app/dist /usr/share/nginx/html
# 复制 nginx 配置
COPY nginx.conf /etc/nginx/nginx.conf
# 添加非 root 用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
# 设置正确的权限
RUN chown -R nodejs:nodejs /usr/share/nginx/html && \
chown -R nodejs:nodejs /var/cache/nginx && \
chown -R nodejs:nodejs /var/log/nginx && \
chown -R nodejs:nodejs /etc/nginx/conf.d
RUN touch /var/run/nginx.pid && \
chown -R nodejs:nodejs /var/run/nginx.pid
# 切换到非 root 用户
USER nodejs
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/ || exit 1
CMD ["nginx", "-g", "daemon off;"]3.4 .dockerignore
bash
# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env
.env.local
.env.*.local
dist
build
coverage
.DS_Store
*.log
.vscode
.idea
README.md4. React 应用容器化
4.1 Vite + React
Dockerfile
dockerfile
# Dockerfile
FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]nginx.conf
nginx
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# Gzip 压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# SPA 路由支持
location / {
try_files $uri $uri/ /index.html;
}
# 静态资源缓存
location /assets {
expires 1y;
add_header Cache-Control "public, immutable";
}
# API 代理(可选)
location /api {
proxy_pass http://backend:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
# 安全 headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}
}构建和运行
bash
# 构建镜像
docker build -t my-vite-app .
# 运行容器
docker run -d \
--name my-app \
-p 8080:80 \
--restart unless-stopped \
my-vite-app
# 查看日志
docker logs -f my-app
# 停止并删除
docker stop my-app
docker rm my-app4.2 Next.js 应用
dockerfile
# Dockerfile - Next.js
FROM node:18-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM node:18-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build
FROM node:18-alpine 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"]next.config.js
javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
reactStrictMode: true,
swcMinify: true,
images: {
domains: ['example.com'],
},
};
module.exports = nextConfig;4.3 开发环境容器
dockerfile
# Dockerfile.dev
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 5173
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]bash
# 运行开发容器
docker run -d \
-p 5173:5173 \
-v $(pwd):/app \
-v /app/node_modules \
--name dev-app \
my-app:dev5. Docker Compose
5.1 基础配置
yaml
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "80:80"
restart: unless-stopped
environment:
- NODE_ENV=production
networks:
- app-network
networks:
app-network:
driver: bridgebash
# 启动服务
docker-compose up -d
# 查看日志
docker-compose logs -f
# 停止服务
docker-compose down5.2 全栈应用配置
yaml
# docker-compose.yml
version: '3.8'
services:
# 前端应用
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "80:80"
depends_on:
- backend
environment:
- VITE_API_URL=http://backend:3000
networks:
- app-network
restart: unless-stopped
# 后端 API
backend:
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "3000:3000"
depends_on:
- db
- redis
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:password@db:5432/mydb
- REDIS_URL=redis://redis:6379
networks:
- app-network
restart: unless-stopped
# PostgreSQL 数据库
db:
image: postgres:15-alpine
volumes:
- db-data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=mydb
networks:
- app-network
restart: unless-stopped
# Redis 缓存
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
networks:
- app-network
restart: unless-stopped
# Nginx 反向代理
nginx:
image: nginx:alpine
ports:
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- frontend
- backend
networks:
- app-network
restart: unless-stopped
volumes:
db-data:
redis-data:
networks:
app-network:
driver: bridge5.3 开发环境配置
yaml
# docker-compose.dev.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "5173:5173"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
- VITE_API_URL=http://localhost:3000
command: npm run dev -- --host 0.0.0.0
api:
build:
context: ./api
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- ./api:/app
- /app/node_modules
environment:
- NODE_ENV=development
command: npm run devbash
# 使用开发配置启动
docker-compose -f docker-compose.dev.yml up6. 镜像优化
6.1 减小镜像大小
使用 Alpine 基础镜像
dockerfile
# 使用更小的基础镜像
FROM node:18-alpine # ~40MB
# 而不是
FROM node:18 # ~900MB多阶段构建
dockerfile
# 只包含运行时需要的文件
FROM node:18-alpine AS build
# ... 构建步骤
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
# 最终镜像只有 ~20MB清理缓存
dockerfile
RUN npm ci --only=production && \
npm cache clean --force && \
rm -rf /tmp/*6.2 利用缓存层
dockerfile
# 先复制依赖文件(变化少)
COPY package*.json ./
RUN npm ci
# 再复制源代码(变化多)
COPY . .
RUN npm run build6.3 并行构建
dockerfile
# 使用 BuildKit
# docker build --build-arg BUILDKIT_INLINE_CACHE=1 .
FROM node:18-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM node:18-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html7. 安全最佳实践
7.1 使用非 root 用户
dockerfile
FROM nginx:alpine
# 创建用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
# 设置权限
RUN chown -R nodejs:nodejs /usr/share/nginx/html
# 切换用户
USER nodejs
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]7.2 扫描漏洞
bash
# 使用 Trivy 扫描镜像
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image my-app:latest
# 使用 Docker Scan
docker scan my-app:latest
# 使用 Snyk
snyk container test my-app:latest7.3 secrets 管理
yaml
# docker-compose.yml
version: '3.8'
services:
app:
image: my-app
secrets:
- db_password
- api_key
environment:
- DB_PASSWORD_FILE=/run/secrets/db_password
- API_KEY_FILE=/run/secrets/api_key
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
file: ./secrets/api_key.txt8. CI/CD 集成
8.1 GitHub Actions
yaml
# .github/workflows/docker.yml
name: Docker Build and Push
on:
push:
branches: [main]
tags: ['v*']
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=sha,prefix={{branch}}-
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Deploy to server
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main
docker-compose up -d8.2 GitLab CI
yaml
# .gitlab-ci.yml
image: docker:latest
services:
- docker:dind
variables:
DOCKER_DRIVER: overlay2
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
stages:
- build
- test
- deploy
build:
stage: build
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
test:
stage: test
script:
- docker run --rm $IMAGE_TAG npm test
deploy:
stage: deploy
only:
- main
script:
- docker pull $IMAGE_TAG
- docker-compose up -d9. 监控和日志
9.1 容器监控
yaml
# docker-compose.yml with monitoring
version: '3.8'
services:
app:
image: my-app
# ...
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
grafana:
image: grafana/grafana
ports:
- "3001:3000"
volumes:
- grafana-data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
node-exporter:
image: prom/node-exporter
ports:
- "9100:9100"
volumes:
prometheus-data:
grafana-data:9.2 集中日志
yaml
# docker-compose.yml with logging
version: '3.8'
services:
app:
image: my-app
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.7.0
environment:
- discovery.type=single-node
logstash:
image: docker.elastic.co/logstash/logstash:8.7.0
volumes:
- ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
kibana:
image: docker.elastic.co/kibana/kibana:8.7.0
ports:
- "5601:5601"10. 常见问题与解决方案
10.1 端口冲突
bash
# 查看端口占用
lsof -i :3000
netstat -tuln | grep 3000
# 使用不同的端口
docker run -p 3001:3000 my-app10.2 权限问题
dockerfile
# 使用正确的用户权限
RUN chown -R node:node /app
USER node10.3 网络问题
bash
# 检查网络
docker network ls
docker network inspect bridge
# 创建自定义网络
docker network create my-network
# 使用自定义网络
docker run --network my-network my-app10.4 卷挂载问题
bash
# 检查卷
docker volume ls
docker volume inspect my-volume
# 清理未使用的卷
docker volume prune11. 最佳实践总结
11.1 Dockerfile 最佳实践
- [ ] 使用官方基础镜像
- [ ] 使用多阶段构建
- [ ] 利用缓存层优化构建速度
- [ ] 使用 .dockerignore 排除不必要的文件
- [ ] 以非 root 用户运行
- [ ] 设置健康检查
- [ ] 使用明确的镜像标签
11.2 安全最佳实践
- [ ] 定期更新基础镜像
- [ ] 扫描镜像漏洞
- [ ] 使用 secrets 管理敏感信息
- [ ] 限制容器资源
- [ ] 使用只读文件系统
11.3 性能优化
- [ ] 减小镜像大小
- [ ] 启用 BuildKit
- [ ] 使用缓存
- [ ] 并行构建
- [ ] 优化层顺序
12. 总结
Docker 容器化为前端应用提供了:
- 环境一致性:消除"在我机器上能跑"的问题
- 快速部署:标准化的部署流程
- 易于扩展:水平扩展变得简单
- 版本管理:镜像版本化,易于回滚
- 资源隔离:安全可靠的运行环境
通过合理使用 Docker,可以大幅提升应用的可移植性和部署效率。