强曰为道

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

第13章:Docker 化构建

第13章:Docker 化构建

13.1 为什么使用 Docker

Docker 为 Jekyll 提供了一致的构建环境,解决了"在我机器上能跑"的问题。

Docker 化的优势

优势说明
环境一致性开发、CI、生产使用相同环境
无需安装 Ruby开发者不需要在本机安装 Ruby
快速上手新成员 docker-compose up 即可开始
CI/CD 友好Docker 镜像可直接用于 CI
多项目隔离不同项目使用不同 Ruby/Jekyll 版本

13.2 基础 Dockerfile

简单版

# Dockerfile
FROM ruby:3.2-slim

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

WORKDIR /srv/jekyll

# 先复制 Gemfile,利用 Docker 缓存层
COPY Gemfile Gemfile.lock ./
RUN bundle install --jobs 4 --retry 3

# 复制源文件
COPY . .

# 构建
RUN JEKYLL_ENV=production bundle exec jekyll build

# 使用 Nginx 托管
FROM nginx:alpine
COPY --from=0 /srv/jekyll/_site /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Nginx 配置

# nginx.conf
server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html;

    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml;

    location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    error_page 404 /404.html;
}

构建和运行

# 构建镜像
docker build -t my-jekyll-site .

# 运行容器
docker run -d -p 8080:80 --name jekyll-site my-jekyll-site

# 访问 http://localhost:8080

13.3 多阶段构建

多阶段构建分离了构建环境和运行环境,生成更小的最终镜像。

# Dockerfile.multistage

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

RUN apt-get update -qq && \
    apt-get install -y --no-install-recommends \
    build-essential \
    git \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /srv/jekyll

COPY Gemfile Gemfile.lock ./
RUN bundle config set --local deployment 'true' && \
    bundle config set --local without 'development test' && \
    bundle install --jobs 4 --retry 3

COPY . .

RUN JEKYLL_ENV=production bundle exec jekyll build \
    --destination /srv/jekyll/_site

# ===== 阶段2:测试(可选) =====
FROM builder AS tester

RUN bundle config set --local without '' && \
    bundle install && \
    bundle exec htmlproofer /srv/jekyll/_site \
    --disable-external \
    --allow-hash-href \
    --check-html

# ===== 阶段3:生产 =====
FROM nginx:1.25-alpine AS production

# 复制构建产物
COPY --from=builder /srv/jekyll/_site /usr/share/nginx/html

# 复制 Nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
    CMD curl -f http://localhost/ || exit 1

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

镜像大小对比

构建方式镜像大小
单阶段(含 Ruby)~800MB
多阶段(Nginx)~25MB

13.4 开发环境 Dockerfile

# Dockerfile.dev
FROM ruby:3.2-slim

RUN apt-get update -qq && \
    apt-get install -y --no-install-recommends \
    build-essential \
    git \
    nodejs \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /srv/jekyll

COPY Gemfile Gemfile.lock ./
RUN bundle install --jobs 4 --retry 3

EXPOSE 4000 35729

CMD ["bundle", "exec", "jekyll", "serve", \
     "--host", "0.0.0.0", \
     "--port", "4000", \
     "--livereload", \
     "--livereload-port", "35729", \
     "--drafts", \
     "--force_polling"]
# 开发模式运行
docker build -f Dockerfile.dev -t jekyll-dev .
docker run -it --rm \
    -p 4000:4000 \
    -p 35729:35729 \
    -v $(pwd):/srv/jekyll \
    jekyll-dev

注意事项

  • --force_polling 在 Docker 卷挂载时必需(inotify 在跨文件系统时不工作)
  • Livereload 需要暴露 35729 端口
  • 使用 -v 挂载实现热重载

13.5 Docker Compose

docker-compose.yml

# docker-compose.yml
version: '3.8'

