Development Preview build — APIs and content may change. Visit ocx.sh for the current release.
Skip to content

Configuration

API reference for OCX configuration files. For the rationale behind the tier model, the merge philosophy, and worked examples, see the Configuration in-depth page.

Config files are in TOML format and are optional. OCX works without any config file using compiled-in defaults.

File Locations

TierPathPurpose
System/etc/ocx/config.tomlMachine-wide defaults
User (Linux)$XDG_CONFIG_HOME/ocx/config.toml or ~/.config/ocx/config.tomlPer-user defaults
User (macOS)~/Library/Application Support/ocx/config.tomlPer-user defaults; XDG_CONFIG_HOME not consulted
OCX home$OCX_HOME/config.toml (default: ~/.ocx/config.toml)Co-located with data; survives a zip-and-move of $OCX_HOME

Missing files are silently skipped.

Explicit additions

Two mechanisms add a file on top of the discovery chain — they do not replace it. Missing files are an error in this case (explicit paths must exist).

  • --config FILE — CLI flag, before subcommand
  • OCX_CONFIG=/path/to/file.toml — environment variable

When both are set, --config layers on top of OCX_CONFIG. Setting OCX_CONFIG to the empty string disables an ambient value without unsetting it.

Discovery and Merge Precedence

Settings are resolved lowest-to-highest. Higher-precedence sources override lower ones.

PrioritySourceNotes
1 (lowest)Compiled defaultsBuilt into the OCX binary
2System config — /etc/ocx/config.tomlDiscovered tier
3User config — $XDG_CONFIG_HOME/ocx/config.toml (Linux) or ~/Library/Application Support/ocx/config.toml (macOS)Discovered tier
4OCX home config — $OCX_HOME/config.tomlDiscovered tier
5OCX_CONFIGLayered on top of discovered tiers
6--config FILELayered on top of OCX_CONFIG
7Environment variables (OCX_*)Always win over any config file
8 (highest)CLI flagsPer-invocation; always win

Merge rules

  • Scalars: the nearest (highest-precedence) value wins.
  • Tables (e.g. [registries.<name>]): merged key-by-key across tiers; inner keys use nearest-wins.
  • Layering: every file is loaded and merged in order. Explicit paths do not replace the discovered tiers.

Kill switch

OCX_NO_CONFIG=1 skips the discovered chain only (tiers 2–4). Explicit paths (--config, OCX_CONFIG) still load.

GoalInvocation
Default(no flags)
Layer override on ambient--config extra.toml
Hermetic with a specific fileOCX_NO_CONFIG=1 --config ci.toml
Hermetic, no filesOCX_NO_CONFIG=1

Configuration Keys

[registry]

Global settings for the registry subsystem.

default

Type: string
Default: "ocx.sh"
Overridden by: OCX_DEFAULT_REGISTRY environment variable

The default registry used for bare package identifiers — those without an explicit registry prefix. When you write cmake:3.28, OCX expands it to <default>/cmake:3.28.

The value may be either a literal hostname ("ghcr.io") or the name of a [registries.<name>] entry. When it matches a named entry, OCX resolves it to that entry's url.

toml
[registry]
default = "ghcr.io"

[registries.<name>]

Per-registry settings, keyed by a friendly name. Each entry configures one registry; [registry] default can then reference it by name rather than by hostname.

The plural form (registries, not registry) is deliberate: it mirrors Cargo's convention and avoids a TOML collision with the singular [registry] global-settings section.

url

Type: string

The actual registry hostname this entry resolves to. When [registry] default names this entry, OCX uses url as the effective default registry hostname.

toml
[registry]
default = "company"

[registries.company]
url = "registry.company.example"

[registries.ghcr]
url = "ghcr.io"

v1 scope

Only url is defined in v1. The [registries.<name>] table is reserved for per-registry settings — future fields (insecure, location rewrite, timeout, auth) will slot into the same entry without breaking existing configs. Unknown fields inside an entry are rejected (typo protection); unknown top-level sections are silently ignored (forward compatibility).

[mirrors."<host>"]

A mirror replaces the network endpoint for one upstream registry host. OCX appends the upstream repository path verbatim after the mirror's path prefix and contacts only the mirror — the upstream origin is never contacted on the read path.

This is a source-replacement model: if a mirror is configured for a host, all read traffic for that host goes to the mirror. There is no origin fallback. A mirror that is unreachable is a hard error — in firewall-controlled networks, fallback to the open internet would silently defeat the point.

toml
[mirrors."ghcr.io"]
url = "https://company.jfrog.io/ghcr-remote"

