强曰为道

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

第十一章 Docker 集成

第十一章:Docker 集成

11.1 概述

Guix 与 Docker 的集成是将 Guix 的函数式包管理优势带入容器化部署工作流的重要方式。通过 guix pack,你可以将 Guix 管理的包集合导出为 Docker 镜像、OCI 镜像或其他格式。

11.1.1 Guix + Docker 的优势

优势说明
可重现镜像相同 manifest 总是产出相同镜像
最小镜像只包含声明的包,无冗余
确定性哈希标识的包路径,可审计
安全沙箱构建,减少供应链风险
兼容性标准 Docker/OCI 格式,任何容器运行时可用

11.1.2 工作流概览

manifest.scm  →  guix pack -f docker  →  image.tar.gz  →  docker load  →  运行容器
   (声明)         (构建命令)           (镜像文件)      (导入)        (部署)

11.2 基本镜像构建

11.2.1 最简单的 Docker 镜像

# 创建包含 Python 的 Docker 镜像
guix pack -f docker -o python-image.tar.gz \
  python python-requests python-flask

# 导入到 Docker
docker load < python-image.tar.gz
# 输出:Loaded image: ...

# 运行
docker run --rm -it <image-id> python3 -c "print('Hello from Guix!')"

11.2.2 指定入口点

# 设置默认入口点
guix pack -f docker \
  --entry-point=/bin/python3 \
  -o python-app.tar.gz \
  python python-flask gunicorn

# 运行时直接执行
docker run --rm -it <image-id> -c "print('hello')"

11.2.3 指定镜像名称和标签

# 指定名称和标签
guix pack -f docker \
  --image-tag="myapp:1.0" \
  -o myapp-1.0.tar.gz \
  python python-flask

# 导入后镜像名为 myapp:1.0
docker load < myapp-1.0.tar.gz
docker images | grep myapp

11.3 使用 Manifest 构建镜像

11.3.1 应用镜像 Manifest

;; webapp-manifest.scm
(specifications->manifest
  '("python"
    "python-flask"
    "python-gunicorn"
    "python-redis"
    "python-psycopg2"
    "openssl"
    "ca-certificates"
    "tzdata"))
guix pack -f docker \
  --image-tag="webapp:latest" \
  --manifest=webapp-manifest.scm \
  --entry-point=/bin/gunicorn \
  -o webapp.tar.gz

11.3.2 多阶段 Manifest

;; manifest.scm — 完整的 Web 应用环境
(use-modules (guix gexp))

(specifications->manifest
  '(;; 运行时
    "python"
    "python-flask"
    "python-gunicorn"
    "python-redis"
    "python-sqlalchemy"
    "python-psycopg2"
    ;; 工具
    "openssl"
    "ca-certificates"
    "tzdata"
    "coreutils"        ; ls, cat 等
    "bash"             ; Shell
    "curl"))            ; 健康检查

11.3.3 复杂的 Manifest 配置

(use-modules (gnu packages)
             (gnu packages python)
             (gnu packages python-web)
             (gnu packages databases))

;; 使用 Scheme 表达式精细控制
(packages->manifest
  (list
    ;; Python 运行时
    python-3.11

    ;; Web 框架
    python-flask
    python-gunicorn

    ;; 数据库
    python-sqlalchemy
    python-alembic
    python-psycopg2

    ;; 缓存
    python-redis

    ;; 工具
    openssl
    (specification->package "ca-certificates")))

11.4 应用打包实战

11.4.1 Flask 应用 Docker 镜像

项目结构:

my-flask-app/
├── app/
│   ├── __init__.py
│   ├── routes.py
│   └── models.py
├── requirements.txt
├── guix-manifest.scm
├── Dockerfile.guix
└── wsgi.py

Guix Manifest:

;; guix-manifest.scm
(specifications->manifest
  '("python"
    "python-flask"
    "python-gunicorn"
    "python-sqlalchemy"
    "python-psycopg2"
    "python-redis"
    "openssl"
    "ca-certificates"))

构建并打包:

# 使用 Guix 构建镜像
guix pack -f docker \
  --image-tag="my-flask-app:1.0" \
  --manifest=guix-manifest.scm \
  --entry-point=/bin/gunicorn \
  -o app-image.tar.gz

# 或者使用 Dockerfile 混合模式

混合 Dockerfile:

# Dockerfile.guix — 使用 Guix 构建的镜像作为基础
FROM guix-pack:latest as builder

# 复制应用代码
COPY app/ /app/
COPY wsgi.py /app/

# 设置工作目录
WORKDIR /app

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

# 暴露端口
EXPOSE 8000

# 启动应用
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "wsgi:app"]

11.4.2 静态网站镜像

;; nginx-manifest.scm
(specifications->manifest
  '("nginx"
    "openssl"))
