BaGetter on Ubuntu 24.04 on Azure User Guide
Overview
BaGetter is a lightweight, self-hosted server for hosting 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, and it presents a clean, browsable web gallery for searching packages and reading their metadata, versions and dependencies. Packages live on the 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 cloudimg image builds the pinned BaGetter 1.6.5 release on the supported ASP.NET Core 10 runtime, runs it as a dedicated bagetter system user under systemd, and fronts its web gallery and NuGet v3 API on port 80 with an nginx reverse proxy. The package store lives on a dedicated Azure data disk, and 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. Backed by 24/7 cloudimg support.
What is included:
- BaGetter 1.6.5 running as the
bagettersystemd service on the ASP.NET Core 10 runtime - The NuGet v3 service index at
/v3/index.jsonso anydotnet/nugetclient 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.service+nginx.serviceas systemd units, enabled and active- An unauthenticated
/healthzendpoint for Azure Load Balancer health probes - 24/7 cloudimg support
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet + subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a comfortable starting point; BaGetter is light on resources. NSG inbound: allow 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 cloudimg, and select Create. On Basics 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 Review + create -> Create.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group <your-rg> \
--name bagetter \
--image <marketplace-image-urn> \
--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 1010
Step 3 - Connect to your VM
ssh azureuser@<vm-public-ip>
Step 4 - Confirm the services are running
systemctl is-active bagetter.service nginx.service
Both report active. BaGetter serves its web gallery and NuGet v3 API on the loopback connector 127.0.0.1:5000; 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:
sudo cat /root/bagetter-credentials.txt
This file contains BAGETTER_API_KEY (the key required to push packages), BAGETTER_SERVICE_INDEX (the /v3/index.json URL your dotnet clients point at) and BAGETTER_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
nginx serves an unauthenticated health endpoint for load balancers and probes:
curl -s http://localhost/healthz
It returns ok. This endpoint never requires authentication, so it is safe 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 any client can restore from the feed, while pushing requires the API key. The following proves 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/Cloudimg.Smoke.Test.1.0.0.nupkg http://127.0.0.1/api/v2/package)"
It prints index: 200 then push (no key): 401. Nobody can push packages to your feed without the per-VM API key, while your builds can freely restore from the public 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 showing 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 package Readme, its dependencies, and a Versions table listing 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 a developer 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. From a machine with the .NET SDK, run the push using 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 and your dotnet restore / dotnet build will 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 restore
Point your CI pipelines at the same source so 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, so your packages survive OS changes and the disk can be resized independently:
findmnt /var/lib/bagetter
The mount is backed by a separate Azure data disk 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. 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/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) so a restore that asks for a public package is cached on your feed. - Package limits: the maximum package size defaults to 8 GiB (
MaxPackageSizeGiB); 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 and replace the contents of/opt/bagetter, then 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 exposing 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
cloudimg provides 24/7 expert support for this image. Contact support@cloudimg.co.uk.