BaGet 是一个基于 ASP.NET Core 实现的轻量级 NuGet 包服务器,用来在内网托管私有 NuGet 包。相比 Nexus、Artifactory 这类制品库,它只有一个容器、配置项很少,元数据用 SQLite、包文件直接落盘,很适合中小团队自建私有源。本文整理用 docker-compose 部署 BaGet、用 Nginx 加 HTTPS 与 Basic 认证,以及客户端推包/拉包的完整过程。
一、为什么自建 NuGet 源
- 公司内部的工具库、公共类库不方便发布到 nuget.org;
- 内网环境无法直连 nuget.org,需要有本地的包托管或缓存;
- 需要自己控制包的可见范围、覆盖策略和删除策略。
二、docker-compose 部署
1. 目录结构
baget/
├── docker-compose.yml
└── data/ # 挂载到容器 /var/baget,存放包文件与 SQLite 数据库先创建数据目录,BaGet 会把自己需要的子目录建在里面:
mkdir -p baget/data && cd baget2. docker-compose.yml
version: '3.8'
services:
baget:
container_name: baget
image: loicsharma/baget
ports:
- "8001:80"
environment:
- "ApiKey=CHANGE_ME_strong_api_key"
- "AllowPackageOverwrites=True"
- "PackageDeletionBehavior=HardDelete"
- Storage__Type=FileSystem
- Storage__Path=/var/baget/packages
- Database__Type=Sqlite
- Database__ConnectionString=Data Source=/var/baget/baget.db
- Search__Type=Database
- TZ=Asia/Shanghai
volumes:
- ./data:/var/baget
restart: always容器内的服务监听 80 端口,映射到宿主机 8001;./data 挂到 /var/baget,包文件和 SQLite 库都在这个目录里,迁移或备份时整体拷走即可。TZ=Asia/Shanghai 用来让容器日志和包的上传时间显示成本地时区。
3. 主要配置项说明
| 配置项 | 本文取值 | 说明 |
|---|---|---|
ApiKey | 自定义随机串 | 推送、删除包时校验的密钥。留空或不配置则允许匿名推送,生产环境务必设置 |
AllowPackageOverwrites | True | 允许覆盖上传同名同版本的包(默认关闭)。开发阶段反复发包时比较方便,正式发布建议关闭 |
PackageDeletionBehavior | HardDelete | 删除包时的行为。默认是 Unlist(仅下架,文件仍在),设为 HardDelete 才会连同文件一起删除 |
Storage__Type | FileSystem | 包文件存储方式,还支持 AzureBlobStorage、AwsS3、GoogleCloud、Null 等 |
Storage__Path | /var/baget/packages | 包文件的落盘路径 |
Database__Type | Sqlite | 元数据存储,支持 Sqlite / SqlServer / MySql / PostgreSql / AzureTable |
Database__ConnectionString | Data Source=/var/baget/baget.db | 数据库连接串 |
Search__Type | Database | 搜索实现。包不多时用 Database 即可,量大可换 ElasticSearch |
这里的双下划线 __ 是 ASP.NET Core 表示配置层级的分隔符,Database__Type 对应配置文件里的 Database.Type,Storage__Path 对应 Storage.Path,在 compose 里也能直接换成 baget.env 环境变量文件来管理。
4. 启动与验证
# 在 docker-compose.yml 所在目录执行
sudo docker-compose up -d
# 查看启动日志
sudo docker-compose logs -f baget
# 确认服务索引可以访问
curl -I http://127.0.0.1:8001/v3/index.json返回 200 OK 说明服务已经就绪,此时用浏览器访问 http://服务器IP:8001/ 能看到 BaGet 的 Web 界面。
三、Nginx 做 HTTPS 反向代理
直接对外暴露 8001 端口只能走 HTTP,ApiKey 和包元数据都会明文传输,同时 8001 也不方便直接暴露在公网。用 Nginx 在前面加一层 HTTPS:
server {
listen 80;
listen 443 ssl;
client_max_body_size 100m;
ssl_certificate /usr/share/nginx/certs/apexglobe.info.crt;
ssl_certificate_key /usr/share/nginx/certs/apexglobe.info.key;
server_name nuget.apexglobe.info;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_prefer_server_ciphers on;
gzip on;
gzip_min_length 1k;
gzip_comp_level 9;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
location / {
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://10.90.1.104:8001/;
#auth_basic "Restricted Content"; # 开启 auth 认证
#auth_basic_user_file /etc/nginx/conf.d/htpasswd; # 开启 auth 认证
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}几个容易出问题的地方:
client_max_body_size必须显式调大。Nginx 默认只有 1m,超过就会返回413 Request Entity Too Large;这里设为 100m,若包体积更大要同步放大(BaGet 自身默认上限是 250MB)。proxy_pass末尾带/,会把location匹配到的前缀替换掉,配合 BaGet 的/v3/路径使用;改路径前缀时要注意这一点。proxy_set_header Host $host与X-Forwarded-Proto建议保留,反向代理后 BaGet 生成的下载地址才正确。
四、加 Basic Auth 访问认证
ApiKey 只能拦住"推包"这一步,任何知道地址的人依然可以浏览和下载包。如果连包列表都不想对外公开,可以在 Nginx 层再套一层 Basic 认证。
1. 生成 htpasswd 账号密码
docker run --rm --entrypoint htpasswd httpd:2 -Bbn your-username your-password > ./htpasswd-B 表示用 bcrypt 加密,-b 从命令行读取密码,-n 把结果输出到标准输出。生成的 htpasswd 文件放到 Nginx 指定的目录里,例如 /etc/nginx/conf.d/htpasswd。
2. 在 Nginx 中启用
把上面配置里注释掉的 auth_basic 和 auth_basic_user_file 两行打开即可:
location / {
auth_basic "Restricted Content";
auth_basic_user_file /etc/nginx/conf.d/htpasswd;
# ... 其余 proxy_set_header / proxy_pass 保持不变
}改完执行 nginx -t && nginx -s reload 生效。刷新页面会弹出账号密码框。
3. htpasswd 文件权限
启用后如果出现 500 或者一直重复弹窗,多半是 Nginx 读不到 htpasswd 文件。以 Debian/Ubuntu 为例,Nginx 的 worker 进程以 www-data 身份运行,需要把文件和目录的属主与权限都改对:
sudo chown -R www-data:www-data /etc/nginx/conf.d
sudo chmod -R 750 /etc/nginx/conf.d
sudo chown www-data:www-data /etc/nginx/conf.d/htpasswd
sudo chmod 640 /etc/nginx/conf.d/htpasswd注意目录本身也需要 x 权限(750 里的最后一位),否则即使文件权限正确,worker 进程也进不去这个目录。若 Nginx 跑在容器里,要改的是容器内对应的路径,而不是宿主机上的同名路径。
五、客户端配置与推包
1. 添加包源
dotnet nuget add source https://nuget.apexglobe.info/v3/index.json --name baget源地址必须带上 /v3/index.json,只写域名或端口会报 Unable to load the service index。Visual Studio 里在「工具 → NuGet 包管理器 → 包管理器设置 → 程序包源」中添加同样的地址即可。
2. 推送包
dotnet nuget push bin/Release/MyCompany.Utils.1.0.0.nupkg \
--source https://nuget.apexglobe.info/v3/index.json \
--api-key CHANGE_ME_strong_api_key
# 重复推送同一版本时跳过,避免报错
dotnet nuget push bin/Release/MyCompany.Utils.1.0.0.nupkg \
--source https://nuget.apexglobe.info/v3/index.json \
--api-key CHANGE_ME_strong_api_key \
--skip-duplicate如果服务端配了 ApiKey,推送时不给 --api-key 会直接返回 401。符号包(.snupkg)用同样的方式推送。
3. 带 Basic 认证的 NuGet.config
Nginx 开了 Basic 认证后,BaGet 收到的请求必须带 Authorization 头,客户端要在 NuGet.config 里额外配置包源凭据:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="baget" value="https://nuget.apexglobe.info/v3/index.json" />
</packageSources>
<packageSourceCredentials>
<baget>
<add key="Username" value="your-username" />
<add key="ClearTextPassword" value="your-password" />
</baget>
</packageSourceCredentials>
</configuration>packageSourceCredentials 下的节点名要和 packageSources 里的 key 完全一致。该文件如果提交到仓库,建议把密码换成环境变量占位或使用 CI 的密钥管理,不要明文入库。
六、常见问题
| 现象 | 原因与处理 |
|---|---|
Unable to load the service index / 404 | 源地址少了 /v3/index.json,BaGet 的 API 根路径是 /v3/,不是域名根 |
| 推送返回 401 / 未提供 API 密钥 | 服务端配置了 ApiKey 但客户端没传 --api-key;或已开 Basic 认证而客户端未配置包源凭据 |
413 Request Entity Too Large | Nginx 的 client_max_body_size 太小,默认仅 1m,按包体积调大 |
| 推上去了但搜索不到 | Search__Type 配置有误,本地部署用 Database;也可直接访问 /v3/index.json 确认服务索引正常 |
| 删除了包但文件还在 | PackageDeletionBehavior 默认为 Unlist,只做下架;要真正删除需设为 HardDelete |
| 覆盖同版本包失败 | 需要 AllowPackageOverwrites=True,或者改用新版本号发布 |
小结
整套部署可以概括为四层:docker-compose 拉起 BaGet 并挂载数据目录保证持久化;Nginx 提供 HTTPS 并调大上传体积限制;需要隐藏包列表时再叠加 htpasswd 的 Basic 认证;客户端侧把源地址统一到 /v3/index.json,并按需配置 ApiKey 与包源凭据。ApiKey、Basic 密码这类凭据建议用环境变量或密钥管理下发,不要直接写在配置文件里。
评论区