guix pack -f docker \
  --image-tag="static-site:latest" \
  --manifest=nginx-manifest.scm \
  --entry-point=/bin/nginx \
  -o static-site.tar.gz
FROM static-site:latest

# 复制网站文件
COPY dist/ /var/www/html/

# 使用自定义 nginx 配置
COPY nginx.conf /etc/nginx/nginx.conf

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

11.4.3 微服务镜像

;; api-gateway-manifest.scm
(specifications->manifest
  '("nginx"
    "openssl"
    "curl"))

;; user-service-manifest.scm
(specifications->manifest
  '("python"
    "python-flask"
    "python-sqlalchemy"
    "python-jwt"))

;; order-service-manifest.scm
(specifications->manifest
  '("python"
    "python-flask"
    "python-redis"
    "python-celery"))
# 构建多个服务镜像
for service in api-gateway user-service order-service; do
  guix pack -f docker \
    --image-tag="${service}:latest" \
    --manifest=${service}-manifest.scm \
    --entry-point=/bin/python3 \
    -o ${service}.tar.gz
done

11.5 高级打包选项

11.5.1 可重定位包(Relocatable)

# 创建可重定位的包
guix pack --relocatable --relocatable-name=myapp \
  -f tarball \
  python python-flask

# 这个包可以在任意位置解压使用
tar xf myapp.tar.gz
./myapp/bin/python3

11.5.2 SquashFS 镜像

# SquashFS 是只读压缩文件系统
guix pack -f squashfs \
  --manifest=manifest.scm \
  -o app.squashfs

# 挂载使用
sudo mount app.squashfs /mnt/app
/mnt/app/bin/python3

11.5.3 自定义镜像配置

# 完整的 guix pack 选项
guix pack -f docker \
  --image-tag="myapp:1.0" \
  --manifest=manifest.scm \
  --entry-point=/bin/python3 \
  --max-jobs=4 \
  --no-grafts \
  --save-provenance \
  -o myapp.tar.gz
选项说明
--image-tag镜像名称和标签
--manifest使用 manifest 文件
--entry-point容器入口点
--max-jobs并行构建数
--no-grafts跳过 graft(安全补丁应用)
--save-provenance保存包定义到镜像中
--relocatable创建可重定位包
-o输出文件名

11.6 Docker Compose 集成

11.6.1 多服务部署

# docker-compose.yml
version: "3.8"
services:
  web:
    image: my-flask-app:1.0
    build:
      context: .
      dockerfile: Dockerfile.guix
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/myapp
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - db
      - redis

  db:
    image: guix-postgres:15
    build:
      context: .
      dockerfile: Dockerfile.postgres.guix
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass

  redis:
    image: guix-redis:7
    build:
      context: .
      dockerfile: Dockerfile.redis.guix
    volumes:
      - redis_data:/data

volumes:
  postgres_data:
  redis_data:

11.6.2 构建脚本

#!/bin/bash
# build-images.sh — 构建所有 Guix Docker 镜像

set -e

echo "Building Guix Docker images..."

# 构建 Web 应用镜像
echo "Building web app..."
guix pack -f docker \
  --image-tag="my-flask-app:1.0" \
  --manifest=webapp-manifest.scm \
  --entry-point=/bin/gunicorn \
  -o webapp.tar.gz

# 构建 PostgreSQL 镜像
echo "Building PostgreSQL..."
guix pack -f docker \
  --image-tag="guix-postgres:15" \
  --manifest=postgres-manifest.scm \
  --entry-point=/bin/postgres \
  -o postgres.tar.gz

# 构建 Redis 镜像
echo "Building Redis..."
guix pack -f docker \
  --image-tag="guix-redis:7" \
  --manifest=redis-manifest.scm \
  --entry-point=/bin/redis-server \
  -o redis.tar.gz

# 导入所有镜像
echo "Loading images..."
docker load < webapp.tar.gz
docker load < postgres.tar.gz
docker load < redis.tar.gz

echo "Done! All images loaded."
docker images | grep guix

11.7 CI/CD 中的镜像构建

11.7.1 GitLab CI

# .gitlab-ci.yml
stages:
  - build
  - test
  - deploy

variables:
  IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

build-image:
  stage: build
  image: guix
  script:
    - guix pull
    - guix pack -f docker
        --image-tag="$IMAGE_TAG"
        --manifest=manifest.scm
        --entry-point=/bin/python3
        -o app.tar.gz
    - docker load < app.tar.gz
    - docker push $IMAGE_TAG
  artifacts:
    paths:
      - app.tar.gz

test-image:
  stage: test
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker load < app.tar.gz
    - docker run --rm $IMAGE_TAG python3 -m pytest /app/tests/

