Documentation

Configuration

velodex reads one TOML file, passed with --config <path>. A few operational settings double as flags or VELODEX_* environment variables, which override the file. Precedence is defaults < TOML file < environment < flags.

Top level

SettingFlagEnvironmentTOML keyDefault
Bind host--hostVELODEX_HOSThost127.0.0.1
Bind port--portVELODEX_PORTport4433
Data directory--data-dirVELODEX_DATA_DIRdata_dirvelodex-data
Offline mode--offlineVELODEX_OFFLINEofflinefalse
Config file--config / -c(n/a)(n/a)(none)
Cache freshness (seconds)(file/env only)VELODEX_CACHE_TTL_SECScache_ttl_secs300
Indexes(file only)(n/a)[[index]](see below)
Rate limits(file only)(n/a)[rate_limit](see below)

Environment variables sit between the file and flags: a VELODEX_* value overrides the TOML file, and a flag overrides the variable. Only scalar settings are environment-configurable — the [[index]] topology and [rate_limit] block stay file-only, since neither maps cleanly to a flat variable. An empty variable is treated as unset. The [log] block also reads variables (VELODEX_LOG_LEVEL, VELODEX_LOG_FORMAT, VELODEX_LOG_SINK, VELODEX_LOG_FILE); see [log].

cache_ttl_secs is a fallback: when an upstream response carries a usable Cache-Control lifetime (s-maxage or max-age), that lifetime governs the page instead. The fallback applies when the header is absent, no-cache/no-store, or zero. Artifacts never expire; they are content-addressed by sha256, so a changed upstream file is a new entry on the page rather than a mutation.

offline = true disables upstream network access for configured cached indexes. Cached project pages, PEP 658 metadata siblings, and artifacts serve from disk. A cold cached-index miss returns 503; virtual-index routes still serve any hosted layer that can answer. Use velodex mirror sync before enabling offline mode on a machine that must run without network access.

[[index]]

Each [[index]] table declares one index. name is required; exactly one of cached, hosted, or layers selects the role. velodex rejects unknown keys.

KeyRoleMeaningDefault
nameallIdentifier other indexes reference in layers(required)
routeallURL prefix the index is served undersame as name
cachedcachedUpstream simple-index URL to cache
usernamecachedBasic-auth username for the upstream(none)
passwordcachedBasic-auth password for the upstream(none)
tokencachedBearer token; takes precedence over username/password(none)
upstream_concurrencycachedCap on concurrent upstream fetches; 0 is unlimited and the default0
offlinecachedServe this cached index from disk onlyfalse
prefetchcachedPackage and artifact selection for velodex mirror(see below)
hostedhostedtrue marks this index as a hosted store (implied by upload_token)false
upload_tokenhostedBasic-auth password uploads must present; unset disables uploads(none)
volatilehostedAllow delete and overwritetrue
layersvirtualOrdered index names to compose; first match per filename wins
uploadvirtualHosted layer that receives uploadsfirst hosted layer
policyallNested index policy tableempty
webhookallSigned delivery targets for upload and index-change eventsnone

A route is a raw URL path prefix. It must be one or more non-empty path segments separated by /; each segment may contain only ASCII letters, digits, -, ., _, and ~. Startup rejects routes with a leading or trailing /, empty segments, percent encoding, traversal segments, control characters, spaces, and routes whose first segment is reserved for Velodex endpoints such as browse, stats, +stats, +status, api-docs, metrics, and pkg.

Declaring any [[index]] replaces the default topology, which is:

[[index]]
name = "pypi"
cached = "https://pypi.org/simple/"

[[index]]
name = "hosted"
hosted = true

[[index]]
name = "root/pypi"
layers = ["hosted", "pypi"]
upload = "hosted"

Startup rejects duplicate names, duplicate routes, invalid routes, layers entries that name no index, and an upload target that is not a hosted index.

[index.policy]

Policy rules apply to the index that owns the table. A cached-index policy filters that cache; a hosted policy filters direct uploads and hosted-route reads; a virtual policy filters the merged index clients use. Project names are compared after PEP 503 normalization.

[[index]]
name = "root/pypi"
layers = ["hosted", "pypi"]
upload = "hosted"

[index.policy]
allow_projects = ["flask", "requests"]
block_projects = ["bad-package"]
allow_versions = ">=1,<3"
allow_package_types = ["wheel"]
block_package_types = ["sdist"]
allow_wheel_pythons = ["py3", "cp313"]
block_wheel_platforms = ["win_amd64"]
max_file_size_bytes = 104857600
max_project_size_bytes = 1073741824
KeyMeaning
allow_projectsOnly these normalized projects may be served, mirrored, or uploaded
block_projectsThese normalized projects are denied
allow_versionsPEP 440 specifier set accepted for parsed distribution filenames
allow_package_typesAccepted parsed file types: wheel, sdist
block_package_typesDenied parsed file types: wheel, sdist
allow_wheel_pythonsAccepted wheel Python tags, matched against each dot-compressed tag segment
block_wheel_pythonsDenied wheel Python tags
allow_wheel_platformsAccepted wheel platform tags, matched against each dot-compressed tag segment
block_wheel_platformsDenied wheel platform tags
max_file_size_bytesMaximum file size from the Simple API size field or from an uploaded file
max_project_size_bytesMaximum sum of retained file sizes for one project detail page

