BaGetter
Overview
BaGetter is a lightweight, self-hosted server for your own private NuGet package feed. It implements the standard NuGet v3 protocol, so any .NET toolchain can restore from it and push to it with the tools developers already use. A clean, browsable web gallery lets you search packages and read their metadata, versions and dependencies.
Packages live on a server you control, with metadata in a self-contained SQLite database and package files on local disk. BaGetter can also mirror packages from the public nuget.org feed on demand.
The SecureAnyCloud image builds the pinned BaGetter 1.6.5 release on the supported ASP.NET Core 10 runtime. It runs as a dedicated bagetter system user under systemd, with an nginx reverse proxy on port 80 in front of the web gallery and NuGet v3 API. The package store lives on a dedicated Azure data disk.
A unique push API key is generated on the first boot of every VM, so no shared credential ships in the image. Browsing and restore are open on your network, while pushing is gated by the per-VM key.
What you get:
- BaGetter 1.6.5 running as the
bagettersystemd service on the ASP.NET Core 10 runtime - The NuGet v3 service index at
/v3/index.json, so anydotnetornugetclient can restore and push - A browsable web gallery on
:80, fronted by nginx, for searching packages and reading metadata - Secure-by-default push: a unique API key is generated on first boot and required for every push
- A dedicated Azure data disk at
/var/lib/bagetterholding the SQLite metadata store and package files bagetter.serviceandnginx.serviceas systemd units, enabled and active- An unauthenticated
/healthzendpoint for Azure Load Balancer health probes - 24/7 SecureAnyCloud support
Prerequisites
You need an active Azure subscription, an SSH key pair, and a VNet with a subnet in the target region.
Standard_B2s (2 vCPU / 4 GiB RAM) is a comfortable starting size, because BaGetter is light on resources.
Configure the network security group (NSG) to allow inbound 22/tcp from your management network, and 80/tcp from the networks that run your builds and developer machines so they can browse, restore and push. BaGetter serves plain HTTP on port 80. For use beyond a trusted network, terminate TLS in front of it with your own domain (see Maintenance).
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure portal, choose Create a resource, search the Marketplace for BaGetter by SecureAnyCloud, and select Create.
On the Basics tab, pick your subscription, resource group, region and size. Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22) and HTTP (80).
Review the dedicated data disk on the Disks tab, then select Review + create and Create.
Step 2 - Deploy from the Azure CLI
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:bagetter-ubuntu-24-04:default:latest
az vm create \
--resource-group <your-rg> \
--name bagetter \
--image sac1647283583153:bagetter-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name bagetter --port 80 --priority 1010Step 3 - Connect to your VM
Connect over SSH as azureuser:
ssh azureuser@<vm-public-ip>Step 4 - Confirm the services are running
Check that both services are active:
systemctl is-active bagetter.service nginx.serviceBoth report active. BaGetter serves its web gallery and NuGet v3 API on the loopback connector 127.0.0.1:5000, and nginx fronts it on port 80. BaGetter's SQLite metadata store and package files live on the dedicated Azure data disk mounted at /var/lib/bagetter.
Step 5 - Retrieve your push API key
BaGetter gates package pushes behind an API key. A unique key is generated on the first boot of your VM and written to a root-only file. Read it with sudo:
sudo cat /root/bagetter-credentials.txtThe file contains three values:
BAGETTER_API_KEY, the key required to push packagesBAGETTER_SERVICE_INDEX, the/v3/index.jsonURL that yourdotnetclients point atBAGETTER_URL, the web gallery
The key is stored only in the per-VM file /var/lib/bagetter/bagetter.env (mode 0600), so no shared key ships in the image. Store the key somewhere safe.
Step 6 - Confirm the health endpoint
Query the unauthenticated health endpoint that nginx serves for load balancers and probes:
curl -s http://localhost/healthzIt returns ok. This endpoint never requires authentication, so you can use it for an Azure Load Balancer health probe.
Step 7 - Confirm the feed serves and push is protected
The NuGet v3 service index is publicly readable so that any client can restore from the feed, while pushing requires the API key. The following commands check both: the service index returns 200, and a push attempt with no key is rejected with 401.
echo "index: $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/v3/index.json)"
echo "push (no key): $(curl -s -o /dev/null -w '%{http_code}' -X PUT -F package=@/opt/bagetter/testdata/Sac.Smoke.Test.1.0.0.nupkg http://127.0.0.1/api/v2/package)"The output is index: 200 followed by push (no key): 401. Nobody can push packages to your feed without the per-VM API key, while your builds can restore freely from the open read surface.
Step 8 - Browse the web gallery
Browse to http://<vm-public-ip>/. BaGetter opens on the Packages gallery, a searchable list of every package hosted on your feed. Each row shows the package ID, author, latest version, tags and description.
Step 9 - View a package
Click any package to open its detail page. BaGetter shows a ready-to-copy install command for the .NET CLI, PackageReference, Paket CLI and Package Manager. The page also shows the package Readme, its dependencies, and a Versions table that lists every published version with its download count and publish date.
Step 10 - Search the feed
Use the search box, or append ?q= to the URL, to filter the gallery. Searching narrows the list to the matching packages, so developers can quickly find the shared library they need across all your private packages.
Step 11 - Push a package from your dev machine
Open the Upload page in the gallery for the exact push commands, or push directly with the standard .NET tooling. On a machine with the .NET SDK, run the push with the API key from Step 5 and the service index URL:
dotnet nuget push YourPackage.1.0.0.nupkg \
--source http://<vm-public-ip>/v3/index.json \
--api-key <BAGETTER_API_KEY>The package is uploaded to your feed and appears immediately in the web gallery. Pushes without the key, or with the wrong key, are rejected with 401.
Step 12 - Restore from the feed
On any developer or build machine, add the feed as a restore source. Then dotnet restore and dotnet build pull private packages from it. Reading and restoring do not require the API key:
dotnet nuget add source http://<vm-public-ip>/v3/index.json --name bagetter dotnet restorePoint your CI pipelines at the same source, so that builds resolve your private packages from BaGetter alongside their public dependencies.
Step 13 - Confirm data lives on the dedicated disk
BaGetter's SQLite metadata store and package files are kept under /var/lib/bagetter on the dedicated Azure data disk. Your packages survive OS changes, and you can resize the disk independently. Confirm the mount:
findmnt /var/lib/bagetterA separate Azure data disk backs the mount. It is captured into the image and re-provisioned on every VM.
Maintenance
- API key. The push key is set on first boot and stored in the per-VM file
/var/lib/bagetter/bagetter.env(ApiKey=..., mode0600). To rotate it, edit that file as root and runsudo systemctl restart bagetter. Then update your CI and developer machines with the new key. - Protecting read access. By default, browsing and restore are open on your network. To require credentials for reading too, add an
Authentication:Credentialslist (username and password) to a BaGetter config override and restart the service. See the BaGetter documentation. - Mirroring nuget.org. BaGetter can serve a locally cached copy of public packages. Enable the upstream mirror in a config override (
Mirror:Enabled=true), and a restore that asks for a public package is then cached on your feed. - Package limits. The maximum package size defaults to 8 GiB (
MaxPackageSizeGiB), and nginx is configured to accept large uploads. - Storage. All BaGetter state lives under
/var/lib/bagetteron the data disk: the SQLitebagetter.dband thepackages/file store. Back up that volume to protect your packages. - Upgrades. BaGetter is built from a pinned source tag and published under
/opt/bagetter, running on the apt-managed ASP.NET Core 10 runtime. To upgrade, rebuild a newer BaGetter release, replace the contents of/opt/bagetter, and runsudo systemctl restart bagetter. Your store under/var/lib/bagetteris preserved. - TLS. BaGetter serves plain HTTP on port 80. Front it with TLS, for example certbot with your own domain, before you expose it beyond a trusted network.
- Security patches. unattended-upgrades remains enabled, so the OS and the ASP.NET Core runtime continue to receive security updates automatically.
Support
SecureAnyCloud provides 24/7 expert support for this image. Contact sales@secureanycloud.com.
Deploy on Azure
Launch BaGetter on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com
Overview
BaGetter is a lightweight, self-hosted NuGet package feed server. This image runs it as a hardened systemd service on Ubuntu 24.04. BaGetter hosts your organization's private .NET packages, exposes the standard NuGet v3 service index that any NuGet client can restore from, and provides a browsable web gallery for discovering what is published.
BaGetter 1.6.5 is built from source and runs framework-dependent on the supported ASP.NET Core 10 runtime.
What you get:
- BaGetter bound to the loopback interface only (
127.0.0.1:5000), behind an nginx TLS reverse proxy on port 443, so the application is never directly reachable from the network and all traffic to the gallery and the NuGet API is encrypted - Package metadata in SQLite and package content on the filesystem, both under
/var/lib/bagetteron a dedicated EBS data volume that is separate from the operating system disk, so your published packages are independently resizable and snapshottable - A unique push API key generated on the first boot of every instance and written to
/root/bagetter-credentials.txtwith mode 0600, so two instances launched from the same AMI never share a credential and no usable key is baked into the image - A unique self-signed TLS certificate generated per instance on first boot
The image is secure by default. A stock NuGet server can accept anonymous uploads if no push key is configured, and this image never ships that way. A push with a missing or wrong API key is rejected. Read access, which covers the service index, package restore and the gallery, stays open by design, which is the standard private feed posture.
Prerequisites
Before you deploy this image, make sure that you have:
- An Amazon Web Services account where you can launch EC2 instances
- IAM permissions to launch instances, create security groups, and subscribe to AWS Marketplace products
- An EC2 key pair in the target Region for SSH access to the instance
- A VPC and subnet in the target Region, with a security group that allows inbound port 22 from your management network and inbound port 443 from the developer and CI networks that will push to and restore from the feed
- The AWS CLI (version 2) installed locally, if you plan to deploy from the command line
- A machine with the .NET SDK installed, to push and restore packages
Launching the instance
Subscribe and launch from the AWS Marketplace console, or launch from the AWS CLI once you have subscribed. Replace the angle-bracket values with your own.
aws ec2 run-instances \
--image-id <ami-id> \
--instance-type m5.large \
--key-name <key-name> \
--subnet-id <subnet-id> \
--security-group-ids <security-group-id> \
--metadata-options "HttpTokens=required,HttpEndpoint=enabled" \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=bagetter}]'The recommended security group opens only SSH (22) and the HTTPS front door (443). Scope both to your own developer, CI and administrator networks, and do not leave them open to the whole internet.
Connecting to your instance
Connect over SSH as the default login user for your operating system variant. The login user depends on the variant that you launched:
OS variant SSH login user
Ubuntu 24.04 ubuntu
ssh -i /path/to/your-key.pem ubuntu@<public-ip>Retrieving your push API key
The per-instance push API key, the feed URL and the NuGet service index URL are written to a root-only file on first boot. Read them over SSH:
sudo cat /root/bagetter-credentials.txtThe file lists the feed URL, the service index URL and the API key, for example BAGETTER_API_KEY=<set-at-first-boot>. Keep the API key secret. Anyone who has it can publish or overwrite packages on your feed.
Browsing the web gallery
Open the feed URL from the credentials file in a browser: https://<public-ip>/. The certificate is self-signed by default, so your browser shows a warning until you install your own certificate (covered below).
The gallery lists every published package with its latest version, owner and description, and has a search box across the top. Click any package to open its detail page. The page shows the install commands for the .NET CLI, PackageReference, Paket and the Package Manager console, plus the full version history and the package metadata.
Verifying the feed on the instance
Confirm on the instance that the services are healthy and that the push gate is enforced. This command reads the API key from the credentials file, checks that the service index answers over TLS, and shows that an anonymous push is refused with HTTP 401:
KEY=$(sudo grep '^BAGETTER_API_KEY=' /root/bagetter-credentials.txt | cut -d= -f2-)
systemctl is-active bagetter.service nginx.service curl -ks -o /dev/null -w 'healthz: %{http_code}\n' https://127.0.0.1/healthz curl -ks -o /dev/null -w 'v3 service index: %{http_code}\n' https://127.0.0.1/v3/index.json curl -ks -o /dev/null -w 'anonymous push (expect 401): %{http_code}\n' \
-X PUT -F 'package=@/opt/bagetter/testdata/Sac.Smoke.Test.1.0.0.nupkg' \
https://127.0.0.1/api/v2/package echo "api key length: ${#KEY} chars"Both services report active, the health endpoint and the service index return 200, and the anonymous push returns 401. Only holders of the API key can publish.
Adding the feed to a NuGet client
On a developer or CI machine, add the service index as a package source. The default certificate is self-signed, so first do one of the following: install your own certificate on the instance, trust the self-signed certificate on the client, or terminate TLS at a load balancer with a real certificate.
dotnet nuget add source https://<public-ip>/v3/index.json --name bagetterPush a package with your API key:
dotnet nuget push YourPackage.1.0.0.nupkg --source https://<public-ip>/v3/index.json --api-key <your-token>Restore from the feed. You can also reference the feed in a nuget.config alongside your solution:
dotnet restore --source https://<public-ip>/v3/index.jsonRead access to the service index, package restore and the gallery is open to the networks that your security group allows in. Only pushing requires the API key.
Rotating the API key
The active push API key lives in the per-instance environment file. To rotate it, replace the value and restart BaGetter:
sudo sed -i "s/^ApiKey=.*/ApiKey=$(openssl rand -hex 32)/" /var/lib/bagetter/bagetter.env
sudo systemctl restart bagetter.serviceUpdate /root/bagetter-credentials.txt and your clients with the new value.
Replacing the TLS certificate
A unique self-signed certificate is generated per instance at /etc/nginx/ssl/bagetter.crt and /etc/nginx/ssl/bagetter.key. Replace both files with your own certificate and private key, then reload nginx. Alternatively, front the instance with a load balancer that terminates TLS using an ACM certificate.
# Install the certificate you obtained for <your-domain>:
sudo cp your-fullchain.pem /etc/nginx/ssl/bagetter.crt
sudo cp your-private.key /etc/nginx/ssl/bagetter.key
sudo nginx -t && sudo systemctl reload nginx.serviceStorage layout
Package metadata (the SQLite database bagetter.db) and package content (the packages/ directory) live under /var/lib/bagetter on a dedicated EBS volume that is separate from the operating system disk. You can resize or snapshot your package store independently. Check the volume:
df -hT /var/lib/bagetterEnabling upstream mirroring
BaGetter can act as a read-through cache of an upstream feed such as nuget.org, so restores continue to work when the upstream is unavailable. You configure this through BaGetter's Upstream settings. Contact SecureAnyCloud support, and we will help you enable and tune mirroring for your environment.
Backups
Back up the SQLite metadata database and the package store together, so that the feed can be restored consistently:
sudo tar -czf /tmp/bagetter-backup.tgz -C /var/lib/bagetter bagetter.db packagesCopy the archive off the instance, or snapshot the dedicated EBS volume from the AWS console or CLI.
Services and troubleshooting
BaGetter runs as bagetter.service on loopback port 5000, and nginx fronts it on port 443. These commands help with troubleshooting:
systemctl status bagetter.service --no-pager sudo journalctl -u bagetter.service --no-pager | tail -50If the gallery does not load, confirm that both services are active and that your security group allows inbound port 443 from your network.
Support
SecureAnyCloud provides 24/7 technical support for this product by email and live chat. Our engineers help with deployment, connecting NuGet clients and CI pipelines, push API key management, TLS termination, upstream mirroring, backups and scaling. Email sales@secureanycloud.com or use live chat, and we will work with you until your issue is resolved.
Deploy on AWS
Launch BaGetter on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com