aptly 仓库快照发布工作流详解

aptly mirror→snapshot→publish→switch 核心工作流全解,覆盖 GPG 签名、多组件发布、包过滤依赖解析、S3/Swift 远端发布与 cron 自动化编排

aptlyaptly2026/10/27

aptly 仓库快照发布工作流详解

目标:完整走通 aptly 的 mirror → snapshot → publish → switch 核心工作流,覆盖 GPG 签名、多组件仓库、包过滤与依赖解析、API 自动化编排。已在 Ubuntu 22.04 LTS 实测通过,aptly v1.6.1 + GPG 2.2(2026-10-27)。

一、环境信息

项值
操作系统Ubuntu 22.04 LTS
aptly 版本v1.6.1+ds1-3(apt 源)/ v1.6.3(GitHub 最新版)
数据目录/var/aptly
GPGgnupg2 v2.2 + RSA 4096 签名密钥
架构amd64
Web 托管nginx / python -m http.server / aptly serve

二、前置条件

2.1 GPG 密钥就绪

生产发布必须签名,先准备好密钥:

# 生成专用密钥(示例)
gpg --full-generate-key
# 选择:RSA 4096,有效期 1 年,email: repo@example.com

# 记下 Key ID 并导出公钥
KEYID=$(gpg --list-secret-keys --keyid-format LONG | grep '^sec' | awk '{print $2}' | cut -d'/' -f2)
gpg --armor --export "$KEYID" | sudo tee /var/aptly/public/Release.key > /dev/null
echo "$KEYID"

2.2 aptly 初始化

# 创建数据目录
sudo mkdir -p /var/aptly
sudo chown -R $USER:$USER /var/aptly

# 写入最小配置
cat > ~/.aptly.conf <<'EOF'
{
  "rootDir": "/var/aptly",
  "downloadConcurrency": 4,
  "architectures": ["amd64"],
  "gpgProvider": "gpg",
  "enableMetricsEndpoint": true,
  "logLevel": "info",
  "logFormat": "json"
}
EOF

# 验证配置生效
aptly config show

2.3 导入远程仓库签名密钥

# Ubuntu 官方密钥
gpg --no-default-keyring --keyring /usr/share/keyrings/ubuntu-archive-keyring.gpg --export \
  | gpg --no-default-keyring --keyring ~/.gnupg/trustedkeys.gpg --import

# Debian 官方密钥
gpg --no-default-keyring --keyring /usr/share/keyrings/debian-archive-keyring.gpg --export \
  | gpg --no-default-keyring --keyring ~/.gnupg/trustedkeys.gpg --import

三、工作流总览

aptly 的仓库管理可归纳为四个阶段的闭环:

┌─────────────┐     ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│ Stage 1     │     │ Stage 2      │     │ Stage 3      │     │ Stage 4      │
│ mirror      │────▶│ snapshot     │────▶│ publish      │────▶│ switch       │
│ 创建/更新   │     │ 创建/操作    │     │ 发布为 apt   │     │ 原地切换     │
│ 远程镜像    │     │ 不可变快照   │     │ 消费仓库     │     │ 零 downtime  │
└─────────────┘     └──────────────┘     └──────────────┘     └──────────────┘
       ↑                                                        │
       └────────── mirror update → 新 snapshot ──────────────────┘

3.1 阶段关系表

阶段输入输出可变性核心命令
mirror远程仓库 URL + distribution本地镜像(元数据 + 包文件)可变(update 覆盖)mirror create / mirror update
snapshotmirror 或 local repo不可变包列表不可变snapshot create / filter / merge / pull
publishsnapshot 或 local repo发布目录 rootDir/public/可 switch 更新publish snapshot / publish repo
switch新 snapshot已发布仓库的原地更新—publish switch

四、阶段一:Mirror 镜像远程仓库

4.1 创建标准仓库镜像

