Hosting
AvantiPoint Packages can be hosted in various environments. This guide covers common hosting scenarios.
Production host (Docker)
The recommended production entry point is AvantiPoint.Packages.Host under src/host/. It includes database-backed API tokens, multi-provider UI authentication, email notifications, package source administration, and downstream syndication.
Build and publish
The root Dockerfile builds the registry image from the repository root:
docker build -t avantipoint/packages-host:latest .
docker tag avantipoint/packages-host:latest avantipoint/packages-host:<version>
docker push avantipoint/packages-host:latest
docker push avantipoint/packages-host:<version>
Image defaults (override at docker run or in your orchestrator):
| Variable | Default in image |
|---|---|
ASPNETCORE_URLS | http://+:8080 |
ASPNETCORE_ENVIRONMENT | Production |
Keep ASPNETCORE_ENVIRONMENT=Production in production so configuration layers from appsettings.json and environment variables apply as designed. Set database, storage, authentication, email, and secrets via environment variables or your orchestrator's secret store—do not rely on dev placeholders in appsettings.json.
Run the published image
Map port 8080 and persist package data under /data when using file-backed SQLite and FileSystem storage:
docker run -d -p 8080:8080 \
-e Database__Type=Sqlite \
-e Database__ConnectionString="Data Source=/data/packages.db" \
-e Storage__Type=FileSystem \
-e Storage__Path=/data/packages \
-v feed-data:/data \
avantipoint/packages-host:latest
SQL Server (managed instance or your own server; connection string points at the database host, not the container name unless you colocate):
docker run -d -p 8080:8080 \
-e Database__Type=SqlServer \
-e Database__ConnectionString="Server=<host>,1433;Database=packages;User Id=<user>;Password=<password>;TrustServerCertificate=True" \
-e Storage__Type=FileSystem \
-e Storage__Path=/data/packages \
-v feed-data:/data \
avantipoint/packages-host:latest
PostgreSQL:
docker run -d -p 8080:8080 \
-e Database__Type=PostgreSQL \
-e Database__ConnectionString="Host=<host>;Database=packages;Username=<user>;Password=<password>" \
-e Storage__Type=FileSystem \
-e Storage__Path=/data/packages \
-v feed-data:/data \
avantipoint/packages-host:latest
For cloud object storage (S3, Azure Blob, GCS, MinIO-compatible endpoints, and others), set Storage__Type and the provider-specific keys documented under Storage—for example Storage__Bucket, Storage__Region, Storage__ConnectionString, or managed-identity flags—instead of mounting /data/packages for blobs.
Production secrets (examples): Host__TokenHashPepper, Host__Authentication__Microsoft__ClientSecret, Host__Authentication__Google__ClientSecret, Host__Authentication__GitHub__ClientSecret, and email provider API keys. Inject them via -e, Docker secrets, Kubernetes secrets, or your platform's secret store—never commit them to images or checked-in config.
Configuration
Use double-underscore environment variables (see configuration):
Database__Type—Sqlite,SqlServer,PostgreSQL,MySqlDatabase__ConnectionStringStorage__Type,Storage__Path(or cloud storage settings)Host__Authentication__Microsoft,Host__Authentication__Google, orHost__Authentication__GitHub— setClientIdandClientSecreton one provider; the host auto-detects in order Microsoft → Google → GitHub. Omit all credentials to run without UI sign-in (local development)EmailSettings__Provider— see Host email
When UI authentication is configured, organizational membership is always enforced for the active provider:
| Provider | Organizational gate | Optional finer-grained access |
|---|---|---|
| Microsoft Account | Directory TenantId (not common / consumers / organizations); token tid must match | AllowedEmailDomains, RequiredGroupIds (Entra group object IDs in token groups claims when consented) |
HostedDomain (Google Workspace; OAuth hd claim) | RequiredGroupIds — placeholder; standard Google OAuth does not emit group membership in the ID token | |
| GitHub | Organization (verified via GitHub API) | TeamSlugs |
Google Workspace groups: Unlike Microsoft Entra security groups, Google sign-in does not include group membership in the ID token. Restricting access to specific Google Groups requires the Admin SDK Directory API or Cloud Identity Groups API with a service account and domain-wide delegation (or equivalent admin consent)—not the end-user OAuth token alone. You may set Host:Authentication:Google:RequiredGroupIds to document intended groups for a future release; leave the list empty until Admin SDK integration is available.
Override structure or provider choice via double-underscore environment variables at runtime. The published image does not ship dev OAuth placeholders or sample secrets; configure every production value explicitly.
Health
GET /health runs checks against both the package catalog (IContext) and host identity (IHostIdentityContext) databases on the same connection.
Project layout
src/host/
├── AvantiPoint.Packages.Host/ # Web app + Docker entrypoint
├── AvantiPoint.Packages.Host.Admin/ # Identity, email, auth, syndication
└── AvantiPoint.Packages.Host.Database.* # Host identity EF migrations (4 providers)
AvantiPoint.Packages.Server remains a minimal reference host for development.
Self-Hosted (On-Premises)
Windows with IIS
-
Install Prerequisites:
- .NET 10.0 Hosting Bundle
- IIS with ASP.NET Core Module
-
Publish your application:
dotnet publish -c Release -o ./publish -
Create IIS Site:
- Create a new Application Pool with "No Managed Code"
- Create a new website pointing to the publish folder
- Set the Application Pool to the one created above
-
Configure
web.config: The publish process creates this automatically, but you can customize it:<?xml version="1.0" encoding="utf-8"?><configuration><location path="." inheritInChildApplications="false"><system.webServer><handlers><add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" /></handlers><aspNetCore processPath="dotnet"arguments=".\MyNuGetFeed.dll"stdoutLogEnabled="false"stdoutLogFile=".\logs\stdout"hostingModel="inprocess" /></system.webServer></location></configuration> -
Configure Storage: For production, use a SQL Server database and configure file storage on a reliable drive:
{"Database": {"Type": "SqlServer"},"Storage": {"Type": "FileStorage","Path": "D:\\PackageStorage"},"ConnectionStrings": {"SqlServer": "Server=localhost;Database=Packages;Integrated Security=true;"}} -
Configure upload size limits (package size):
IIS and ASP.NET Core enforce request size limits that affect how large a
.nupkgyou can push. To increase the limit:- In
web.config, adjust the IIS request filtering settings (values are in bytes):
<configuration><location path="." inheritInChildApplications="false"><system.webServer><security><requestFiltering><!-- Example: allow up to 512 MB uploads --><requestLimits maxAllowedContentLength="536870912" /></requestFiltering></security><aspNetCore processPath="dotnet"arguments=".\MyNuGetFeed.dll"stdoutLogEnabled="false"stdoutLogFile=".\logs\stdout"hostingModel="inprocess" /></system.webServer></location></configuration>- If you self-host with Kestrel (no IIS), you can also configure the max request body size in
Program.cs:
builder.WebHost.ConfigureKestrel(options =>{// Example: allow up to 512 MB uploadsoptions.Limits.MaxRequestBodySize = 512L * 1024L * 1024L;}); - In
Linux with Nginx
-
Install .NET 10.0 Runtime:
wget https://dot.net/v1/dotnet-install.shchmod +x dotnet-install.sh./dotnet-install.sh --channel 10.0 --runtime aspnetcore -
Publish and Deploy:
dotnet publish -c Release -o /var/www/nuget-feed -
Create systemd Service: Create
/etc/systemd/system/nuget-feed.service:[Unit]Description=AvantiPoint NuGet FeedAfter=network.target[Service]WorkingDirectory=/var/www/nuget-feedExecStart=/usr/bin/dotnet /var/www/nuget-feed/MyNuGetFeed.dllRestart=alwaysRestartSec=10KillSignal=SIGINTSyslogIdentifier=nuget-feedUser=www-dataEnvironment=ASPNETCORE_ENVIRONMENT=Production[Install]WantedBy=multi-user.target -
Configure Nginx: Create
/etc/nginx/sites-available/nuget-feed:server {listen 80;server_name packages.example.com;location / {proxy_pass http://localhost:5000;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection keep-alive;proxy_set_header Host $host;proxy_cache_bypass $http_upgrade;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;# Allow large package uploadsclient_max_body_size 100M;}} -
Enable and Start:
sudo systemctl enable nuget-feedsudo systemctl start nuget-feedsudo ln -s /etc/nginx/sites-available/nuget-feed /etc/nginx/sites-enabled/sudo systemctl reload nginx
Docker
For AvantiPoint.Packages.Host, use the root Dockerfile and Production host (Docker) above. For a custom feed generated from a template, build your own image from dotnet publish output and set ASPNETCORE_ENVIRONMENT=Production with the same double-underscore configuration variables.
Cloud Hosting
Azure App Service
-
Create App Service:
- Runtime: .NET 10
- Region: Choose closest to your users
- Pricing Tier: B1 or higher recommended
-
Configure Application Settings: In the Azure Portal, add these settings:
Database__Type:SqlServerStorage__Type:AzureBlobStorageStorage__Container:packages
-
Configure Connection Strings: Add connection strings with type "SQLAzure":
- Name:
SqlServer - Value: Your Azure SQL connection string
- Name:
Storage__ConnectionString(or use Managed Identity)
- Name:
-
Deploy: Using Azure CLI:
az webapp deployment source config-zip \--resource-group myResourceGroup \--name myNuGetFeed \--src ./publish.zip -
Configure Managed Identity (Recommended):
- Enable System Assigned Managed Identity on your App Service
- Grant the identity access to your Azure SQL and Blob Storage
- Remove
Storage__ConnectionStringfrom config (uses Managed Identity automatically)
AWS Elastic Beanstalk
-
Install Prerequisites: Add the AWS package:
dotnet add package AvantiPoint.Packages.Aws -
Configure for AWS: Update
appsettings.json:{"Database": {"Type": "SqlServer"},"Storage": {"Type": "AwsS3","Region": "us-west-2","Bucket": "my-nuget-packages","UseInstanceProfile": true}} -
Create IAM Role: Create a role for your Elastic Beanstalk instances with:
- S3 access to your bucket
- RDS access if using RDS
-
Deploy:
dotnet eb deploy
AWS EC2 with S3 and RDS
This is similar to the Linux self-hosted setup, but with AWS-specific configuration:
-
Set up EC2 instance with .NET 10 runtime
-
Configure IAM Role for the instance with S3 and RDS access
-
Update configuration:
{"Database": {"Type": "SqlServer"},"Storage": {"Type": "AwsS3","Region": "us-west-2","Bucket": "my-packages","UseInstanceProfile": true},"ConnectionStrings": {"SqlServer": "Server=mydb.xxxx.us-west-2.rds.amazonaws.com;Database=packages;User Id=admin;Password=..."}} -
Use the instance profile for authentication (no credentials in config)
Performance Considerations
Database
- Use connection pooling (enabled by default)
- For high traffic, consider read replicas
- Regular index maintenance for SQL Server/MySQL
Storage
- File Storage: Use fast SSD storage, consider NAS/SAN for multiple instances
- Cloud Storage: Use the same region as your application
- Enable CDN for package downloads in high-traffic scenarios
Caching
Configure output caching for metadata responses (add to Program.cs):
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder.Cache());
});
var app = builder.Build();
app.UseOutputCache();
app.UseRouting();
Load Balancing
For multiple instances:
- Use a shared database (SQL Server, MySQL, or PostgreSQL)
- Use cloud storage (S3 or Azure Blob)
- Configure session affinity on your load balancer (not required, but can improve performance)
SSL/TLS Configuration
Always use HTTPS in production! NuGet credentials are sent in headers.
Let's Encrypt with Nginx
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d packages.example.com
Azure App Service
SSL is automatic with custom domains. Just add your domain and Azure handles the certificate.
AWS with Application Load Balancer
Use AWS Certificate Manager for free SSL certificates, attach to your ALB.
Monitoring
Consider adding:
- Application Insights (Azure) or CloudWatch (AWS)
- Health check endpoints
- Logging to file/database/cloud
The production Host registers database health checks for both contexts and exposes GET /health (see Production host (Docker) above).
See Also
- Configuration - Detailed configuration options
- Database - Database setup
- Storage - Storage configuration