-
Notifications
You must be signed in to change notification settings - Fork 0
Configuration
squarepeg reads config from a layered set of YAML files, resolved Nextflow-style: several sources are located, loaded, and deep-merged together before any CLI flag is applied — rather than there being just one config file.
Lowest precedence to highest — each layer can override just the keys it cares about, without having to restate everything from the layers below it:
| Order | Source | Notes |
|---|---|---|
| 1 | Built-in defaults | Not a file — hardcoded fallbacks (mode: pod, timeout: 300, cleanup: true, etc.) |
| 2 | ~/.config/squarepeg/config.yaml |
The single default location. Silently skipped if it doesn't exist. Skip it entirely with --no-default-config. |
| 3 | $SQUAREPEG_CONFIG |
A colon-separated list of paths (same convention as $PATH/$KUBECONFIG), loaded left-to-right. A listed-but-missing file is an error (unlike the default location). |
| 4 | --config PATH |
Repeatable CLI flag, loaded in the order given, left-to-right. |
| 5 | Individual CLI flags |
-e, --cpus, -n, etc. — always win over everything above. |
Every layer (2–4) is deep-merged key by key, not replaced wholesale — a later file overriding just mode: job doesn't wipe out a namespace or tolerations set by an earlier one. See How merging works below for the exact algorithm.
# Site-wide defaults plus a per-user override, composed automatically:
SQUAREPEG_CONFIG=/etc/squarepeg/site.yaml squarepeg run --config ~/my-overrides.yaml alpine true| Key | Type | Description |
|---|---|---|
namespace |
string | Target namespace. |
mode |
pod | job
|
Default execution mode. |
cleanup |
bool |
false behaves as if --keep were always passed. |
timeout |
int | Seconds to wait for the container to start (see Execution Lifecycle). |
quiet |
bool | Suppress squarepeg's own status messages. |
allow_host_path_mounts |
bool | Opt in to -v /host:/container bind mounts (only sensible against a local cluster). |
split_streams |
bool | Reserved for splitting stdout/stderr into separate streams (depends on an apiserver feature gate; not fully wired up yet). |
defaults |
mapping | See defaults below. |
volumes |
mapping | Named volume sources — see Volume forms. |
kubernetes |
mapping | Arbitrary Kubernetes passthrough — see below. |
job |
mapping | Job-level passthrough (ignored in --mode pod) — see below. |
profiles |
mapping | Named config overlays, selected with --profile — see Profiles. |
Unknown top-level keys are a hard error, naming the offending file — this is deliberate typo protection (a silently-ignored namesapce: would be a bad day). The kubernetes and job sections are the exception: they're deliberately free-form and not validated by squarepeg at all — the Kubernetes apiserver validates them, and --dry-run lets you inspect the result before it gets there.
Applied whenever the equivalent CLI flag isn't given:
| Key | Maps to | Units |
|---|---|---|
cpus |
--cpus |
docker-style (e.g. "1", "0.5") |
memory |
--memory |
docker-style (e.g. 2g) |
image_pull_policy |
--pull |
Kubernetes-native (Always/IfNotPresent/Never, already-cased) |
workdir |
-w/--workdir
|
— |
env |
-e/--env
|
mapping of KEY: value; CLI -e entries win over these on a key collision, but config-only keys still get included |
These merge directly into the generated manifest, letting you set any Kubernetes field squarepeg has no dedicated flag for — node selectors, tolerations, service accounts, image pull secrets, security contexts, GPU resources, and so on.
kubernetes.spec (and kubernetes.metadata) is always a Pod spec/metadata, even when --mode job is selected. squarepeg re-homes it under the Job's pod template automatically, so the same config file works unchanged in either mode — without this rule, switching --mode would silently drop every toleration and node selector you'd configured.
kubernetes:
metadata:
labels: {team: bioinf}
spec:
serviceAccountName: squarepeg
nodeSelector: {"kubernetes.io/arch": amd64}
tolerations:
- {key: dedicated, operator: Equal, value: batch, effect: NoSchedule}
imagePullSecrets:
- {name: regcred}
containers:
- name: main # matched by name; merges into squarepeg's own container
securityContext: {allowPrivilegeEscalation: false}
job: # Job-level only; ignored in --mode pod
spec:
backoffLimit: 0
ttlSecondsAfterFinished: 3600CLI flags always win over passthrough on the same field. If you pass -e FOO=bar on the command line, a kubernetes.spec.containers[].env entry for FOO in your config is silently overridden for that field only — every other field you set in passthrough still applies. This is what "layered, not replaced" means in practice; see How merging works.
profiles:
gpu:
defaults: {cpus: "8", memory: 32g}
kubernetes:
spec:
nodeSelector: {"nvidia.com/gpu.present": "true"}
containers:
- name: main
resources: {limits: {"nvidia.com/gpu": 1}}squarepeg run --profile gpu my-training-image train.pyA profile is itself a config document (using the same schema, minus a nested profiles key — that's rejected) that gets deep-merged over the rest of the resolved config when selected with --profile NAME.
Two things worth knowing:
- Same-named profiles across multiple config layers deep-merge, last-loaded wins per key — not an error. This lets a site-wide file own a profile's scheduling details (node selector, toleration) while a per-user file adds just a resource size to the same profile name, without either file needing to know about the other's contents.
-
Profile bodies are validated and interpolated eagerly, regardless of whether you select them. A typo or an unset
${VAR}reference insideprofiles.gpuis a hard error on every invocation of squarepeg against that config file, not just ones using--profile gpu. This makes "is my config file valid?" a property of the file, not of the flags you happen to pass.
Every layering step — config file over config file, profile over base config, and Kubernetes passthrough over the generated manifest — uses the same deep-merge routine:
- Dicts merge key by key, recursively.
-
Lists where every element has a
namekey merge by name (this is what makescontainers,env,volumes,volumeMountsbehave intuitively — a later layer adding one env var doesn't wipe out the others). -
Any other list is replaced wholesale by the later layer — this is deliberate:
tolerationsis a list without a stable key to merge by, and replacement is the only way a later layer can remove an inherited toleration (an append-only merge would give no way to say "actually, none of these"). -
CLI-set fields are "claimed" and cannot be overridden by config passthrough, even if the passthrough is loaded after the CLI flags are parsed — e.g.
-e FOO=baralways beats akubernetes...enventry forFOO, but a passthroughBARenv var still lands untouched.
See examples/config.yaml in the repo for a complete worked example covering every section above, including ${VAR} references (see Environment Variable Interpolation).