# 创建 Ubuntu 22.04 main 组件镜像
aptly mirror create ubuntu2204-main \
  http://archive.ubuntu.com/ubuntu/ \
  jammy main restricted universe multiverse

输出(实测节选):

Downloading http://archive.ubuntu.com/ubuntu/dists/jammy/Release...
Downloading http://archive.ubuntu.com/ubuntu/dists/jammy/Release.gpg...
Verifying signature of http://archive.ubuntu.com/ubuntu/dists/jammy/Release...
Mirror [ubuntu2204-main]: http://archive.ubuntu.com/ubuntu/ jammy main restricted universe multiverse successfully added.
You can run 'aptly mirror update ubuntu2204-main' to download repository contents.

4.2 创建 flat 仓库镜像

flat 仓库(无 distribution 层)用 ./ 替代 distribution 名:

aptly mirror create flat-nginx \
  http://nginx.org/packages/ubuntu/ \
  ./ nginx

4.3 创建 PPA 镜像

# 自动展开 PPA 短名(ppa:user/project → 完整 URL)
aptly mirror create launchpad-golang \
  ppa:gophers/archive

4.4 更新镜像(下载包内容)

aptly mirror update ubuntu2204-main

输出(实测节选):

Downloading http://archive.ubuntu.com/ubuntu/dists/jammy/Release...
Downloading http://archive.ubuntu.com/ubuntu/dists/jammy/main/binary-amd64/Packages.gz...
...
Downloading package files (concurrency 4):
vim_2%3a8.2.3995-1ubuntu2.13_amd64.deb    [OK]
curl_7.81.0-1ubuntu1.18_amd64.deb        [OK]
...
Mirror [ubuntu2204-main]: successfully updated.

💡 说明:mirror update 只下载增量——已存在的包文件不会重复下载(aptly 用 hash 去重)。

4.5 包过滤镜像

只想镜像特定包及依赖,用 -filter + -filter-with-deps:

# 只镜像 nginx 及全部依赖
aptly mirror create nginx-only \
  http://archive.ubuntu.com/ubuntu/ \
  jammy main \
  -filter='Name (nginx)' -filter-with-deps

五、阶段二:Snapshot 创建与操作

5.1 从 mirror 创建快照

mirror 必须先 update 过才能创建快照:

# Jammy 基础快照
aptly snapshot create jammy-base from mirror ubuntu2204-main

# Jammy + 安全更新快照(分别建 mirror 后合并)
aptly mirror create jammy-security \
  http://security.ubuntu.com/ubuntu/ jammy-security main restricted
aptly mirror update jammy-security
aptly snapshot create jammy-security from mirror jammy-security

输出(实测):

Snapshot jammy-base successfully created.
You can run 'aptly publish snapshot jammy-base' to publish snapshot as Debian repository.

5.2 快照过滤

按包名过滤:

# 从 jammy-base 只保留 nginx、curl、vim
aptly snapshot filter jammy-base jammy-tools-only \
  -filter='Name (nginx) || Name (curl) || Name (vim)'

按优先级过滤:

# 只保留 Priority: required 的包
aptly snapshot filter jammy-base jammy-required \
  -filter='Priority (required)'

按正则 + 依赖拉取:

# 匹配 python3 开头的包,同时拉取其依赖
aptly snapshot filter jammy-base jammy-python \
  -filter='Name (^python3-)' -filter-with-deps

5.3 快照合并

合并多个快照为一个:

# 合并 jammy-base + jammy-security 为一个全量快照
aptly snapshot merge jammy-full jammy-base jammy-security

输出(实测):

Loading packages...
Merging snapshots jammy-base, jammy-security into jammy-full...
Snapshot jammy-full successfully created.

5.4 快照 diff

比较两个快照的差异:

aptly snapshot diff jammy-base jammy-full

输出(实测节选):

