侧边栏壁纸
博主头像
Setyg的博客

当秋风染红落叶的时候,就是新生~

  • 累计撰写 86 篇文章
  • 累计创建 66 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

Docker Compose 部署 BaGet 私有 NuGet 服务端实践

Setyg
2026-09-17 / 0 评论 / 0 点赞 / 3 阅读 / 0 字

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 baget

2. 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自定义随机串推送、删除包时校验的密钥。留空或不配置则允许匿名推送,生产环境务必设置
AllowPackageOverwritesTrue允许覆盖上传同名同版本的包(默认关闭)。开发阶段反复发包时比较方便,正式发布建议关闭
PackageDeletionBehaviorHardDelete删除包时的行为。默认是 Unlist(仅下架,文件仍在),设为 HardDelete 才会连同文件一起删除
Storage__TypeFileSystem包文件存储方式,还支持 AzureBlobStorage、AwsS3、GoogleCloud、Null 等
Storage__Path/var/baget/packages包文件的落盘路径
Database__TypeSqlite元数据存储,支持 Sqlite / SqlServer / MySql / PostgreSql / AzureTable
Database__ConnectionStringData Source=/var/baget/baget.db数据库连接串
Search__TypeDatabase搜索实现。包不多时用 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 LargeNginx 的 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 密码这类凭据建议用环境变量或密钥管理下发,不要直接写在配置文件里。

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区