File and project size rules require declared sizes. A file without size is denied by max_file_size_bytes; a project page with any retained file lacking size is denied by max_project_size_bytes. Active policies use the buffered Simple-page path so file lists and PEP 691 versions are filtered together before velodex serves bytes.

[index.prefetch]

Cached indexes can declare the default selection for velodex mirror plan, velodex mirror sync, and velodex mirror verify. CLI flags add package selectors and override booleans or mode for one run.

[[index]]
name = "pypi"
cached = "https://pypi.org/simple/"

[index.prefetch]
mode = "selected"
packages = ["requests>=2,<3"]
requirements = ["requirements.txt"]
include_wheels = true
include_sdists = true
python_tags = ["py3", "cp312"]
abi_tags = ["none", "abi3"]
platform_tags = ["any", "manylinux_2_28_x86_64"]
max_file_size_bytes = 524288000
metadata_only = false
KeyValuesDefault
modeselected, all, metadata-onlyselected
packagespackage selectors such as flask>=3[]
requirementsrequirements or constraints files[]
include_wheelsbooleantrue
include_sdistsbooleantrue
python_tagswheel Python tags[]
abi_tagswheel ABI tags[]
platform_tagswheel platform tags[]
max_file_size_bytespositive integer(none)
metadata_onlybooleanfalse

mode = "all" reads the upstream root Simple project list and then visits matching projects. Artifact filters apply after a project page is fetched. mode = "metadata-only" implies metadata_only = true.

[rate_limit]

Rate limits are local to one velodex process and disabled by default. When enabled = true, they use fixed windows and bounded in-memory buckets; restarting the process clears the buckets. max_clients caps the number of client/class buckets kept in memory. Set a class requests or window_secs to 0 to disable that class limit.

For authenticated requests, velodex hashes the Authorization header and uses the hash as the bucket key. It does not store the credential value. Other requests use the peer IP address. In in-process tests and deployments without socket peer metadata, velodex falls back to X-Forwarded-For, then X-Real-IP, then 127.0.0.1.

KeyMeaningDefault
enabledInstall the HTTP request limiterfalse
max_clientsMaximum client/class buckets kept in memory8192

Each route class is a sub-table with requests and window_secs:

TableRoute classDefault
[rate_limit.listing]Project listing and detail pages600 / 60s
[rate_limit.metadata]PEP 658/714 .metadata siblings1200 / 60s
[rate_limit.artifact]Artifact downloads and archive inspection300 / 60s
[rate_limit.upload]Upload, yank, restore, and delete requests60 / 60s
[rate_limit.admin]Status, stats, metrics, and discovery endpoints120 / 60s

Example:

[rate_limit]
enabled = true
max_clients = 4096

[rate_limit.listing]
requests = 300
window_secs = 60

[[index]]
name = "pypi"
cached = "https://pypi.org/simple/"
upstream_concurrency = 4

[[index.webhook]]

Put webhook tables under the index that should emit them. A target on a virtual index receives events for requests made through the virtual-index route; the payload also names the hosted layer that stored the change.

[[index]]
name = "root/pypi"
layers = ["hosted", "pypi"]
upload = "hosted"

[[index.webhook]]
name = "ci"
url = "https://ci.example/hooks/velodex"
secret_env = "VELODEX_WEBHOOK_SECRET"
events = ["upload", "delete", "restore"]
KeyMeaningDefault
nameStable target name used in delivery logs
urlHTTP or HTTPS endpoint that receives JSON payloads; credentials, query, and fragment are rejected
secretLiteral HMAC signing secret
secret_envEnvironment variable that contains the HMAC signing secret
eventsEvent names to send; omit or leave empty for all supported event namesall

Use one of secret or secret_env. Supported event names are upload, yank, unyank, delete, restore, promote, project-status, and management. Velodex emits upload, yank, unyank, delete, and restore from the write endpoints in this release; the other names reserve the contract for management surfaces that use this runtime.

Velodex stores pending deliveries in the metadata database and sends them outside the request path. A failed delivery retries up to five attempts with capped backoff of 5, 15, 45, and 135 seconds. The delivery log stores the payload, target name, attempt count, next retry time, response status, and last error. It does not store webhook secrets.

[log]

KeyValuesDefault
levela tracing directive: error ... trace, per-module filtersinfo
formatpretty, jsonpretty
sinkstdout, file, journald, syslogstdout
filepath, required when sink = "file"(none)

The flags --log-level, --log-format, --log-sink, --log-file, -v, and -vv override these, as do the VELODEX_LOG_LEVEL, VELODEX_LOG_FORMAT, VELODEX_LOG_SINK, and VELODEX_LOG_FILE variables (below the flags in precedence).

On this page