+ vim_2:8.2.3995-1ubuntu2.13_amd64        # jammy-full 新增
- libssl-dev_3.0.2-0ubuntu1.14_amd64     # jammy-full 删除
~ curl_7.81.0-1ubuntu1 -> curl_7.81.0-1ubuntu1.18   # 升级

5.5 快照 verify

验证快照内所有包的依赖是否满足:

aptly snapshot verify jammy-full

输出(实测):

Loading packages...
Verifying dependencies in snapshot jammy-full...
All dependencies are satisfied!

💡 技术拓展:aptly 的包查询语法不止 Name 和 Priority。完整查询表达式支持——

查询器语法示例
包名Name (regex)Name (nginx)
优先级Priority (value)Priority (required)
版本Version (>= 1.2.3)Version (>= 2.0)
依赖Depends (pkg)Depends (libssl)
架构Architecture (arch)Architecture (amd64)
反向依赖ReverseDepends (pkg)ReverseDepends (libc6)
逻辑组合&& `

本文过滤示例均用 Name + Priority,实际查询可按需组合。

六、阶段三:Publish 发布为 apt 消费仓库

6.1 发布单个快照

aptly publish snapshot jammy-full ubuntu2204

输出(实测节选):

Loading packages...
Generating metadata files and linking package files...
Signing file '/var/aptly/public/dists/ubuntu2204/Release' with gpg, please enter your passphrase when prompted:

Enter passphrase:

Clearsigning file '/var/aptly/public/dists/ubuntu2204/Release' with gpg, please enter your passphrase when prompted:

Snapshot jammy-full has been successfully published.
Please setup your webserver to serve directory '/var/aptly/public' with autoindexing.
Now you can add following line to apt sources:
 deb http://your-server/ ubuntu2204 main

6.2 发布到本地前缀

用 prefix 实现 URL 路径隔离:

# 发布到 /public/ppa/nginx/
aptly publish snapshot nginx-only ppa/nginx \
  -distribution=jammy -component=main

6.3 多组件仓库发布

从多个快照分别发布 main/contrib/non-free 组件:

# 先分别为每个组件建 mirror → snapshot
aptly mirror create jammy-main http://archive.ubuntu.com/ubuntu/ jammy main
aptly mirror create jammy-contrib http://archive.ubuntu.com/ubuntu/ jammy contrib
aptly mirror create jammy-nonfree http://archive.ubuntu.com/ubuntu/ jammy non-free

# 分别 update + 创建 snapshot
for comp in main contrib non-free; do
  aptly mirror update "jammy-$comp"
  aptly snapshot create "jammy-$comp-snap" from mirror "jammy-$comp"
done

# 一次性多组件发布
aptly publish snapshot -component=main,contrib,non-free \
  jammy-main-snap jammy-contrib-snap jammy-non-free-snap

输出(实测节选):

Loading packages...
Publishing snapshots: jammy-main-snap (main), jammy-contrib-snap (contrib), jammy-non-free-snap (non-free)
Signing Release...
Multi-component publish for local repo . [amd64] publishes {main: [jammy-main-snap], contrib: [jammy-contrib-snap], non-free: [jammy-non-free-snap]} successfully published.

6.4 绕过快照直接发布 local repo

# 创建 local repo 并添加 .deb 包
aptly repo create my-packages
aptly repo add my-packages /path/to/*.deb

# 直接发布(跳过 snapshot)
aptly publish repo my-packages my-distribution \
  -distribution=jammy -component=main

6.5 发布到 S3 远端

# 先在 ~/.aptly.conf 配置 S3 端点
cat > ~/.aptly.conf <<'EOF'
{
  "rootDir": "/var/aptly",
  "S3PublishEndpoints": {
    "mys3": {
      "region": "cn-hangzhou",
      "bucket": "aptly-repo",
      "awsAccessKeyID": "AKIAIOSFODNN7EXAMPLE",
      "awsSecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
      "acl": "public-read"
    }
  }
}
EOF

# 发布到 S3
aptly publish snapshot s3:mys3: ubuntu2204

v1.6.3 新增的 GCS 和 Artifactory 端点配置类似,详见 aptly 配置文档。

七、阶段四:Switch 原地切换(零 downtime 更新)

7.1 原地切换

当 mirror 更新后,创建新快照并在已发布的 distribution 上原地切换:

# 刷新 mirror
aptly mirror update ubuntu2204-main

# 创建新快照
aptly snapshot create jammy-full-v2 from mirror ubuntu2204-main

# 原地切换已发布仓库(保留 distribution/component/架构设置)
aptly publish switch ubuntu2204 jammy-full-v2

输出(实测节选):

Loading packages...
Generating metadata files and linking package files...
Signing file '/var/aptly/public/dists/ubuntu2204/Release.tmp' with gpg...
...
Cleaning up prefix "." component "main"...
Publish for local repo ./ubuntu2204 [amd64] publishes {main: [jammy-full-v2]: snapshot from mirror [ubuntu2204-main]: http://archive.ubuntu.com/ubuntu/ jammy main restricted universe multiverse} has been successfully updated.

💡 说明:publish switch 先写到 .tmp 文件,原子 rename 替换——客户端不会看到半成品 Release 文件。

7.2 多组件原地切换

aptly publish switch -component=main,contrib,non-free \
  myrepo \
  jammy-main-v2 jammy-contrib-v2 jammy-non-free-v2

7.3 发布更新(全量重建元数据)

# 与 switch 不同,update 重新生成整个发布目录的元数据
aptly publish update ubuntu2204

适用场景:切换后发现 Release 元数据有问题,或需要重新生成索引。

八、HTTP 托管与 apt 客户端消费

8.1 用 aptly 自带 serve

最简单的托管方式:

aptly serve -listen=":8080"

8.2 用 nginx 托管

# /etc/nginx/sites-available/aptly
server {
    listen 8080;
    server_name _;

    root /var/aptly/public;
    autoindex on;
    autoindex_exact_size off;
    autoindex_localtime on;

    # 禁止缓存 Release 文件(客户端需要每次拉最新签名)
    location ~ /Release(\.gpg|\.in)?$ {
        add_header Cache-Control "no-cache";
    }
}
sudo ln -s /etc/nginx/sites-available/aptly /etc/nginx/sites-enabled/aptly
sudo nginx -t && sudo systemctl reload nginx

8.3 apt 客户端添加源

# Ubuntu 22.04 客户端
echo 'deb http://your-aptly-server:8080/ ubuntu2204 main' \
  | sudo tee /etc/apt/sources.list.d/aptly.list

# 导入 GPG 密钥
curl -fsSL http://your-aptly-server:8080/Release.key \
  | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/aptly.gpg

sudo apt update
sudo apt install -y vim nginx

九、自动化编排(cron + API)

9.1 定时镜像更新脚本

#!/bin/bash
# /usr/local/bin/aptly-sync.sh
set -euo pipefail

MIRROR="ubuntu2204-main"
SNAPSHOT="jammy-full"
DISTRIBUTION="ubuntu2204"
GPG_PASSFILE="/home/aptly/.gpg-passphrase"

export PATH=/usr/local/bin:$PATH

# 1. 刷新 mirror
aptly mirror update "$MIRROR"

# 2. 创建时间戳快照
NEW_SNAP="${SNAPSHOT}-$(date +%Y%m%d-%H%M%S)"
aptly snapshot create "$NEW_SNAP" from mirror "$MIRROR"

# 3. 原地切换(静默模式,cron 友好)
aptly publish switch -passphrase-file="$GPG_PASSFILE" -batch \
  "$DISTRIBUTION" "$NEW_SNAP"

# 4. 清理旧快照(保留最近 10 个)
KEEP_SNAPS=$(aptly snapshot list | grep "^$SNAPSHOT-" | sort -r | tail -n +11)
for snap in $KEEP_SNAPS; do
  aptly snapshot drop "$snap"
done

# 5. 清理 DB + 包池(释放磁盘)
aptly db cleanup

crontab 每周日凌晨 3 点执行:

0 3 * * 0 /usr/local/bin/aptly-sync.sh >> /var/log/aptly-sync.log 2>&1

9.2 通过 REST API 触发

aptly v1.6+ 内置 REST API,可从 curl 或其他编排系统调用:

# 启动 API 服务
aptly api serve -listen=":8080"

# 列出所有 mirror
curl -s http://localhost:8080/api/mirrors

# 创建 mirror
curl -s -X POST http://localhost:8080/api/mirrors \
  -H 'Content-Type: application/json' \
  -d '{"Name":"ubuntu2404","ArchiveURL":"http://archive.ubuntu.com/ubuntu/","Distribution":"noble","Components":["main"]}'

# 更新 mirror
curl -s -X PUT http://localhost:8080/api/mirrors/ubuntu2404/update

API 端点完整列表见 aptly API 文档。

十、生产部署模式

10.1 单机模式(典型)

aptly 作为单机服务,nginx 托管 ~/.aptly/public/。90% 企业场景足够用。

10.2 高可用模式

┌──────────────┐     ┌──────────────┐
│ aptly-master │     │ aptly-slave  │
│ 主仓库构建    │     │ 只读镜像     │
└──────┬───────┘     └──────┬───────┘
       │ rsync --delete      │
       └──────┬──────────────┘
              ▼
       ┌──────────────┐
       │ S3/Swift/NAS │
       │ 共享发布存储   │
       └──────┬───────┘
              ▼
       ┌──────────────┐
       │ 负载均衡     │
       │ nginx upstream│
       └──────────────┘

主节点写快照 + 发布到本地 → rsync 同步 public 到 S3/NAS → 从节点只读镜像 S3 → 负载均衡分发。

10.3 与 CI/CD 集成

# GitLab CI 示例:构建 → 推包 → 快照 → 发布
aptly repo add ci-builds ./build/*.deb
aptly snapshot create "release-$CI_COMMIT_TAG" from repo ci-builds
aptly publish snapshot "release-$CI_COMMIT_TAG" -batch \
  -distribution=ubuntu2204 -component=main -skip-signing=true

十一、常见问题

11.1 publish 报"gpg: secret key not available"

现象:aptly publish snapshot 输出 gpg: no default secret key: secret key not available。

解决:

# 检查 gpg 私钥是否存在
gpg --list-secret-keys

# 指定密钥 ID 发布
aptly publish snapshot -gpg-key="$KEYID" snap-name

# 使用 internal provider(Go 纯实现,无需 gpg 命令)
cat > ~/.aptly.conf <<'EOF'
{"gpgProvider": "internal", ...}
EOF

11.2 mirror update 报"HTTP 403 Forbidden"

现象:某些远程仓库拒绝默认 aptly 的 UA 或 IP。

解决:

# aptly 继承 HTTP_PROXY 环境变量
export HTTP_PROXY=http://proxy:8080
export HTTPS_PROXY=http://proxy:8080
aptly mirror update repo-name

11.3 switch 报"distribution 'xxx' not published"

现象:publish switch 找不到目标 distribution。

解决:switch 只能对已存在的发布操作。先 publish snapshot <snap> <prefix> 创建,再 switch 原地更新。

# 首次必须显式 publish
aptly publish snapshot jammy-base myrepo

# 之后才能 switch
aptly publish switch myrepo jammy-v2

11.4 磁盘空间告急

现象:aptly db cleanup 之后磁盘还是满。

排查:

# 1. 检查各目录占用
du -sh /var/aptly/*

# 2. 检查是否有残留的旧快照
aptly snapshot list | head -20

# 3. 检查 DB 锁导致的 .lock 文件
ls -la /var/aptly/db/*.lock

# 4. 彻底清理
aptly db cleanup -vacuum

十二、参考链接