{"id":30743166,"url":"https://github.com/kekyo/nuget-server","last_synced_at":"2026-01-20T17:30:03.641Z","repository":{"id":310842574,"uuid":"1040821843","full_name":"kekyo/nuget-server","owner":"kekyo","description":"Simple modernized NuGet server 📦","archived":false,"fork":false,"pushed_at":"2025-10-08T00:13:15.000Z","size":17705,"stargazers_count":46,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-08T01:23:21.407Z","etag":null,"topics":["authentication","docker","dotnet","nuget","nuget-server","package","simple"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kekyo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-08-19T14:56:24.000Z","updated_at":"2025-10-07T10:33:19.000Z","dependencies_parsed_at":"2025-08-20T16:00:07.198Z","dependency_job_id":"a749072c-2664-4802-a33e-db0d4a6898f0","html_url":"https://github.com/kekyo/nuget-server","commit_stats":null,"previous_names":["kekyo/nuget-server"],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/kekyo/nuget-server","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2Fnuget-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2Fnuget-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2Fnuget-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2Fnuget-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kekyo","download_url":"https://codeload.github.com/kekyo/nuget-server/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2Fnuget-server/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278956331,"owners_count":26075221,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-08T02:00:06.501Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","docker","dotnet","nuget","nuget-server","package","simple"],"created_at":"2025-09-04T02:03:31.100Z","updated_at":"2026-01-20T17:30:03.624Z","avatar_url":"https://github.com/kekyo.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# nuget-server\n\nSimple modernized NuGet server implementation.\n\n![nuget-server](images/nuget-server-120.png)\n\n[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![npm version](https://img.shields.io/npm/v/nuget-server.svg)](https://www.npmjs.com/package/nuget-server)\n[![Docker Image Version](https://img.shields.io/docker/v/kekyo/nuget-server.svg?label=docker)](https://hub.docker.com/r/kekyo/nuget-server)\n\n---\n\n[(日本語はこちら)](./README_ja.md)\n\n## What is this?\n\nA simple NuGet server implementation built on Node.js that provides essential NuGet v3 API endpoints.\n\nCompatible with `dotnet restore` and standard NuGet clients for package publishing, querying, and manually downloading.\n\nA modern browser-based UI is also provided:\n\n- You can refer to registered packages. You can check various package attributes.\n- You can download packages by version.\n- You can also publish (upload) packages.\n- You can manage user accounts.\n\n**Browse package list:**\n\n![Browse package list](images/nuget-server-ss-1.png)\n\n**Publishing packages:**\n\n![Publishing packages](images/nuget-server-ss-2.png)\n\n**User account managements:**\n\n![User account managements](images/nuget-server-ss-3.png)\n\n### Key Features\n\n- **Easy setup, run NuGet server in 10 seconds!**\n- NuGet V3 API compatibility: Support for modern NuGet client operations\n- No need database management: Store package file and nuspecs into filesystem directly, feel free any database managements\n- Package publish: Flexible client to upload `.nupkg` files via `HTTP POST` using cURL and others\n- Basic authentication: Setup authentication for publish and general access when you want it\n- Reverse proxy support: Configurable trusted reverse proxy handling for proper URL resolution\n- Modern Web UI with enhanced features:\n  - Multiple package upload: Drag \u0026 drop multiple .nupkg files at once\n  - User account management: Add/delete users, reset passwords (admin only)\n  - API password regeneration: Self-service API password updates\n  - Password change: Users can change their own passwords\n- Package importer: Included package importer from existing NuGet server\n- Docker image available\n\n## System Requirements\n\nNode.js 20.18.0 or later\n\nUses stacks: Node.js, Typescript, Vite, Vitest, prettier-max, screw-up, Fastify, Passport, zxcvbn, React, React MUI, react-infinite-scroll, notistack, typed-message, dayjs, commander, adm-zip, xml2js\n\nBy implementing everything in a TypeScript environment, we keep our development environment and development cycle simple.\n\n---\n\n## Installation\n\n```bash\nnpm install -g nuget-server\n```\n\nFor using Docker images, refer to a separate chapter.\n\n## Usage\n\n```bash\n# Start server on default port 5963\nnuget-server\n\n# Custom port\nnuget-server --port 3000\n\n# Multiple options\nnuget-server --port 3000 --users-file config/users.json --max-upload-size-mb 500\n```\n\nThe NuGet V3 API is served on the `/v3` path.\n\n- Default nuget-server served URL (Show UI): `http://localhost:5963`\n- Actual NuGet V3 API endpoint: `http://localhost:5963/v3/index.json`\n\nThe default URL provided by nuget-server can be changed using the `--base-url` option.\nThis is particularly necessary when public endpoint service using a reverse proxy. For details, refer to below chapter.\n\n## Configure the NuGet client\n\nnuget-server only supports the NuGet V3 API. Therefore, NuGet clients must always access it using the V3 API.\n\nIf you do not explicitly specify to use the V3 API, some implementations may fall back to the V3 API while others may not, potentially causing unstable behavior. Therefore, you must always specify it. Example below.\n\nAdd as package source:\n\n**For HTTP endpoints:**\n\n```bash\ndotnet nuget add source http://localhost:5963/v3/index.json \\\n  -n \"local\" --protocol-version 3 --allow-insecure-connections\n```\n\n**For HTTPS endpoints:**\n\n```bash\ndotnet nuget add source https://packages.example.com/v3/index.json \\\n  -n \"packages\" --protocol-version 3\n```\n\nOr specify in `nuget.config`:\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003cconfiguration\u003e\n  \u003cpackageSources\u003e\n    \u003cadd key=\"local\" value=\"http://localhost:5963/v3/index.json\"\n      protocolVersion=\"3\" allowInsecureConnections=\"true\" /\u003e\n  \u003c/packageSources\u003e\n\u003c/configuration\u003e\n```\n\n### Publish packages\n\nUpload packages by `HTTP POST` method, using cURL or any HTTP client with `/api/publish` endpoint:\n\n```bash\n# Upload \"MyPackage.1.0.0.nupkg\" file\ncurl -X POST http://localhost:5963/api/publish \\\n  --data-binary @MyPackage.1.0.0.nupkg \\\n  -H \"Content-Type: application/octet-stream\"\n```\n\nYou may be dissatisfied with publishing using this method. The dotnet command includes `dotnet nuget push`, which is the standard approach.\nHowever, in my experience, this protocol uses `multipart/form-data` for transmission, which has caused issues with gateway services, reverse proxies, load balancers, and similar components.\nTherefore, the current nuget-server does not implement this method and instead uses the simplest binary transmission procedure.\n\nAnother advantage is that when authentication is enabled, you don't need to manage Basic authentication and V3 API keys separately.\nYou might still feel issue with managing read operations and publish operation with the same key,\nbut in that case, you can simply separate the users.\n\nFor authentication feature, please refer to below chapter.\n\n---\n\n## Package storage configuration\n\n### Storage location\n\nBy default, packages are stored in the `./packages` directory relative to where you run nuget-server.\nYou can customize this location using the `--package-dir` option:\n\n```bash\n# Use default ./packages directory\nnuget-server\n\n# Use custom directory (relative or absolute path)\nnuget-server --package-dir /another/package/location\n```\n\n### Package storage layout\n\nPackages are stored in the filesystem using the following structure:\n\n```\npackages/\n├── PackageName/\n│   ├── 1.0.0/\n│   │   ├── PackageName.1.0.0.nupkg\n│   │   ├── PackageName.nuspec\n│   │   └── icon.png            # Package icon (if present)\n│   └── 2.0.0/\n│       ├── PackageName.2.0.0.nupkg\n│       ├── PackageName.nuspec\n│       └── icon.jpg            # Package icon (if present)\n└── AnotherPackage/\n    └── 1.5.0/\n        ├── AnotherPackage.1.5.0.nupkg\n        ├── AnotherPackage.nuspec\n        └── icon.png            # Package icon (if present)\n```\n\n### Backup and restore\n\nYou can backup the package directory using simply `tar` or other achiver:\n\n```bash\ncd /your/server/base/dir\ntar -cf - ./packages | lz4 \u003e backup-packages.tar.lz4\n```\n\nRestore is simply extract it and re-run nuget-server with the same package directory configuration, because nuget-server does not use any specialized storage such as databases.\n\n---\n\n## Configuration\n\nnuget-server supports configuration through command-line options, environment variables, and JSON file.\n\nSettings are applied in the following order (highest to lowest priority):\n\n1. Command-line options\n2. Environment variables\n3. `config.json`\n4. Default values\n\n## Configuration file structure\n\nYou can specify a custom configuration file:\n\n```bash\n# Using command line option\nnuget-server --config-file /path/to/config.json\n# or short alias\nnuget-server -c /path/to/config.json\n\n# Using environment variable\nexport NUGET_SERVER_CONFIG_FILE=/path/to/config.json\nnuget-server\n```\n\nIf not specified, nuget-server looks for `./config.json` in the current directory.\n\n### config.json structure\n\nCreate a `config.json` file:\n\n```json\n{\n  \"port\": 5963,\n  \"baseUrl\": \"http://localhost:5963\",\n  \"packageDir\": \"./packages\",\n  \"usersFile\": \"./users.json\",\n  \"realm\": \"Awsome nuget-server\",\n  \"logLevel\": \"info\",\n  \"trustedProxies\": [\"127.0.0.1\", \"::1\"],\n  \"authMode\": \"none\",\n  \"sessionSecret\": \"\u003cyour-secret-here\u003e\",\n  \"passwordMinScore\": 2,\n  \"passwordStrengthCheck\": true,\n  \"maxUploadSizeMb\": 100\n}\n```\n\nAll fields are optional. Only include the settings you want to override.\nBoth `packageDir` and `usersFile` paths can be absolute or relative. If relative, they are resolved from the directory containing the `config.json` file.\n\n---\n\n## Authentication\n\nnuget-server also supports authentication.\n\n| Authentication Mode | Details                                                       | Auth Initialization |\n| :------------------ | :------------------------------------------------------------ | :------------------ |\n| `none`              | Default. No authentication required                           | Not required        |\n| `publish`           | Authentication required only for package publishing           | Required            |\n| `full`              | Authentication required for all operations (must login first) | Required            |\n\nTo enable authentication on the NuGet server, first register an initial user using the `--auth-init` option.\n\n### Initialize\n\nCreate an initial admin user interactively:\n\n```bash\nnuget-server --auth-init\n```\n\nThis command will:\n\n1. Prompt for admin username (default: `admin`)\n2. Prompt for password (with strength checking, masked input)\n3. Create `users.json`\n4. Exit after initialization (server does not start)\n\nWhen enabling authentication using a Docker image, use this option to generate the initial user.\n\n### Example session\n\n```\nInitializing authentication...\nEnter admin username [admin]:\nEnter password: ********\nConfirm password: ********\n\n============================================================\nAdmin user created successfully!\n============================================================\nUsername: admin\nPassword: *********************\n============================================================\n```\n\n### User Management\n\nUsers added with `--auth-init` automatically become administrator users.\nAdministrator users can add or remove other users via the UI. They can also reset user passwords.\n\n![User administration](images/nuget-server-ss-4.png)\n\nWhile administrator users can also be assigned API passwords (described later), we recommend separating users for management whenever possible.\n\n### Using the API password\n\nThe NuGet server distinguishes between the password used to log in to the UI and the password used by NuGet clients when accessing the server.\nThe password used by NuGet clients when accessing the server is called the \"API password,\"\nand access is granted using the combination of the user and the API password.\n\nPlease log in by displaying the UI in the browser.\nSelect the “API password” menu from the UI menu to generate an API password.\nUsing this API password will enable access from the NuGet client.\n\n![API password](images/nuget-server-ss-5.png)\n\nHere is an example of using the API password:\n\n```bash\n# Add source with API password\ndotnet nuget add source http://localhost:5963/v3/index.json \\\n  -n \"local\" \\\n  -u admin \\\n  -p xxxxxxxxxxxxxxxxxxxxxx \\\n  --protocol-version 3 --store-password-in-clear-text --allow-insecure-connections\n```\n\nOr specify `nuget.config` with credentials:\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003cconfiguration\u003e\n  \u003cpackageSources\u003e\n    \u003cadd key=\"local\" value=\"http://localhost:5963/v3/index.json\"\n      protocolVersion=\"3\" allowInsecureConnections=\"true\" /\u003e\n  \u003c/packageSources\u003e\n  \u003cpackageSourceCredentials\u003e\n    \u003clocal\u003e\n      \u003cadd key=\"Username\" value=\"reader\" /\u003e\n      \u003cadd key=\"ClearTextPassword\" value=\"xxxxxxxxxxxxxxxxxxxxxx\" /\u003e\n    \u003c/local\u003e\n  \u003c/packageSourceCredentials\u003e\n\u003c/configuration\u003e\n```\n\nFor package publishing:\n\n```bash\n# Publish packages with API password\ncurl -X POST http://localhost:5963/api/publish \\\n  -u admin:xxxxxxxxxxxxxxxxxxxxxx \\\n  --data-binary @MyPackage.1.0.0.nupkg \\\n  -H \"Content-Type: application/octet-stream\"\n```\n\nWhen publishing a package, you can send the package by setting Basic authentication in the `Authorization` header.\n\n### Password strength requirements\n\nnuget-server uses the `zxcvbn` library to enforce strong password requirements:\n\n- Evaluates password strength on a scale of 0-4 (Weak to Very Strong)\n- Default minimum score: 2 (Good)\n- Checks against common passwords, dictionary words, and patterns\n- Provides real-time feedback during password creation\n\nConfigure password requirements in `config.json`:\n\n```json\n{\n  \"passwordMinScore\": 2, // 0-4, default: 2 (Good)\n  \"passwordStrengthCheck\": true // default: true\n}\n```\n\nThe NuGet server stores both \"password\" and \"API password\" as SALT hashed information, so no plaintext passwords are ever saved.\nHowever, if you do not use HTTPS (TLS), be aware that the `Authorization` header will contain the plaintext password, making it vulnerable to sniffing.\nWhen makes public endpoint, protect communications using HTTPS.\n\n---\n\n## Import packages from another NuGet server\n\nImport all packages from another NuGet server to your local nuget-server instance.\nThis feature can be used when migrating the foreign NuGet server to nuget-server.\n\n### Package import from another NuGet server\n\nImport packages interactively in CLI:\n\n```bash\nnuget-server --import-packages --package-dir ./packages\n```\n\nThis command will:\n\n1. Prompt for source NuGet server URL\n2. Ask if authentication is required\n3. If needed, prompt for username and password (masked input)\n4. Discover all packages from the source server\n5. Download and import all packages to local storage\n6. Display progress for each package (1% intervals)\n7. Exit after import (server does not start)\n\n### Import behavior\n\n- Existing packages with the same version will be overwritten\n- Failed imports are logged with error details\n- Progress is reported at 1% intervals to reduce log noise\n- Package icons are preserved during import\n\nParallel downloads are not done. This is to avoid making a large number of requests to the repository.\n\nThis feature is a type of downloader.\nTherefore, it does not need to be run on the actual host where it will operate.\nYou can perform the import process in advance on a separate host and then move the `packages` directory as-is.\n\n### Example session\n\n```\nStarting package import...\nEnter source NuGet server URL [http://host.example.com/repository/nuget/]: https://nexus.example.com/repository/nuget/\nDoes the server require authentication? [y/N]: y\nEnter username: reader\nEnter password: **********\n\n============================================================\nImport Configuration:\nSource: https://nexus.example.com/repository/nuget/\nTarget: ./packages\nAuthentication: reader (password hidden)\n============================================================\n\nStart importing packages? (existing packages will be overwritten) [y/N]: y\n\nDiscovering packages from source server...\nFound 125 packages with 563 versions total.\nStarting package import...\nProgress: 100/563 packages (17%) - MyPackage.Core@1.2.3\nProgress: 563/563 packages (100%) - AnotherPackage@2.0.0\n\n============================================================\nImport Complete!\n============================================================\nTotal packages: 125\nTotal versions: 563\nSuccessfully imported: 563\nFailed: 0\nTime elapsed: 125.3 seconds\n============================================================\n```\n\n---\n\n## Reverse proxy interoperability\n\nThe server supports running behind a reverse proxy.\nFor example, when you have a public URL like `https://nuget.example.com` and run nuget-server on a host within your internal network via a gateway.\n\nIn such cases, you MUST specify the base URL of the public URL to ensure the NuGet V3 API can provide the correct sub-endpoint address.\n\n### URL resolving\n\nThe server resolves URLs using the following priority order:\n\n1. Fixed base URL (highest priority): When `--base-url` option is specified, it always takes precedence\n2. Trusted proxy headers: When trusted proxies are configured with `--trusted-proxies`:\n   - HTTP `Forwarded` header (proto, host, port)\n   - Traditional `X-Forwarded-*` headers (`X-Forwarded-Proto`, `X-Forwarded-Host`, `X-Forwarded-Port`)\n3. Standard request information (fallback): Uses `Host` header when proxy headers are not available\n\nFor example `--base-url` option:\n\n- nuget-server served public base URL: `https://packages.example.com`\n- Actual NuGet V3 API endpoint: `https://packages.example.com/v3/index.json`\n\n```bash\n# Configure served base URL (do not include /v3 path)\nnuget-server --base-url https://packages.example.com\n\n# Add as NuGet source (HTTPS - no --allow-insecure-connections needed)\ndotnet nuget add source https://packages.example.com/v3/index.json \\\n  -n \"packages\" --protocol-version 3\n```\n\nAnother option, you can configure with trusted proxy addresses:\n\n```bash\n# Configure trusted proxies for proper host header handling\nnuget-server --trusted-proxies \"10.0.0.1,192.168.1.100\"\n```\n\nEnvironment variables are also supported:\n\n```bash\nexport NUGET_SERVER_BASE_URL=https://packages.example.com\nexport NUGET_SERVER_TRUSTED_PROXIES=10.0.0.1,192.168.1.100\nexport NUGET_SERVER_CONFIG_FILE=/path/to/config.json\nexport NUGET_SERVER_USERS_FILE=/path/to/users.json\nexport NUGET_SERVER_SESSION_SECRET=your-secret-key-here\nexport NUGET_SERVER_MAX_UPLOAD_SIZE_MB=500\n```\n\n---\n\n## Docker usage\n\nDocker images are available for multiple architectures:\n\n- `linux/amd64` (x86_64)\n- `linux/arm64` (aarch64)\n\nWhen pulling the image, Docker automatically selects the appropriate architecture for your platform.\n\n### Quick start\n\nSuppose you have configured the following directory structure for persistence (recommended):\n\n```\ndocker-instance/\n├── data/\n│   ├── config.json\n│   └── user.json\n└── packages/\n    └── (package files)\n```\n\nExecute as follows:\n\n```bash\n# Pull and run the latest version\ndocker run -d -p 5963:5963 \\\n  -v $(pwd)/data:/data \\\n  -v $(pwd)/packages:/packages \\\n  kekyo/nuget-server:latest\n\n# Or with Docker Compose\ncat \u003e docker-compose.yml \u003c\u003c EOF\nversion: '3'\nservices:\n  nuget-server:\n    image: kekyo/nuget-server:latest\n    ports:\n      - \"5963:5963\"\n    volumes:\n      - ./data:/data\n      - ./packages:/packages\n    environment:\n      - NUGET_SERVER_AUTH_MODE=publish\nEOF\n\ndocker-compose up -d\n```\n\nYour NuGet server is now available at:\n\n- Web UI: `http://localhost:5963`\n- NuGet V3 API: `http://localhost:5963/v3/index.json`\n\n### Permission requirements\n\nThe Docker container runs as the `nugetserver` user (UID 1001) for security reasons. You need to ensure that the mounted directories have the appropriate permissions for this user to write files.\n\n**Set proper permissions for mounted directories:**\n\n```bash\n# Create directories if they don't exist\nmkdir -p ./data ./packages\n\n# Set ownership to UID 1001 (matches the container's nugetserver user)\nsudo chown -R 1001:1001 ./data ./packages\n```\n\n**Important**: Without proper permissions, you may encounter `500 Permission Denied` errors when:\n\n- Creating or updating user accounts\n- Publishing packages\n- Writing configuration files\n\n### Basic usage\n\n```bash\n# Run with default settings (port 5963, packages and data stored in mounted volumes)\ndocker run -p 5963:5963 \\\n  -v $(pwd)/data:/data \\\n  -v $(pwd)/packages:/packages \\\n  kekyo/nuget-server:latest\n\n# With authentication (users.json will be created in /data)\ndocker run -p 5963:5963 \\\n  -v $(pwd)/data:/data \\\n  -v $(pwd)/packages:/packages \\\n  -e NUGET_SERVER_AUTH_MODE=publish \\\n  kekyo/nuget-server:latest\n```\n\nYou can also change settings using environment variables or command-line options, but the easiest way to configure settings is to use `config.json`.\n\nSince the Docker image has mount points configured, you can mount `/data` and `/packages` as shown in the example above and place `/data/config.json` there to flexibly configure settings. Below is an example of `config.json`:\n\n```json\n{\n  \"port\": 5963,\n  \"baseUrl\": \"http://localhost:5963\",\n  \"realm\": \"Awsome nuget-server\",\n  \"logLevel\": \"info\",\n  \"authMode\": \"publish\"\n}\n```\n\nWhen initializing credentials or importing packages, configure `config.json` and perform the operation via the CLI before launching the Docker image:\n\n```bash\n# Initialize authentication\nnuget-server -c ./data/config.json --auth-init\n```\n\n### Volume mounts and configuration\n\n- `/data`: Default data directory for `config.json`, `users.json` and other persistent data\n- `/packages`: Default package storage directory (mounted to persist packages)\n\n**Default behavior**: The Docker image runs with `--users-file /data/users.json --package-dir /packages` by default.\n\n**Configuration priority** (highest to lowest):\n\n1. Custom command line arguments (when overriding CMD)\n2. Environment variables (e.g., `NUGET_SERVER_PACKAGE_DIR`)\n3. `config.json` file (if explicitly specified)\n4. Default command line arguments in Dockerfile\n\n### Example of Automatic Startup Using systemd\n\nVarious methods exist for automatically starting containers with systemd.\nBelow is a simple example of configuring a systemd service using Podman.\nThis is a simple service unit file used before quadlets were introduced to Podman.\nBy placing this file and having systemd recognize it, you can automatically start the nuget-server:\n\n`/etc/systemd/system/container-nuget-server.service`:\n\n```ini\n# container-nuget-server.service\n\n[Unit]\nDescription=Podman container-nuget-server.service\nDocumentation=man:podman-generate-systemd(1)\nWants=network-online.target\nAfter=network-online.target\nRequiresMountsFor=%t/containers\n\n[Service]\nEnvironment=PODMAN_SYSTEMD_UNIT=%n\nRestart=always\nRestartSec=30\nTimeoutStopSec=70\nExecStart=/usr/bin/podman run \\\n        --cidfile=%t/%n.ctr-id \\\n        --cgroups=no-conmon \\\n        --rm \\\n        --sdnotify=conmon \\\n        --replace \\\n        -d \\\n        -p 5963:5963 \\\n        --name nuget_server \\\n        -v /export/data:/data -v /export/packages:/packages docker.io/kekyo/nuget-server:latest\nExecStop=/usr/bin/podman stop \\\n        --ignore -t 10 \\\n        --cidfile=%t/%n.ctr-id\nExecStopPost=/usr/bin/podman rm \\\n        -f \\\n        --ignore -t 10 \\\n        --cidfile=%t/%n.ctr-id\nType=notify\nNotifyAccess=all\n\n[Install]\nWantedBy=default.target\n```\n\n---\n\n## Building the Docker image (Advanced)\n\nThe build of the nuget-server Docker image uses Podman.\n\n### Multi-platform build with Podman (recommended)\n\nUse the provided multi-platform build script that uses Podman to build for all supported architectures:\n\n```bash\n# Build for all platforms (local only, no push)\n./build-docker-multiplatform.sh\n\n# Build and push to Docker Hub\n./build-docker-multiplatform.sh --push\n\n# Build for specific platforms only\n./build-docker-multiplatform.sh --platforms linux/amd64,linux/arm64\n\n# Push with custom Docker Hub username\nOCI_SERVER_USER=yourusername ./build-docker-multiplatform.sh --push\n\n# Inspect existing manifest\n./build-docker-multiplatform.sh --inspect\n```\n\n**Important**: For cross-platform builds, QEMU emulation must be configured first:\n\n```bash\n# Option 1: Use QEMU container (recommended)\nsudo podman run --rm --privileged docker.io/multiarch/qemu-user-static --reset -p yes\n\n# Option 2: Install system packages\n# Ubuntu/Debian:\nsudo apt-get update \u0026\u0026 sudo apt-get install -y qemu-user-static\n# Fedora/RHEL:\nsudo dnf install -y qemu-user-static\n\n# Verify QEMU is working:\npodman run --rm --platform linux/arm64 alpine:latest uname -m\n# Should output: aarch64\n```\n\nWithout QEMU, you can only build for your native architecture.\n\n---\n\n## Note\n\n### Tested environment\n\n- Ubuntu 24.04 x86-64\n- Ubuntu 24.04 x86-64 (podman hosted container)\n- Ubuntu 22.04 x86-64\n- Ubuntu 24.04 arm64\n- Ubuntu 24.04 arm64 (podman hosted container)\n- Cloudflare tunnel serviced global IPv4/IPv6 endpoint\n- Direct global IPv4 endpoint\n\n### Supported NuGet V3 API endpoints\n\nThe server implements a subset of the NuGet V3 API protocol:\n\n- Service index: `/v3/index.json`\n- Package content: `/v3/package/{id}/index.json`\n- Package downloads: `/v3/package/{id}/{version}/{filename}`\n- Registration index: `/v3/registrations/{id}/index.json`\n\nNote: Visual Studio does not correctly reference the service index in the NuGet protocol specification when accessing NuGet servers.\n(Based on my testing, access via Rider and the dotnet CLI works without issues.)\n\nTherefore, the contents of the service index are merely formalities,\nand you can access NuGet servers directly via fixed paths without referencing the service index.\n\n### Non-interactive mode (CI/CD)\n\nThe `--auth-init` and `--import-packages` options require interactive responses from the operator.\nTherefore, attempting to automate these may not work properly.\nIn such cases, you can provide credentials via environment variables:\n\n```bash\nexport NUGET_SERVER_ADMIN_USERNAME=admin\nexport NUGET_SERVER_ADMIN_PASSWORD=MySecurePassword123!\nnuget-server --auth-init --config-file ./config.json\n```\n\nThis allows initialization in CI/CD pipelines without user interaction.\n\n### Session Security\n\nFor special configurations (or to support persistent sessions), you can set a fixed session secret. Specify a sufficiently long value for the secret:\n\n```bash\nexport NUGET_SERVER_SESSION_SECRET=$(openssl rand -base64 32)\nnuget-server\n```\n\n(Or use `config.json`.)\n\nIf not set, a random secret is generated (warning will be logged).\n\n### Response to requests for non-existent package IDs\n\nBy default, nuget-server responds to NuGet V3 API requests for non-existent package IDs by returning an empty package list (endpoint: `/v3/package/{id}/index.json`).\nThis violates the NuGet V3 API specification: [PackageBaseAddress/3.0.0 response](https://learn.microsoft.com/en-us/nuget/api/package-base-address-resource#response)\n\nThis behavior exists to avoid a weird implementation where NuGet clients, when configured with multiple package sources, return an error immediately if any single source returns an error like a 404, without waiting for results from other sources.\n\nFor details, see the following discussion:\n\nhttps://github.com/NuGet/Home/issues/6373\n\nTo prevent users from being troubled by this behavior (I too was greatly troubled), I deliberately modified the response to violate the V3 API specification. If a fully V3-compliant response (i.e., returning a 404 error) is required, you can achieve this by setting the `missingPackageResponse` configuration to `not-found`.\n\n```json\n{\n  “missingPackageResponse”: “not-found”  // Default is “empty-array”\n}\n```\n\nAlternatively, you can use the CLI option `--missing-package-response \u003cmode\u003e` or the environment variable `NUGET_SERVER_MISSING_PACKAGE_RESPONSE`.\n\n### Configuration Reference Table\n\nAll configuration options can be set via CLI arguments, environment variables, or config.json. The priority order is: **CLI \u003e Environment Variable \u003e config.json \u003e Default**.\n\n| CLI Option                          | Environment Variable                      | config.json Key          | Description                                                    | Valid Values                               | Default                  |\n| ----------------------------------- | ----------------------------------------- | ------------------------ | -------------------------------------------------------------- | ------------------------------------------ | ------------------------ |\n| `-p, --port \u003cport\u003e`                 | `NUGET_SERVER_PORT`                       | `port`                   | Server port number                                             | 1-65535                                    | 5963                     |\n| `-b, --base-url \u003curl\u003e`              | `NUGET_SERVER_BASE_URL`                   | `baseUrl`                | Fixed base URL for API endpoints (overrides auto-detection)    | Valid URL                                  | Auto-detect              |\n| `-d, --package-dir \u003cdir\u003e`           | `NUGET_SERVER_PACKAGE_DIR`                | `packageDir`             | Package storage directory                                      | Valid path                                 | `./packages`             |\n| `-c, --config-file \u003cpath\u003e`          | `NUGET_SERVER_CONFIG_FILE`                | N/A                      | Path to config.json file                                       | Valid path                                 | `./config.json`          |\n| `-u, --users-file \u003cpath\u003e`           | `NUGET_SERVER_USERS_FILE`                 | `usersFile`              | Path to users.json file                                        | Valid path                                 | None                     |\n| `-r, --realm \u003crealm\u003e`               | `NUGET_SERVER_REALM`                      | `realm`                  | Authentication realm                                           | String                                     | `nuget-server [version]` |\n| `-l, --log-level \u003clevel\u003e`           | `NUGET_SERVER_LOG_LEVEL`                  | `logLevel`               | Logging verbosity level                                        | `debug`, `info`, `warn`, `error`, `ignore` | `info`                   |\n| `--trusted-proxies \u003cips\u003e`           | `NUGET_SERVER_TRUSTED_PROXIES`            | `trustedProxies`         | Comma-separated list of trusted proxy IPs                      | IP addresses                               | None                     |\n| `--auth-mode \u003cmode\u003e`                | `NUGET_SERVER_AUTH_MODE`                  | `authMode`               | Authentication mode                                            | `none`, `publish`, `full`                  | `none`                   |\n| N/A                                 | `NUGET_SERVER_SESSION_SECRET`             | `sessionSecret`          | Secret key for session management (required for auth)          | String                                     | None                     |\n| N/A                                 | `NUGET_SERVER_PASSWORD_MIN_SCORE`         | `passwordMinScore`       | Minimum password strength score                                | 0-4                                        | 2                        |\n| N/A                                 | `NUGET_SERVER_PASSWORD_STRENGTH_CHECK`    | `passwordStrengthCheck`  | Enable password strength checking                              | `true`, `false`                            | `true`                   |\n| N/A                                 | `NUGET_SERVER_DUPLICATE_PACKAGE_POLICY`   | `duplicatePackagePolicy` | Policy for handling duplicate package uploads                  | `overwrite`, `ignore`, `error`             | `ignore`                 |\n| `--max-upload-size-mb \u003csize\u003e`       | `NUGET_SERVER_MAX_UPLOAD_SIZE_MB`         | `maxUploadSizeMb`        | Maximum package upload size in MB                              | 1-10000                                    | 100                      |\n| `--missing-package-response \u003cmode\u003e` | `NUGET_SERVER_MISSING_PACKAGE_RESPONSE`   | `missingPackageResponse` | Response mode for missing packages                             | `empty-array`, `not-found`                 | `empty-array`            |\n| N/A                                 | `NUGET_SERVER_AUTH_FAILURE_DELAY_ENABLED` | N/A                      | Enable progressive delays for failed auth attempts             | `true`, `false`                            | `true`                   |\n| N/A                                 | `NUGET_SERVER_AUTH_FAILURE_MAX_DELAY`     | N/A                      | Maximum delay for failed auth attempts (ms)                    | Number                                     | 10000                    |\n| `--auth-init`                       | N/A                                       | N/A                      | Initialize authentication with interactive admin user creation | Flag                                       | N/A                      |\n| `--import-packages`                 | N/A                                       | N/A                      | Import packages from another NuGet server interactively        | Flag                                       | N/A                      |\n\n---\n\n## Other\n\nNuGet is the .NET package system, and NuGet servers host it.\nThere are likely differing opinions about NOT building this with .NET.\n\nI chose this configuration as the minimal means to achieve the goal of requiring a private NuGet server.\nIn fact, I feel it ensured the shortest development time relative to scale while providing sufficient functionality, so I'm very satisfied.\n\nThere are other reasons too, but first, just give it a try.\nI think it'll be a great fit for anyone who thinks they need a private NuGet server.\n\n## Discussions and Pull Requests\n\nFor discussions, please refer to the [GitHub Discussions page](https://github.com/kekyo/nuget-server/discussions). We have currently stopped issue-based discussions.\n\nPull requests are welcome! Please submit them as diffs against the `develop` branch and squashed changes before send.\n\n## License\n\nUnder MIT.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkekyo%2Fnuget-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkekyo%2Fnuget-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkekyo%2Fnuget-server/lists"}