deploy:
  stage: deploy
  only:
    - main
  script:
    - docker load < app.tar.gz
    - docker tag $IMAGE_TAG $CI_REGISTRY_IMAGE:latest
    - docker push $CI_REGISTRY_IMAGE:latest

11.7.2 GitHub Actions

# .github/workflows/docker.yml
name: Build Docker Image
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

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

      - name: Install Guix
        run: |
          wget https://git.savannah.gnu.org/cgit/guix.git/plain/etc/guix-install.sh
          chmod +x guix-install.sh
          sudo ./guix-install.sh
          guix pull

      - name: Build Docker Image
        run: |
          guix pack -f docker \
            --image-tag="myapp:${{ github.sha }}" \
            --manifest=manifest.scm \
            -o app.tar.gz

      - name: Load and Tag
        run: |
          docker load < app.tar.gz
          docker tag myapp:${{ github.sha }} ghcr.io/${{ github.repository }}:latest
          docker tag myapp:${{ github.sha }} ghcr.io/${{ github.repository }}:${{ github.sha }}

      - name: Push to Registry
        if: github.event_name == 'push'
        run: |
          echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
          docker push ghcr.io/${{ github.repository }}:latest
          docker push ghcr.io/${{ github.repository }}:${{ github.sha }}

11.8 镜像优化

11.8.1 减小镜像体积

技巧说明
精简依赖只在 manifest 中声明必需的包
使用输出package:out 只包含运行时文件
移除文档排除 :doc 输出
最小基础包使用 coreutils 替代完整 GNU 工具集
;; 精简 manifest
(specifications->manifest
  '("python:minimal"        ; 最小 Python
    "python-flask"
    "ca-certificates"))

11.8.2 多层构建策略

;; 运行时最小集
(define %runtime-packages
  (specifications->packages
    '("python:minimal"
      "ca-certificates"
      "tzdata")))

;; 开发工具(不在最终镜像中)
(define %dev-packages
  (specifications->packages
    '("gcc-toolchain"
      "python-pytest"
      "python-mypy")))

11.9 安全考虑

11.9.1 镜像安全检查

# 验证镜像内容
guix pack -f docker --save-provenance ...

# 镜像中会包含 /gnu/store 中所有包的定义
# 可以审计每个包的构建来源

11.9.2 安全最佳实践

实践说明
固定版本使用 channels.scm 锁定版本
签名验证验证通道签名
最小权限容器内使用非 root 用户
只读文件系统使用 --read-only 运行
健康检查添加 HEALTHCHECK
定期更新定期重建镜像获取安全补丁

11.9.3 非 root 容器

FROM guix-app:latest

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

# 设置目录权限
RUN mkdir -p /app/data && chown -R appuser:appuser /app

# 切换到非 root 用户
USER appuser

CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]

11.10 Guix System 的 Docker/OCI 导出

11.10.1 导出完整系统容器

;; system-container.scm
(use-modules (gnu)
             (gnu system linux-container))

(operating-system
  (host-name "guix-container")
  (timezone "Asia/Shanghai")
  (locale "zh_CN.utf8")
  (bootloader (bootloader-configuration
                (bootloader grub-bootloader)
                (targets '("/dev/null"))))
  (file-systems
    (cons* (file-system
              (device "tmpfs")
              (mount-point "/")
              (type "tmpfs"))
            %base-file-systems))
  (packages (list python nginx))
  (services
    (cons* (service nginx-service-type
              (nginx-configuration
                (server-blocks
                  (list (nginx-server-configuration
                          (server-name '("localhost"))
                          (listen '("80"))
                          (root "/var/www/html"))))))
            %base-services)))
# 构建系统容器
guix system container system-container.scm

11.11 Docker 命令速查表

操作命令
构建镜像guix pack -f docker -o image.tar.gz <packages>
使用 Manifestguix pack -f docker --manifest=m.scm -o image.tar.gz
指定标签guix pack -f docker --image-tag="name:tag"
设置入口guix pack -f docker --entry-point=/bin/app
导入镜像docker load < image.tar.gz
运行容器docker run --rm -it <image> /bin/bash
保存溯源guix pack -f docker --save-provenance
SquashFSguix pack -f squashfs
Tarballguix pack -f tarball
可重定位guix pack --relocatable

11.12 总结

本章讲解了 Guix 与 Docker 的集成:

  1. 基本概念——Guix pack 生成 Docker/OCI 镜像
  2. 镜像构建——从简单到复杂的各种场景
  3. Manifest 管理——声明式镜像依赖管理
  4. 应用打包——Flask、静态网站、微服务
  5. CI/CD 集成——自动化镜像构建和发布
  6. 镜像优化——减小体积和提升安全性
  7. 最佳实践——安全、可重现的镜像管理

下一章我们将总结最佳实践和工作流。


扩展阅读