A nuget server created with linux-first approach.
There seems to be no good nuget server for hosting private nuget packages and caching, when working mainly with linux and dotnet core. Running windows just to host a several nuget packages seems like a big waste.
This project aims at following:
- provide self-hosted nuget server for private package hosting.
- hosted with kestrel on dotnet core 2.0
- released as ready to use docker image, preferably to be deployed to kubernetes.
- continuously tested with paket including several common project setup cases
- good performance when server is used by multiple clients, such as CI agents building various projects, downloading lots of packages at the same time.
- easy to develop on linux in VS Code, not only in VS on windows.
- if possible, implement caching mode for public packages from nuget.org
- Limited NuGet V2 API for hosting private packages. Includes endpoints
FindPackagesById(),Packages()andPUT /api/v2. Which is sufficient for clients to download, push, find or restore packages. - Caching proxy of with limited NuGet V3 API. It intercepts responses from selected
services of
https://api.nuget.org/v3/index.jsonreplacinghttps://api.nuget.org/v3by local LiGet server URL.- Allows to cache
.nupkgpackages on server, rather than downloading them from the Internet each time. - Caches package metadata and invalidates when upstream changes are detected using NuGet.CatalogReader.
- For end user effect is similar to running a mirror of nuget.org, but instead of downloading all packages, cache keeps only the ones which were ever requested.
- Allows to cache
Not implemented:
- V2 search, filter and alike queries. These seem to used only by UI or nuget gallery.
- Authentication and user-based access. Currently the server is open for all requests.
For dotnet CLI and nuget you need to configure nuget config ~/.nuget/NuGet/NuGet.Config with something like:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://www.nuget.org/api/v2" protocolVersion="2" />
<add key="liget" value="http://liget:9011/api/v2" protocolVersion="2" />
</packageSources>
</configuration>For paket, in paket.dependencies, just specify another source:
source http://liget:9011/api/v2
For dotnet CLI and nuget you need to configure nuget config ~/.nuget/NuGet/NuGet.Config with something like:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="liget-proxy" value="http://liget:9011/api/cache/v3/index.json" protocolVersion="3" />
<add key="liget" value="http://liget:9011/api/v2" protocolVersion="2" />
</packageSources>
</configuration>For paket, in paket.dependencies, just specify liget as the 2 only sources
source http://liget:9011/api/cache/v3/index.json
# public packages...
source http://liget:9011/api/v2
# private packages...
The simplest start command is
mkdir -p /tmp/liget-test
docker run -p 9011:9011 -v /tmp/liget-test/:/data tomzo/liget- Default current directory
/data. - Main process starts with tini as root,
then drops privileges to run as
ligetuser withdotnet.
For best reference see the docker directory with Dockerfile and startup script.
All packages, cache, and temporary data is stored in /data.
By default in /data/<backend>.
/data will be always owned by liget. Startup script switches uid/gid at start
to fit with whatever was mounted from the host.
The exception to this is when /data is owned by root, then liget has to run as root.``
Everything can be configured with environment variables:
LIGET_BACKENDby defaultsimple. Currently the only implementationLIGET_SIMPLE_ROOT_PATH- root directory used bysimplebackend. By default/data/simple.LIGET_BACKGROUND_TASKS- run background tasks periodically. By defaulttrue.LIGET_FS_MONITORING- monitorLIGET_SIMPLE_ROOT_PATHfor changes. By defaulttrue, which allows to drop packages directly toLIGET_SIMPLE_ROOT_PATHto be added to repo.LIGET_ALLOW_OVERWRITE, by defaultfalse. Whentrueallows push to replace previous package with same version.LIGET_FRAMEWORK_FILTERING, by defaulttrue. Not implemented.LIGET_ENABLE_DELISTING, by defaulttrue. Not implemented.LIGET_IGNORE_SYMBOLS, by defaultfalse. Not implemented.
Every dotnet Core application has .runtimeconfig.json, which can configure garbage collector.
You may want to set following:
LIGET_GC_CONCURRENT- by defaulttrueLIGET_GC_SERVER- by defaulttrue, beware though that this may cause higher memory use.LIGET_THREAD_POOL_MIN- minimal number of worker threads. By default 16.LIGET_THREAD_POOL_MAX- minimal number of worker threads. By default 32.
Kestrel specific:
LIGET_LIBUV_THREAD_COUNT- number of libuv threads handling the requests. By default not set, determined by libuv default.
LIGET_CACHE_PROXY_SOURCE_INDEX- address of original V3 API to cache. By defaulthttps://api.nuget.org/v3/index.json.LIGET_CACHE_INVALIDATION_CHECK_PERIOD- defines frequency at which a check with upstream server is made to see if cache is invalid. By default60(seconds).LIGET_NUPKG_CACHE_BACKEND- backend of the .nupkg caching proxy. By defaultdbreeze, which, currently is the only implementation.LIGET_NUPKG_CACHE_DBREEZE_ROOT_PATH- root directory where dbreeze will store cached packages. By default/data/cache/dbreeze.LIGET_NUPKG_CACHE_DBREEZE_BACKEND- storage backend of dbreeze, can bediskormemory. By defaultdisk.
LIGET_LOG_LEVEL- by defaultINFO.LIGET_LOG_BACKEND- by defaultconsole. Also can begelforcustom.
Default logging is to console. log4net is configured by /etc/liget/log4net.xml.
If you set LIGET_LOG_BACKEND=custom then it is expected that you will provide /etc/liget/log4net.xml.
Logging to graylog.
LIGET_LOG_GELF_HOST- no default. But should be configured whenLIGET_LOG_BACKEND=gelfLIGET_LOG_GELF_PORT- by default12201.
We are running load tests against the server under following setup:
- There is a dedicated 2-core VM with capped IO limits on disk:
- max read bytes/sec 64 MB/s
- max write bytes/sec 32 MB/s
- max read IO ops/s 3000
- max write IO ops/s 1200
- On the VM we start 2 docker containers with docker-compose, server has limit on memory of 550MB.
- LiGet is configured with defaults and:
LIGET_LIBUV_THREAD_COUNT=16
The stress tests run
- paket install to download about 500MB of packages, via liget caching proxy V3 API.
- nuget push of each package to repository using V2 API.
- Then 6 workers run
paket install -fvia liget caching proxy V3 API.
Under these conditions:
- cache download performance is good. The 6 workers end download within 9 minutes and are mostly constrained by IO limit. Same test on my local machine on SSD drive, passes in 3 minutes.
- memory usage reported by docker stats peaks at about 520 MB. So docker mem_limit of 550MB-600MB makes sense.
All building and tests are done with IDE and docker.
dotnet restore
dotnet build
Or
./tasks.sh build
./tasks.sh build
./tasks.sh test
Firsly, this project is using lots of code from other nuget servers, either as reference or actually porting pieces of code. Credits:
- TanukiSharp/MinimalNugetServer as minimal dotnet core setup
- emresenturk/NetCoreNugetServer another minimal dotnet core server
- official NuGet.Server
- NuGet.Lucene which is part of klondike
This project is licenced under Apache License 2.0.