[mirrors."docker.io"]
url = "https://company.jfrog.io/dockerhub-remote"

url

Type: string
Required at startup: a missing or empty url is a hard error when OCX resolves the mirror map — same enforcement point as the [registries] v1 scope.
Overridden by: OCX_MIRRORS — per-host key wins when both are set

The mirror endpoint: scheme://host[/repo-key-prefix]. OCX builds the full pull path as <mirror-host>/<prefix>/<upstream-repo>.

toml
# Artifactory path-based routing (repository-path method):
# ghcr.io/owner/tool:1.2  →  company.jfrog.io/ghcr-remote/owner/tool:1.2
[mirrors."ghcr.io"]
url = "https://company.jfrog.io/ghcr-remote"

# Subdomain / host-only form (empty prefix):
# ghcr.io/owner/tool:1.2  →  ghcr-remote.company.jfrog.io/owner/tool:1.2
[mirrors."ghcr.io"]
url = "https://ghcr-remote.company.jfrog.io"

Artifactory note. The url is the Docker/OCI pull path: <host>/<repo-key>. This is not the Artifactory admin REST path (/artifactory/api/docker/<repo-key>) — that path is for administrative operations and is not a valid Docker pull URL. The pull path is what you would use with docker pull or oras pull.

Nexus 3.83+ path-based routing uses the same <host>/<repo-key> shape as Artifactory — the repo-key alone, without any prefix:

toml
# Nexus Repository 3.83+ path-based routing (repo-key only, no /repository/ prefix):
# ghcr.io/owner/tool:1.2  →  nexus.corp/docker-proxy/owner/tool:1.2
[mirrors."ghcr.io"]
url = "https://nexus.corp/docker-proxy"

Nexus legacy form

