BaGet 私服搭建
BaGet 私服搭建
简介
BaGet 是一个轻量级的 NuGet 服务端实现,用纯 .NET 编写,兼容 NuGet 协议 v3,用于搭建私有 NuGet 包管理服务器。它支持 Docker 部署、SQLite/MySQL/PostgreSQL 数据库、文件系统或云存储(Azure Blob、Amazon S3),可以直接用 dotnet nuget push 和 Visual Studio 还原包。适合企业内部管理私有 NuGet 包,特别是在需要控制包分发、加速 CI/CD 构建或离线环境中使用的场景。
在微服务架构下,每个团队可能维护多个类库项目(基础设施封装、领域模型、公共工具等),BaGet 提供了一个低成本的统一分发点,避免了通过共享文件系统或源码引用来管理内部依赖的混乱局面。
NuGet 私服在架构中的位置
开发者工作站 CI/CD 流水线 生产环境
| | |
v v v
nuget.config ──> BaGet <──── dotnet restore ──> 构建产物
私服
| |
+-------> NuGet.org <------+
(上游镜像)特点
Docker 部署
前置条件
在开始部署之前,请确保服务器已安装以下组件:
# 检查 Docker 版本
docker --version
# 推荐 Docker 20.10+ 版本
# 检查 Docker Compose 版本
docker compose version
# 推荐 Docker Compose v2+创建配置文件
cd /var
mkdir baget && cd /var/baget
vim baget.env配置内容:
# ============================================================
# BaGet 配置文件
# ============================================================
# API 密钥,用于发布包的身份验证
# 生产环境务必使用强密码,建议 32 位以上随机字符串
ApiKey=Your-API-Key-Change-This
# 存储配置
# 可选值: FileSystem, AzureBlobStorage, AmazonS3
Storage__Type=FileSystem
Storage__Path=/var/baget/packages
# 数据库配置
# 可选值: Sqlite, MySql, PostgreSql
Database__Type=Sqlite
Database__ConnectionString=Data Source=/var/baget/baget.db
# 搜索引擎
# 可选值: Database, AzureSearch
Search__Type=Database
# 启用 NuGet.org 镜像(缓存官方包)
# 开启后,当私服没有某个包时会自动从 NuGet.org 拉取并缓存
Mirror__Enabled=true
# 镜像包的过期时间(分钟)
# 超过此时间后会重新检查 NuGet.org 是否有更新版本
Mirror__PackageCacheDurationInMinutes=60启动容器
# 创建数据目录
mkdir -p /var/baget/baget-data
# 拉取镜像
docker pull loicsharma/baget
# 运行容器
docker run --name nuget-server \
--restart=always \
-d -p 5555:80 \
--env-file baget.env \
-v "$(pwd)/baget-data:/var/baget" \
loicsharma/baget:latest关键参数说明
| 参数 | 说明 | 生产建议 |
|---|---|---|
--name | 容器名称,方便管理 | 使用语义化名称如 baget-prod |
--restart=always | 容器异常退出时自动重启 | 生产环境必须设置 |
-p 5555:80 | 端口映射,宿主机 5555 -> 容器 80 | 可改为非标准端口增加安全性 |
-v | 数据卷挂载,持久化包和数据库 | 必须挂载,否则容器重建数据丢失 |
--env-file | 环境变量配置文件路径 | 不要将敏感信息写在 docker run 中 |
使用固定版本标签
生产环境绝对不要使用 latest 标签,应固定到具体版本:
# 查看可用版本
docker search loicsharma/baget
# 或访问 https://hub.docker.com/r/loicsharma/baget/tags
# 使用固定版本
docker run --name nuget-server \
--restart=always \
-d -p 5555:80 \
--env-file baget.env \
-v "$(pwd)/baget-data:/var/baget" \
loicsharma/baget:0.3.2使用 Docker Compose 部署(推荐)
Docker Compose 方式更便于管理和版本控制:
# docker-compose.yml
version: '3.8'
services:
baget:
image: loicsharma/baget:0.3.2
container_name: baget-prod
restart: always
ports:
- "5555:80"
env_file:
- baget.env
volumes:
- ./baget-data:/var/baget
networks:
- nuget-net
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
nuget-net:
driver: bridge启动和管理:
# 启动服务
docker compose up -d
# 查看日志
docker compose logs -f baget
# 查看服务状态
docker compose ps
# 停止服务
docker compose down
# 更新镜像版本
docker compose pull baget
docker compose up -d防火墙放行
# CentOS/RHEL
firewall-cmd --add-port=5555/tcp --permanent
firewall-cmd --reload
# Ubuntu/Debian
ufw allow 5555/tcp
ufw reload
# 验证端口是否开放
ss -tlnp | grep 5555访问 http://服务器IP:5555/ 验证服务是否正常。正常情况下应该能看到 BaGet 的 Web 界面,可以搜索和浏览包。
MySQL 后端配置
如果团队规模较大,SQLite 的并发写入能力可能成为瓶颈,此时可以切换到 MySQL:
# baget.env (MySQL 版本)
ApiKey=Your-API-Key-Change-This
Storage__Type=FileSystem
Storage__Path=/var/baget/packages
Database__Type=MySql
Database__ConnectionString=Server=mysql-host;Port=3306;Database=baget;User=baget;Password=YourDbPassword;
Search__Type=Database
Mirror__Enabled=true# docker-compose.yml (含 MySQL)
version: '3.8'
services:
baget:
image: loicsharma/baget:0.3.2
container_name: baget-prod
restart: always
depends_on:
mysql:
condition: service_healthy
ports:
- "5555:80"
env_file:
- baget.env
volumes:
- ./baget-data:/var/baget
networks:
- nuget-net
mysql:
image: mysql:8.0
container_name: baget-mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: RootPassword123
MYSQL_DATABASE: baget
MYSQL_USER: baget
MYSQL_PASSWORD: YourDbPassword
volumes:
- mysql-data:/var/lib/mysql
networks:
- nuget-net
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
volumes:
mysql-data:
networks:
nuget-net:
driver: bridge发布与使用
准备 NuGet 包
在发布之前,需要在项目文件中配置包元数据:
<!-- MyLibrary.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- 包元数据 -->
<PackageId>MyCompany.MyLibrary</PackageId>
<Version>1.0.0</Version>
<Authors>MyTeam</Authors>
<Company>MyCompany</Company>
<Description>企业内部通用工具库</Description>
<PackageTags>utilities;internal;mycompany</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://git.example.com/myteam/mylibrary</PackageProjectUrl>
<RepositoryUrl>https://git.example.com/myteam/mylibrary.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<!-- 生成符号包(用于源码调试) -->
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<!-- 在构建时自动生成包 -->
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
</Project>发布 NuGet 包
# 打包
dotnet pack MyLibrary.csproj -c Release
# 推送到私服(替换 IP 和 API Key)
dotnet nuget push ./bin/Release/MyCompany.MyLibrary.1.0.0.nupkg \
-s http://10.6.251.241:5555/v3/index.json \
--skip-duplicate \
-k Your-API-Key-Change-This
# 推送符号包(如果已单独生成)
dotnet nuget push ./bin/Release/MyCompany.MyLibrary.1.0.0.snupkg \
-s http://10.6.251.241:5555/v3/index.json \
--skip-duplicate \
-k Your-API-Key-Change-ThisCI/CD 自动发布
在 CI/CD 流水线中自动打包和发布:
# .github/workflows/publish-nuget.yml
name: Publish NuGet Package
on:
push:
tags:
- 'v*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Pack
run: dotnet pack MyLibrary.csproj -c Release --output ./artifacts
- name: Push to Private Feed
run: |
dotnet nuget push ./artifacts/*.nupkg \
-s ${{ secrets.NUGET_SOURCE_URL }} \
-k ${{ secrets.NUGET_API_KEY }} \
--skip-duplicate配置 NuGet 源
# 添加私服源
dotnet nuget add source http://10.6.251.241:5555/v3/index.json \
-n MyPrivateFeed
# 查看已配置的源
dotnet nuget list source
# 移除源
dotnet nuget remove source MyPrivateFeed
# 更新源的 API Key(用于推送)
dotnet nuget update source MyPrivateFeed \
-s http://10.6.251.241:5555/v3/index.json \
-u anyUser \
-p Your-API-Key-Change-Thisnuget.config 全局配置
推荐在解决方案根目录或全局配置文件中统一管理 NuGet 源:
<!-- nuget.config -->
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- 包源配置 -->
<packageSources>
<!-- 私服放在最前面,优先从私服获取 -->
<add key="private" value="http://10.6.251.241:5555/v3/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<!-- 清除默认源(防止意外从 nuget.org 拉取未审核的包) -->
<disabledPackageSources>
<add key="nuget.org" value="true" />
</disabledPackageSources>
<!-- 包的存放路径 -->
<config>
<add key="globalPackagesFolder" value="./.nuget/packages" />
</config>
</configuration>按环境区分源配置
在严格的内网环境中,可以完全禁用 nuget.org:
<!-- nuget.config (仅内网环境) -->
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<!-- 只允许从私服拉取 -->
<add key="private" value="http://10.6.251.241:5555/v3/index.json" />
</packageSources>
</configuration>还原包
# 从指定源还原
dotnet restore --source http://10.6.251.241:5555/v3/index.json
# 指定配置文件还原
dotnet restore --configfile nuget.config
# 强制还原(忽略缓存)
dotnet restore --forceSymbol 调试符号
在 Visual Studio 中配置符号服务器地址:
工具 -> 选项 -> 调试 -> 符号
勾选 "NuGet Symbol Server"
或添加自定义位置: http://10.6.251.241:5555/api/download/symbols也可以在 nuget.config 中配置:
<configuration>
<packageSourceMapping>
<packageSource key="private">
<package pattern="MyCompany.*" />
</packageSource>
</packageSourceMapping>
</configuration>删除和管理包
# 删除指定版本的包
dotnet nuget delete MyCompany.MyLibrary 1.0.0 \
-s http://10.6.251.241:5555/v3/index.json \
-k Your-API-Key-Change-This
# 列出包(通过 API)
curl http://10.6.251.241:5555/v3/registration5-gz-semver2/index.json安全配置
HTTPS 反向代理
生产环境建议通过 Nginx 反向代理并启用 HTTPS。这不仅是安全最佳实践,Visual Studio 和 dotnet CLI 在使用 HTTP 源时也会发出警告。
# /etc/nginx/conf.d/baget.conf
server {
listen 80;
server_name nuget.example.com;
# 强制跳转 HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name nuget.example.com;
# SSL 证书配置
ssl_certificate /etc/nginx/ssl/nuget.crt;
ssl_certificate_key /etc/nginx/ssl/nuget.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# 安全头
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# NuGet 包可能较大,需要调整上传限制
client_max_body_size 200M;
location / {
proxy_pass http://127.0.0.1:5555;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时设置(大包上传可能需要更长时间)
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
# 访问日志
access_log /var/log/nginx/baget_access.log;
error_log /var/log/nginx/baget_error.log;
}API Key 管理
# 生成安全的 API Key(推荐 32 字节以上的随机字符串)
# Linux/macOS
openssl rand -hex 32
# 或使用 GUID
uuidgen
# Windows PowerShell
[guid]::NewGuid().ToString("N") + [guid]::NewGuid().ToString("N")API Key 管理的最佳实践:
| 策略 | 说明 |
|---|---|
| 强密钥 | 至少 32 字符,包含大小写字母、数字和特殊字符 |
| 环境隔离 | 开发/测试/生产使用不同的 API Key |
| 定期轮换 | 建议每季度轮换一次,泄露后立即更换 |
| 最小权限 | 只给需要发布包的人员分配 Key |
| 不入代码库 | Key 通过环境变量或密钥管理服务注入 |
网络隔离
# 仅允许内网段访问 BaGet(在防火墙层面)
firewall-cmd --permanent --zone=trusted --add-source=10.6.0.0/16
firewall-cmd --permanent --zone=trusted --add-port=5555/tcp
firewall-cmd --reload备份与恢复
数据备份
BaGet 的核心数据包括两部分:数据库(包元数据)和存储(包文件)。
# 备份脚本 backup-baget.sh
#!/bin/bash
BACKUP_DIR="/backup/baget/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR"
# 备份数据卷
docker cp baget-prod:/var/baget "$BACKUP_DIR/data"
# 如果使用 MySQL,还需要备份数据库
docker exec baget-mysql mysqldump \
-u baget -pYourDbPassword baget > "$BACKUP_DIR/baget.sql"
# 压缩
tar -czf "$BACKUP_DIR.tar.gz" -C /backup/baget "$(date +%Y%m%d_%H%M%S)"
# 清理 7 天前的备份
find /backup/baget -name "*.tar.gz" -mtime +7 -delete
echo "备份完成: $BACKUP_DIR.tar.gz"数据恢复
# 恢复数据卷
docker cp /backup/baget/20260101_120000/data/. baget-prod:/var/baget/
# 重启容器使数据生效
docker restart baget-prod监控与运维
健康检查
BaGet 内置了健康检查端点,可以集成到监控系统中:
# 手动检查健康状态
curl -s http://localhost:5555/health
# 返回 200 OK 表示服务正常
# 检查 v3 API 是否可用
curl -s http://localhost:5555/v3/index.json | head -c 200日志管理
# 查看容器日志
docker logs baget-prod --tail 100 -f
# 使用 Docker 日志驱动限制日志大小
docker run --name nuget-server \
--log-driver json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
--restart=always \
-d -p 5555:80 \
--env-file baget.env \
-v "$(pwd)/baget-data:/var/baget" \
loicsharma/baget:0.3.2磁盘空间监控
NuGet 包会持续占用磁盘空间,需要定期监控:
# 查看包存储目录大小
du -sh /var/baget/baget-data/packages
# 查看数据库大小
du -sh /var/baget/baget-data/baget.db
# 查找体积最大的包
find /var/baget/baget-data/packages -name "*.nupkg" \
-exec ls -lh {} \; | sort -k5 -hr | head -20C# 项目集成示例
创建私有包项目模板
在团队中统一内部包的项目结构:
// Directory.Build.props — 所有内部包共享的属性
// 放在解决方案根目录
<Project>
<PropertyGroup>
<!-- 统一公司信息 -->
<Company>MyCompany</Company>
<Authors>MyTeam</Authors>
<Copyright>Copyright (c) MyCompany $([System.DateTime]::Now.Year)</Copyright>
<PackageLicenseExpression>INTERNAL</PackageLicenseExpression>
<!-- 统一源码链接 -->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<!-- 统一版本规则 -->
<Version>1.0.0</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
</PropertyGroup>
</Project>在 ASP.NET Core 中自动引用私有包
// Program.cs — 在构建时自动添加私有源
var builder = WebApplication.CreateBuilder(args);
// 如果需要以编程方式管理 NuGet 源(通常通过 nuget.config 更简单)
// 这里展示如何在 CI 环境中确保使用正确的源
builder.Services.AddControllers();
var app = builder.Build();
app.MapGet("/nuget-info", () => new
{
PrivateFeed = "http://10.6.251.241:5555/v3/index.json",
PackageCount = GetInstalledPackageCount(),
LastRestore = DateTime.UtcNow
});
app.Run();
static int GetInstalledPackageCount()
{
// 通过读取 .nuget 目录或 project.assets.json 统计已安装包数
var assetsFile = Path.Combine(
AppContext.BaseDirectory, "..", "..", "..", "..",
"obj", "project.assets.json");
return File.Exists(assetsFile) ? CountPackages(assetsFile) : 0;
}
static int CountPackages(string path)
{
var json = File.ReadAllText(path);
// 简单统计 libraries 节点下的包数量
var count = 0;
var idx = 0;
while ((idx = json.IndexOf("\"type\": \"package\"", idx)) != -1)
{
count++;
idx++;
}
return count;
}版本管理策略
在多团队协作中,建议采用语义化版本(SemVer)策略:
// 在 CI 中自动计算版本号
// build-version.sh
#!/bin/bash
BRANCH=$(git branch --show-current)
BASE_VERSION="1.0.0"
if [ "$BRANCH" = "main" ]; then
# 主分支:稳定版本
VERSION="$BASE_VERSION"
elif [ "$BRANCH" = "develop" ]; then
# 开发分支:预发布版本
VERSION="$BASE_VERSION-beta.$(date +%Y%m%d)"
else
# 功能分支:预览版本
VERSION="$BASE_VERSION-preview.$(date +%Y%m%d%H%M)"
fi
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "构建版本: $VERSION"优点
缺点
替代方案对比
| 特性 | BaGet | Nexus | Artifactory | Azure Artifacts |
|---|---|---|---|---|
| 部署难度 | 低 | 中 | 中 | 低(SaaS) |
| NuGet 支持 | v3 | v2/v3 | v2/v3 | v3 |
| 权限管理 | 无 | 有 | 有 | 有 |
| 多格式支持 | NuGet | npm/Maven/NuGet 等 | 全格式 | npm/NuGet/Maven 等 |
| 高可用 | 无 | 有 | 有 | 内置 |
| 审批流程 | 无 | 有 | 有 | 有 |
| 免费使用 | 完全免费 | 免费版有限制 | 免费版有限制 | 需 Azure DevOps 许可 |
| 资源占用 | 低 | 高 | 高 | 无(SaaS) |
总结
BaGet 适合小型团队快速搭建 NuGet 私服。核心操作:Docker 部署服务、配置 API Key、dotnet nuget push 发布包、配置 nuget.config 还原包。生产环境务必启用 HTTPS 反向代理,使用固定版本标签而非 latest,并做好数据备份。大型团队建议使用 Nexus Repository 或 JFrog Artifactory 替代。
关键知识点
- 先分清这个主题位于请求链路、后台任务链路还是基础设施链路。
- 服务端主题通常不只关心功能正确,还关心稳定性、性能和可观测性。
- 任何框架能力都要结合配置、生命周期、异常传播和外部依赖一起看。
- 部署主题通常要同时看镜像、容器、卷、网络和宿主机资源。
- NuGet 私服是 CI/CD 链路的关键节点,直接影响构建速度和可靠性。
项目落地视角
- 画清请求进入、业务执行、外部调用、日志记录和错误返回的完整路径。
- 为关键链路补齐超时、重试、熔断、追踪和结构化日志。
- 把配置与敏感信息分离,并明确不同环境的差异来源。
- 固定镜像标签,记录端口、挂载目录、环境变量和自启动策略。
- 将 NuGet 源配置纳入版本控制,确保团队成员环境一致。
常见误区
- 只会堆中间件或组件,不知道它们在链路中的执行顺序。
- 忽略生命周期和线程池、连接池等运行时资源约束。
- 没有监控和测试就对性能或可靠性下结论。
- 使用 latest 导致结果不可复现。
- 忽视 NuGet 源的优先级顺序,导致从错误的源拉取包。
- 不做包存储的磁盘空间监控,导致服务器磁盘写满。
- 在 CI/CD 中硬编码 API Key 而非使用密钥管理。
进阶路线
- 继续向运行时行为、可观测性、发布治理和微服务协同深入。
- 把主题和数据库、缓存、消息队列、认证授权联动起来理解。
- 沉淀团队级模板,包括统一异常处理、配置约定和基础设施封装。
- 继续补齐 Compose 编排、镜像瘦身、安全扫描和镜像仓库治理。
- 研究包签名验证和可信源管理,提升供应链安全。
适用场景
- 当你准备把《BaGet 私服搭建》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合 API 服务、后台任务、实时通信、认证授权和微服务协作场景。
- 当需求开始涉及稳定性、性能、可观测性和发布流程时,这类主题会成为基础设施能力。
- 特别适合需要离线构建、包版本管控和加速 CI/CD 的场景。
落地建议
- 先定义请求链路与失败路径,再决定中间件、过滤器、服务边界和依赖方式。
- 为关键链路补日志、指标、追踪、超时与重试策略。
- 环境配置与敏感信息分离,避免把生产参数写死在代码或镜像里。
- 将 nuget.config 纳入版本控制,使用 Directory.Build.props 统一包元数据。
排错清单
- 先确认问题发生在路由、模型绑定、中间件、业务层还是基础设施层。
- 检查 DI 生命周期、配置来源、序列化规则和认证上下文。
- 查看线程池、连接池、缓存命中率和外部依赖超时。
- 检查容器日志:
docker logs baget-prod --tail 50 - 检查端口映射:
docker port baget-prod - 检查防火墙规则:
firewall-cmd --list-all - 检查磁盘空间:
df -h
复盘问题
- 如果把《BaGet 私服搭建》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《BaGet 私服搭建》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《BaGet 私服搭建》最大的收益和代价分别是什么?
