Jekyll 静态站点完全教程 / 第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/CD | GitHub Actions 构建 Docker 镜像并部署 |
| 预览环境 | 每个 PR 自动创建独立预览 |
下一章:最佳实践