services:
  # 开发环境
  dev:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "4000:4000"
      - "35729:35729"
    volumes:
      - .:/srv/jekyll
      - bundle_cache:/usr/local/bundle
    environment:
      - JEKYLL_ENV=development
    command: >
      bundle exec jekyll serve
      --host 0.0.0.0
      --port 4000
      --livereload
      --livereload-port 35729
      --drafts
      --force_polling
      --incremental

  # 生产构建
  prod:
    build:
      context: .
      dockerfile: Dockerfile
      target: production
    ports:
      - "8080:80"
    restart: unless-stopped

  # 构建服务(仅执行构建,用于 CI)
  build:
    build:
      context: .
      dockerfile: Dockerfile
      target: builder
    volumes:
      - ./_site:/srv/jekyll/_site
    environment:
      - JEKYLL_ENV=production
    command: bundle exec jekyll build --destination /srv/jekyll/_site

volumes:
  bundle_cache:

使用 Docker Compose

# 开发模式(热重载)
docker-compose up dev

# 生产模式
docker-compose up -d prod

# 仅构建
docker-compose run --rm build

# 查看日志
docker-compose logs -f dev

# 停止所有服务
docker-compose down

# 重建镜像
docker-compose build --no-cache

13.6 GitHub Actions 使用 Docker

# .github/workflows/docker-build.yml
name: Docker Build & Deploy

on:
  push:
    branches: [main]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            user/jekyll-site:latest
            user/jekyll-site:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Deploy to server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            docker pull user/jekyll-site:latest
            docker-compose -f /opt/jekyll/docker-compose.yml up -d

13.7 预览环境(Preview Environments)

每个 Pull Request 自动创建独立的预览环境。

Netlify 预览(自动)

# .github/workflows/preview.yml
name: PR Preview

on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.2'
          bundler-cache: true

      - name: Build
        run: JEKYLL_ENV=production bundle exec jekyll build

      - name: Deploy Preview
        uses: nwtgck/actions-netlify@v2
        with:
          publish-dir: './_site'
          deploy-message: "Preview for PR #${{ github.event.pull_request.number }}"
        env:
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}

Docker 预览环境

# .github/workflows/pr-preview-docker.yml
name: Docker PR Preview

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build preview image
        run: |
          docker build \
            --build-arg JEKYLL_ENV=preview \
            -t jekyll-preview:pr-${{ github.event.pull_request.number }} \
            .

      - name: Deploy to preview server
        run: |
          # 部署到临时容器
          docker run -d \
            --name preview-${{ github.event.pull_request.number }} \
            -p "808${{ github.event.pull_request.number }}:80" \
            jekyll-preview:pr-${{ github.event.pull_request.number }}

      - name: Comment PR with preview URL
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: `🚀 Preview deployed! View at: http://preview.example.com:808${context.issue.number}`
            })

13.8 Docker 最佳实践

.dockerignore

# .dockerignore
.git
.github
.gitignore
.sass-cache
.jekyll-cache
.jekyll-metadata
_site
vendor
node_modules
*.md
!README.md
LICENSE
Rakefile
docker-compose*.yml
Dockerfile*

健康检查

# Dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:4000/ || exit 1

镜像标签策略

# 构建并打标签
docker build -t my-jekyll-site:latest .
docker tag my-jekyll-site:latest my-jekyll-site:2025.01.15
docker tag my-jekyll-site:latest my-jekyll-site:abc123

# 推送
docker push my-jekyll-site:latest
docker push my-jekyll-site:2025.01.15

13.9 业务场景:多环境管理

# docker-compose.yml
version: '3.8'

services:
  # 本地开发
  dev:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports: ["4000:4000"]
    volumes:
      - .:/srv/jekyll
    environment:
      JEKYLL_ENV: development

  # 预发布环境
  staging:
    build:
      context: .
      dockerfile: Dockerfile
    ports: ["8081:80"]
    environment:
      JEKYLL_ENV: staging

  # 生产环境
  production:
    image: my-jekyll-site:latest
    ports: ["80:80"]
    restart: always
    environment:
      JEKYLL_ENV: production

13.10 扩展阅读


本章小结

要点说明
Dockerfile包含 Ruby、依赖、源文件的完整构建环境
多阶段构建分离构建和运行环境,大幅减小镜像体积
Docker Compose管理开发、测试、生产多环境
开发模式挂载卷 + force_polling 实现热重载
CI/CDGitHub Actions 构建 Docker 镜像并部署
预览环境每个 PR 自动创建独立预览

下一章:最佳实践