强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Ruby 入门指南 / 第 21 章:Docker 部署

第 21 章:Docker 部署

“在我机器上能跑!” —— Docker 终结了这句话。


21.1 Docker 基础

21.1.1 为什么使用 Docker

优势说明
环境一致性开发、测试、生产环境完全一致
隔离性应用互不干扰
可移植性任何支持 Docker 的机器都能运行
版本控制镜像版本化管理
扩展性容器编排轻松实现水平扩展

21.1.2 Docker 核心概念

Dockerfile      → 构建镜像的"菜谱"
Image (镜像)    → 只读模板,包含应用和依赖
Container (容器) → 镜像的运行实例
Volume (卷)     → 持久化存储
Network (网络)  → 容器间通信

21.2 基本 Dockerfile

21.2.1 简单的 Ruby 应用

# Dockerfile
FROM ruby:3.3-slim

# 安装系统依赖
RUN apt-get update -qq && \
    apt-get install -y --no-install-recommends \
    build-essential \
    libpq-dev \
    git \
    curl \
    && rm -rf /var/lib/apt/lists/*

# 设置工作目录
WORKDIR /app

# 先复制依赖文件(利用缓存)
COPY Gemfile Gemfile.lock ./

# 安装依赖
RUN bundle config set --local without 'development test' && \
    bundle install --jobs 4 --retry 3

# 复制应用代码
COPY . .

# 暴露端口
EXPOSE 3000

# 启动命令
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

21.2.2 .dockerignore

# .dockerignore
.git
.github
.env
.env.*
*.gem
*.rbc
.bundle/
vendor/bundle
log/*
tmp/*
tmp/**/*
coverage/*
spec/*
test/*
.DS_Store
.idea/
.vscode/
node_modules/

21.3 多阶段构建

21.3.1 Rails 应用多阶段构建

# ========== 阶段 1:构建 ==========
FROM ruby:3.3-slim AS builder

# 安装构建依赖
RUN apt-get update -qq && \
    apt-get install -y --no-install-recommends \
    build-essential \
    libpq-dev \
    git \
    nodejs \
    npm \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# 安装 JavaScript 依赖
COPY package.json package-lock.json ./
RUN npm ci --production

# 安装 Ruby 依赖
COPY Gemfile Gemfile.lock ./
RUN bundle config set --local deployment true && \
    bundle config set --local without 'development test' && \
    bundle install --jobs 4 --retry 3 && \
    bundle exec bootsnap precompile --gemfile

# 复制代码并预编译资源
COPY . .
RUN SECRET_KEY_BASE=placeholder bundle exec rails assets:precompile

# ========== 阶段 2:运行 ==========
FROM ruby:3.3-slim AS runtime

# 安装运行时依赖
RUN apt-get update -qq && \
    apt-get install -y --no-install-recommends \
    libpq5 \
    curl \
    && rm -rf /var/lib/apt/lists/*

# 创建非 root 用户
RUN groupadd -r appuser && useradd -r -g appuser appuser

WORKDIR /app

# 从构建阶段复制文件
COPY --from=builder --chown=appuser:appuser /app /app

# 切换用户
USER appuser

EXPOSE 3000

CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

21.3.2 多阶段构建的优势

特性单阶段多阶段
镜像大小大(包含编译工具)小(只有运行时)
安全性包含开发工具最小攻击面
构建缓存有限高效分层缓存
推荐度快速原型生产环境

21.4 Bundler 缓存优化

21.4.1 利用 Docker 层缓存

# ❌ 每次代码变更都重新安装依赖
COPY . .
RUN bundle install

# ✅ 先复制 Gemfile,利用缓存
COPY Gemfile Gemfile.lock ./
RUN bundle install
COPY . .

21.4.2 完整的缓存策略

FROM ruby:3.3-slim AS builder

RUN apt-get update -qq && \
    apt-get install -y --no-install-recommends build-essential libpq-dev

WORKDIR /app

# 第 1 层:Gemfile 缓存
COPY Gemfile Gemfile.lock ./
RUN bundle config set --local deployment true && \
    bundle install --jobs 4 --retry 3

# 第 2 层:应用代码
COPY . .

# 清理缓存
RUN rm -rf vendor/bundle/ruby/*/cache/*.gem && \
    find vendor/bundle/ruby/*/gems/ -name "*.c" -delete && \
    find vendor/bundle/ruby/*/gems/ -name "*.o" -delete

21.5 生产配置

21.5.1 Puma 配置

# config/puma.rb
workers ENV.fetch("WEB_CONCURRENCY") { 2 }
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count

preload_app!

port ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "production" }

plugin :tmp_restart

on_worker_boot do
  ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
end

before_fork do
  ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord)
end

21.5.2 环境变量

# .env.production
RAILS_ENV=production
RAILS_LOG_TO_STDOUT=true
RAILS_SERVE_STATIC_FILES=true
SECRET_KEY_BASE=your_secret_key_here
DATABASE_URL=postgresql://user:pass@db:5432/myapp
WEB_CONCURRENCY=2
RAILS_MAX_THREADS=5

21.5.3 健康检查

# config/routes.rb
Rails.application.routes.draw do
  get "/health", to: proc { [200, { "Content-Type" => "application/json" }, 
    [{ status: "ok", version: ENV.fetch("APP_VERSION", "unknown") }.to_json]] }
end
# Dockerfile 中添加健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

21.6 Docker Compose

21.6.1 完整的 docker-compose.yml

# docker-compose.yml
version: '3.8'

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    environment:
      - RAILS_ENV=production
      - DATABASE_URL=postgresql://postgres:password@db:5432/myapp
      - REDIS_URL=redis://redis:6379/0
      - SECRET_KEY_BASE=${SECRET_KEY_BASE}
    volumes:
      - rails_storage:/app/storage
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: password
      POSTGRES_DB: myapp
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

  worker:
    build:
      context: .
      dockerfile: Dockerfile
    command: bundle exec sidekiq
    depends_on:
      - db
      - redis
    environment:
      - RAILS_ENV=production
      - DATABASE_URL=postgresql://postgres:password@db:5432/myapp
      - REDIS_URL=redis://redis:6379/0
    restart: unless-stopped

volumes:
  postgres_data:
  redis_data:
  rails_storage:

21.6.2 常用命令

# 构建并启动
docker-compose up -d --build

# 查看日志
docker-compose logs -f web
docker-compose logs -f --tail 100

# 进入容器
docker-compose exec web bash
docker-compose exec web rails console
docker-compose exec web rails db:migrate

# 停止服务
docker-compose down

# 停止并删除数据
docker-compose down -v

# 查看状态
docker-compose ps

21.7 CI/CD 集成

21.7.1 GitHub Actions

# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: test
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
      - uses: actions/checkout@v4
      - uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true
      - run: bundle exec rspec

  build:
    needs: test
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      
      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

21.8 安全最佳实践

21.8.1 镜像安全

# 1. 使用官方基础镜像
FROM ruby:3.3-slim

# 2. 创建非 root 用户
RUN groupadd -r appuser && useradd -r -g appuser appuser

# 3. 设置正确的文件权限
COPY --chown=appuser:appuser . /app

# 4. 使用 USER 指令
USER appuser

# 5. 不要暴露不必要的端口
EXPOSE 3000

# 6. 使用 HEALTHCHECK
HEALTHCHECK CMD curl -f http://localhost:3000/health || exit 1

21.8.2 Docker 安全扫描

# 使用 Trivy 扫描漏洞
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy image myapp:latest

# 使用 Docker Scout
docker scout cves myapp:latest

21.9 动手练习

  1. 为现有应用创建 Dockerfile
# 为你的 Ruby 项目创建生产级 Dockerfile
# 包含多阶段构建和健康检查
  1. 配置 Docker Compose
# 创建完整的 docker-compose.yml
# 包含 Web、数据库、Redis、后台任务
  1. 优化镜像大小
# 比较不同构建策略的镜像大小
# 目标:小于 200MB

21.10 本章小结

要点说明
Dockerfile构建镜像的指令文件
多阶段构建减小镜像大小,提高安全性
缓存优化合理分层利用 Docker 缓存
Docker Compose多容器编排
安全非 root 用户、最小镜像、漏洞扫描
CI/CDGitHub Actions 自动构建部署

📖 扩展阅读


上一章← 第 20 章:性能优化 下一章第 22 章:最佳实践 →