The legacy /repository/<name> URL form (e.g. https://nexus.corp/repository/docker-proxy) is not used with Nexus 3.83+ path routing. Use the repo-key alone as the path prefix, matching the Artifactory convention above.

Older Nexus deployments expose each repository on a per-repository port. Those use the host-only mirror form (https://nexus.corp:8082 — no path prefix).

Harbor follows the same <host>/<project-name>/<image> shape for its project-level proxy caches.

Docker Hub library/ images. OCX appends the repository path verbatim and does not expand Docker Hub short names. For Docker Hub official images, use the fully-qualified form (docker.io/library/alpine) so the mirror URL resolves to <mirror>/<prefix>/library/alpine.

Scheme default. When url has no scheme:// prefix (e.g., "nexus.corp/docker-proxy"), OCX defaults to https. Explicit https:// is recommended for clarity.

Plain-HTTP mirrors. A url starting with http:// requires the mirror host to be listed in OCX_INSECURE_REGISTRIES. If the mirror host is absent, OCX exits at startup with an actionable error naming the variable and the mirror host — it does not silently downgrade TLS. The check runs before any network activity.

Typo protection

[mirrors."<host>"] uses deny_unknown_fields — a typo such as urll = "..." is a TOML parse error, not a silent no-op. This matches the [registries.<name>] behavior.

Merge behavior

[mirrors."<host>"] entries are merged key-by-key across config tiers, following the same nearest-wins rule as [registries.<name>]. A higher-precedence tier that sets [mirrors."ghcr.io"] replaces the lower-tier entry for that host; hosts not mentioned in the higher tier are untouched.

OCX_MIRRORS overrides on a per-host basis: a host key present in OCX_MIRRORS replaces the config entry for that host; hosts absent from OCX_MIRRORS still come from [mirrors].

Auth

Credentials are resolved against the mirror host, not the upstream. Configure them with OCX_AUTH_<mirror_slug>_* or via docker login against the mirror host. The upstream's credentials are never consulted on the read path.

Interactions

ConcernBehavior
[registry] default / OCX_DEFAULT_REGISTRYDefault injection runs before mirror rewrite. A bare identifier expanded to the default registry is then mirrored if that registry has a [mirrors] entry.
--offlineNo network activity at all; mirrors are not consulted.
--remoteMutable lookups (tag list, tag→digest resolution) hit the mirror, not the origin.
ocx.lockStores canonical upstream coordinates and per-platform leaf digests — not the mirror host. A lock made behind a mirror is valid on a machine with direct egress, and vice versa.
pushPush is not mirror-redirected. The canonical upstream host is contacted. Remote/proxy repositories are read-only; redirecting push would fail confusingly.
ocx index catalogAgainst a proxy-type mirror, the catalog lists only repositories the proxy has cached. This is a registry-side constraint, not an OCX behavior.

Environment Variable Override Table

This table shows which OCX environment variables map to config file fields. Variables not listed here have no config equivalent.

Environment VariableConfig EquivalentNotes
OCX_DEFAULT_REGISTRY[registry] defaultEnv var wins when both are set
OCX_MIRRORS[mirrors."<host>"] urlEnv var wins per-host key when both are set; hosts absent from env var still come from config
OCX_HOMENoneDetermines where config is loaded from; cannot be in a config file
OCX_CONFIGNoneMeta-variable pointing at the config file itself
OCX_NO_CONFIGNoneKill switch; cannot be represented in a config file by definition
OCX_OFFLINENonePer-invocation mode, not a persistent setting
OCX_REMOTENonePer-invocation debugging mode, not a persistent setting
OCX_BINARY_PINNoneSubprocess-only: set automatically by ocx on every spawn so child ocx invocations pin to the same binary
OCX_INSECURE_REGISTRIESNone (deferred)Will move to a per-entry insecure field under [registries.<name>] once the flag is implemented; the env var remains the source of truth today
OCX_NO_UPDATE_CHECKNoneCI-only concern; env var is sufficient
OCX_NO_MODIFY_PATHNoneInstall-time concern; env var is sufficient

OCX_OFFLINE and OCX_REMOTE are intentionally absent from the config file. Both are per-invocation modes — a persistent offline = true would silently break ocx install on a fresh setup.

Error Reference

Literal sizes in the examples below reflect the current 64 KiB safety cap (MAX_CONFIG_SIZE in the loader source). Angle-bracket placeholders such as <SIZE> stand in for runtime values that depend on the offending file.

ErrorCauseResolution
error: config file not found: /path/to/file.toml (check --config or OCX_CONFIG)--config or OCX_CONFIG points to a non-existent fileCheck the path; unlike the three discovery tiers, explicit paths must exist. To disable an ambient OCX_CONFIG without unsetting it, set it to the empty string.
error: config file /path/to/file.toml exceeds maximum allowed size (<SIZE> bytes > 65536 bytes); OCX config files are typically under 1 KiB — did you point at the wrong fileA config file is larger than the 64 KiB safety capThe hint usually explains it — a --config flag or OCX_CONFIG env var pointed at a non-config file (e.g. an archive or binary).
error: invalid TOML at /path/to/file.toml: ...TOML syntax error in the config fileFix the TOML syntax error at the indicated location
error: failed to read config file /path/to/file.toml: ...The file exists but cannot be read — permission denied, the path is a directory, or another I/O failureCheck file permissions; --config and OCX_CONFIG must point to a regular, readable file.

JSON Schemas

OCX publishes JSON Schemas for every config and project file at stable URLs. IDEs and language servers (taplo, yaml-language-server, VS Code, Zed) consume them for autocompletion, hover docs, and validation.

FileSchema URL
config.toml (any tier)https://ocx.sh/schemas/config/v1.json
ocx.toml (project)https://ocx.sh/schemas/project/v1.json
ocx.lock (project lock — machine-generated)https://ocx.sh/schemas/project-lock/v2.json
metadata.json (package)https://ocx.sh/schemas/metadata/v1.json

ocx init writes a #:schema https://ocx.sh/schemas/project/v1.json directive on the first line of every generated ocx.toml, so taplo-aware editors pick the schema up automatically with no extra wiring. To opt other files in by hand, prepend the same directive at the top of the file. The project-lock schema carries a top-level $comment flagging it as machine-generated — never hand-edit ocx.lock; rerun ocx lock instead.

Future Config Keys

Not yet implemented in v1

These sections are documented here so the format design is stable before they land. They do not exist in the current release.

Per-registry fields beyond url

The [registries.<name>] table is live in v1, but only url is defined. Future per-registry fields will slot in without breaking existing configs:

toml
# Future shape (not in v1 — only `url` is implemented today):
[registries.private]
url = "registry.company.example"
insecure = false                 # per-registry TLS opt-out
location = "mirror.company.example"  # URL rewrite / mirror

[patches] section

Infrastructure patch entries will live under [patches]. This section is reserved for the patches feature and is ignored by the v1 loader.

[clean] section

Retention policy configuration will live under [clean]. Deferred to the retention policy feature.

Project-level ocx.toml

A project-level ocx.toml is now shipped — see the Project Toolchain section in the user guide for the schema, locking model, and activation hooks. The file name is deliberately different from config.toml so the data-directory tier and project tier are never confused: ocx.toml is loaded by a distinct API and never participates in the ambient config chain described above.