Draft, not final
v1beta1 does not exist yet. These pages describe the changes proposed for it in docuconf-go pull requests #27, #28, #29 and #30, which are not merged. Anything here can still change. Keep contracts on v1alpha1 until the format freeze lands. See the versioning policy and the draft SPEC.md.
Inputs
An application reads its configuration from two places: environment variables and files. A contract describes both. For each input it records the type, the constraints, whether it is required or secret, and — because libraries disagree — exactly how the app parses it.
The platform supplies each input from a source: a literal, a Kubernetes reference, an injector, a mounted file or a config-file overlay. Whatever the source, the value the app finally sees is checked twice: by the platform before deploy where it can be, and by the SDK at boot always.
Environment variables
vars is a map keyed by the variable name, which must match ^[A-Z][A-Z0-9_]*$. SDKs map idiomatic field names (databaseUrl, DatabaseUrl) to this form.
Variable types
Every SDK must parse each type identically, so a type exists only once the spec defines it. In v1beta1 the set can grow but never shrink: a new type is an addition, and a tool that does not know one reports it instead of guessing. v1beta1 adds keySet.
| Type | Meaning | Constraint fields | Platform value | Wire form |
|---|---|---|---|---|
string | Free text. | minLength, maxLength (in characters), pattern (RE2, matches anywhere unless anchored) | string | as is |
int | 64-bit signed integer, base 10 only: 007 is 7, and 0x10, 1_000 or 1e3 are invalid_type. SDKs export narrower host ranges as min/max. | min, max | int | base-10, no leading + or zeros: 8080 |
float | Finite decimal number with a digit on each side of the point, parsed independently of locale. inf, NaN, .5 and 0,5 are invalid_type. | min, max | number | shortest round-trip decimal: 0.5 |
bool | true or false, in any case (TRUE, False). Nothing else: not 1, yes or on. | — | bool | true / false |
duration | A length of time, written in Go syntax on the platform side. | min, max (durations), encoding | Go-syntax duration: 1m30s | per encoding: go, iso8601, seconds or timespan |
url | An absolute URL with a scheme. | schemes, maxLength (the URL as it is) | string with scheme:// | as is |
enum | One of a fixed set of strings. | values (non-empty) | one of values | as is |
list | A list of strings or integers. | items (string | int), encoding, separator, minItems, maxItems, itemMin and itemMax (int items), itemMinLength and itemMaxLength (string items, after splitting) | list | per encoding: csv, json or indexed |
keySet | Secret keys that are all valid at once, so one can be rotated without an outage: webhook signatures, inbound API keys. Always secret. | encoding, separator (csv), minKeys (default 1), maxKeys (default 2), keyMinLength, keyMaxLength (in characters) | a secret reference only | as a list of strings: old,new in csv |
json | A structured value, checked against a JSON Schema generated from the app’s own type. | schema (JSON Schema), maxLength (the wire string) | any JSON value | compact JSON |
patternis RE2 and matches anywhere in the value; anchor it with^…$to match the whole value. RE2's\d,\w,\sand\bare ASCII-only.jsonvariables andconfigfiles carry a JSON Schema generated from the app's own type, so the platform checks structured values against the type the app deserializes into.- A
keySetis a set of secret keys that are all valid at once, for the side that verifies: webhook signatures, inbound API keys, JWT HMAC verification. It is alwayssecret, so it has no default and no examples. It travels in a list's wire encodings, and its keys are never trimmed. The number of keys outsideminKeys..maxKeysistoo_few_itemsortoo_many_items; a key outsidekeyMinLength..keyMaxLength, and an empty key whatever the bounds (a stray separator), isout_of_range. See Secret rotation. - An empty string is a value for
string. For every other type it means unset.
Length limits
- Lengths count characters, meaning Unicode code points, never bytes or UTF-16 code units:
日本is 2 characters, andZÜ01fits anitemMaxLengthof 4. CUE'sstrings.MaxRunesand JSON Schema'smaxLengthcount the same way, so the platform and every SDK agree. minLengthandmaxLengthbound astring, andmaxLengthalso aurl(the URL as it is) or ajsonvalue.maxLengthon ajsonvalue bounds its wire string: before deploy, the compact JSON the renderer writes; at boot, the raw value the app receives, whitespace included, before it is parsed. The Helm values schema cannot express it, sodocuconf vetand the SDK check it.itemMinLengthanditemMaxLengthbound each item of astringlist, after the list is split, so acsvseparator never counts. They are not allowed on anintlist, asitemMinanditemMaxare not allowed on astringlist.keyMinLengthandkeyMaxLengthbound each key of akeySetthe same way.- A value outside its limits is
out_of_rangeat boot. A too-long secret is reported by its length, never its value. - An app that stores values in fixed-width byte fields, such as a COBOL
PIC X(n), should declare a limit that leaves room for multi-byte characters, or restrict values to ASCII with apatternsuch as^[ -~]*$.
Fields on every variable
| Field | Rule |
|---|---|
type | One of the variable types. Beta may add a type; a tool that does not know one reports it instead of guessing. |
description | Required. Plain text, at least 5 characters: what the input is, in one phrase. Rendered into generated docs. |
details | Optional. CommonMark: why the input exists and when to change it. Not blank, at most 4000 characters. Used only in generated docs, never at runtime. |
required | Default false. A required variable cannot have a default, and cannot be deprecated. Whether an input is optional is the app’s choice. |
default | Must satisfy the variable’s own constraints (checked at declaration time). |
secret | The value must come from a secretKeyRef. No default, no examples, never printed. |
group | Free-form grouping for docs. |
examples | Example values, as strings, for docs. |
deprecated | {message, replacedBy?}, for staged removal: the platform should stop setting the input. message is not blank and at most 500 characters. A deprecated input that is still set is a warning, never an error, and SDKs warn at boot. |
configKey | The app’s own config key (Orders:Timeout, orders.timeout), for docs. |
Deprecating an input
An input that is going away is marked deprecated first, so the platform can stop setting it before it disappears. This works for variables and file inputs alike:
OLD_PORT: {
type: "int"
description: "Port the service listened on before 2.0"
deprecated: {message: "Use PORT instead", replacedBy: "PORT"}
}messagesays what to use instead, or why the input is going away: not blank, and at most 500 characters.replacedBynames the input that replaces it.- A
requiredinput cannot be deprecated, since the platform could not stop setting it. - The platform sees a warning, never an error.
#Validatelists every deprecated input the platform still sets, as a value, an overlay value or a file source, indeprecatedSet.docuconf vetprints one line per warning with its message, and exits 0 when there are only warnings. - The unknown-name check stays strict. Values that set an input the contract does not declare are always rejected; deprecation is how an input is removed in stages.
docuconf diffreports a deprecation as notable, and a removal as breaking for the platform, saying when the input was already deprecated. - SDKs should log a warning at boot for each deprecated input that is set, naming the input and its message, never its value. The input still loads and is still checked.
- The generated docs show the notice and link to the replacement, and tell agents not to add uses of deprecated inputs.
This is your own inputs' deprecation. How docuconf deprecates its own fields and APIs is on Versioning and deprecation.
Value sources
A value is usually a literal. Some only exist in the cluster or are supplied at runtime, so the platform may give a reference instead:
| Source | Example | Allowed for | Checked | Status |
|---|---|---|---|---|
| literal | PORT: 9090 | every non-secret type | before deploy | Implemented |
| configMapKeyRef | {configMapKeyRef: {name: "limits", key: "rate"}} | every non-secret type except list | at boot | Implemented |
| fieldRef (Downward API) | {fieldRef: {fieldPath: "metadata.namespace"}} | string | always valid | Implemented |
| resourceFieldRef | {resourceFieldRef: {resource: "limits.memory"}} | int | always valid | Implemented |
| secretKeyRef | {secretKeyRef: {name: "db", key: "url"}} | secret variables, including every keySet (one of the two secret sources) | at boot | Implemented |
| injected, with a reference | {injected: {provider: "bank-vaults", ref: "vault:secret/data/db#url"}} | every type, including secrets, but not a list or key set in the indexed encoding; rendered verbatim as the env value for the injector to resolve | reference shape before deploy; the value at boot | Implemented |
| injected, without a reference | {injected: {provider: "otel-operator"}} | every type, including secrets; not rendered: a webhook, init process or operator sets it | at boot | Implemented |
A secret variable is never a literal. It comes from a secretKeyRef or from an injector, and its content is checked only at boot. No docuconf tool ever prints a secret value.
Cloud-native injection
Many platforms do not put configuration into the pod spec at all. A mutating webhook, an init process or a wrapper resolves secrets at startup and hands the app a ready environment, or writes files the app reads. docuconf supports this by design:
- The SDK validates the process as it actually starts. Configuration is read from the real process environment and filesystem at startup, after any injector has run. A value from Bank-Vaults or
op runis checked exactly like one from the pod spec. - The contract does not care who supplies a value. It describes what the app accepts. The platform's values document says who supplies it.
- The platform declares injected inputs as
injected, naming the provider and, when the injector reads a reference from the env value, that reference.docuconf vetchecks the reference's shape, not the secret, and renders it verbatim for the injector to resolve.
# Platform values for orders-api
DATABASE_URL:
injected: {provider: bank-vaults, ref: "vault:secret/data/orders/db#url"}
OTEL_EXPORTER_OTLP_ENDPOINT:
injected: {provider: otel-operator} # set by the operator; not rendered
LOG_LEVEL: warn # an ordinary literal| Injector | What it does | How the contract describes it |
|---|---|---|
| Bank-Vaults (vault-env webhook) | Env values like vault:secret/data/db#url are resolved by vault-env, which then execs the app with real values. | injected value source with ref; the SDK sees and validates the resolved value. |
| Vault Agent injector | Writes secrets as files under /vault/secrets, rendered from templates. | injected file source, with the pod annotations that make the agent write the file at its declared path; the SDK checks the file at boot like any other. |
| External Secrets Operator | Syncs a cloud secret manager into a Kubernetes Secret. | secretKeyRef or secret file source, as usual; nothing special. |
| Secrets Store CSI driver | Mounts secrets from Vault, AWS, Azure or GCP as files. | csi file source. |
| 1Password, Doppler, Infisical and similar wrappers | A wrapper process (op run, doppler run) resolves references and execs the app. | injected value source, with the wrapper’s reference format as ref. |
| Operators and webhooks (OpenTelemetry, service meshes) | Add variables such as OTEL_EXPORTER_OTLP_ENDPOINT to the pod. | injected value source without ref, so the platform knows who supplies it, with the pod annotation or label that switches the injector on. |
SDKs validate injected values like any other, because they read the process environment at startup, after injection. If a secret still holds a reference (vault:, op://, ref+), the injector did not run, and the SDK fails with invalid_type, naming the variable and the reference scheme but never the value.
Enabling the injector
Most injectors are switched on by annotations or labels on the pod. An injected source, for a variable or a file, may carry them as podAnnotations and podLabels, maps of strings. The values document may also have its own podAnnotations and podLabels, beside the variables, for settings every injected input shares, such as the Vault role. The contract does not change: which injector runs is a fact about the cluster.
- Placeholders let an annotation that names one input be written once, beside it:
{input}is the variable's or file's name, and for a file{path}is its path,{dir}the directory it occupies and{file}its file name. Nothing else is expanded, so a Vault Agent template's{{ .Data }}stays as it is. A placeholder the source does not define ({path}on a variable, any placeholder in the shared maps) is anundefinedPlaceholdererror. - Rendering.
docuconf rendermerges the shared maps and every injected input into two maps,podAnnotationsandpodLabels, for the pod template's metadata: webhooks see pods, not Deployments. In Helm, the library chart'sdocuconf.podAnnotationsanddocuconf.podLabelshelpers return the same maps. Keys must be Kubernetes qualified names and label values valid label values, checked after expansion. - Conflicts. The same key with the same value from several inputs is fine. The same key with different values is a
conflictingPodAnnotationorconflictingPodLabelerror naming the key and both sources, indocuconf vet, in the render and in the Helm helpers. - Not secret. Anyone who can read the pod can read its annotations, so they hold references and settings (a Vault path, a role, a template), never secret material.
The Vault Agent injector writing a secret config file db-creds, declared at /vault/secrets/db.json:
# values.yaml
podAnnotations: # shared: one agent per pod, one role
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: ledger
# files.yaml
db-creds:
injected:
provider: vault-agent
podAnnotations:
vault.hashicorp.com/agent-inject-secret-{input}: database/creds/ledger
vault.hashicorp.com/agent-inject-template-{input}: '{{- with secret "database/creds/ledger" -}}{{ .Data | toJSON }}{{- end }}'
vault.hashicorp.com/secret-volume-path-{input}: "{dir}"
vault.hashicorp.com/agent-inject-file-{input}: "{file}"renders the two shared annotations plus agent-inject-secret-db-creds, agent-inject-template-db-creds, secret-volume-path-db-creds: /vault/secrets and agent-inject-file-db-creds: db.json, so the agent writes the file exactly at the declared path. Values are strings: write "true", not true. See SPEC.md §4.5.2 for Bank-Vaults, the OpenTelemetry operator and the edge cases.
Files
files is a map keyed by input name, a DNS label because it names a volume.
File types
| Type | Content | Constraint fields | Secret |
|---|---|---|---|
config | A structured config file in format json, yaml or toml. | format, schema (JSON Schema generated from the type the app binds the file to) | optional |
tls | A key pair in the kubernetes.io/tls layout: tls.crt, tls.key, and ca.crt when requireCA is set. | dnsNames, keyAlgorithms (RSA, ECDSA, Ed25519), minRemaining, requireCA | always |
caBundle | One or more PEM CA certificates. | minCertificates (default 1) | optional |
keystore | A PKCS#12 or JKS keystore. | format, passwordVar (a declared secret variable) | always |
text | A text file, such as a licence key. | pattern (RE2), minLength, maxLength | optional |
binary | Opaque bytes, such as a GeoIP database. | maxSize only | optional |
Fields on every file input
| Field | Rule |
|---|---|
type | One of the file types. |
description, details, required, group, deprecated | As for variables. |
secret | Content must come from a secret store. Forced true for tls and keystore. |
path | Where the app reads it: a directory for tls, a file otherwise. Absolute. |
pathEnv | A variable the platform sets to path (SSL_CERT_FILE). Not also declared in vars. |
reload | restart (default): a changed source rolls the pods. watch: the app reloads it. |
maxSize | Upper bound in bytes. |
Mount rules, enforced by the meta-schema: a file is mounted at its parent directory, and a TLS pair at its own directory; no two inputs share a directory; none is mounted over a reserved directory such as /, /etc, /etc/ssl/certs, /usr, /var or /app; files are projected with items, never subPath, so rotation reaches them; secret files are mode 0400, others 0444.
File sources
| Source | For | Checked before deploy | Status |
|---|---|---|---|
| inline | non-secret files | Everything: format, schema, pattern, size, certificate count. Rendered as an immutable, content-hashed ConfigMap. Config files may be given as structured data. | Implemented |
| configMap | non-secret files | That a key is given for single files. | Implemented |
| secret | any | Key presence; with resolved metadata, the Secret’s type (kubernetes.io/tls) and its keys. | Implemented |
| certificate (cert-manager) | tls | From the Certificate spec: covers dnsNames, uses an allowed key algorithm, renewBefore ≥ minRemaining. | Implemented |
| csi (Secrets Store CSI driver) | any | Nothing; checked at boot. | Implemented |
| image (image volume) | non-secret files | Nothing; for data above the 1 MiB ConfigMap limit. | Implemented |
| injected (Vault Agent and similar) | any | That the injector is named, and its pod annotations and labels; no volume is rendered, the injector writes the file at path. Checked at boot. | Implemented |
Resolved fields — a Secret's type and keys, a Certificate's spec — are metadata the platform tooling reads from the cluster. They are never secret contents. When absent, those checks move to boot.
Rotation. reload: watch means the app rereads the file, so the platform does nothing. reload: restart makes the platform roll the pods when the source changes. Inline content is content-hashed, so it always rolls the pods. For secrets in environment variables and keys with an overlap, see Secret rotation.
Secret rotation
docuconf delivers secrets and checks them. It does not store, issue or rotate them: Vault, External Secrets, cert-manager and the Secrets Store CSI driver do that. What the contract decides is how a rotated value reaches the running app, and that depends on how the secret is delivered.
| Delivered as | How a rotated value reaches the app |
|---|---|
Env var from a Secret (secretKeyRef) | Environment variables are read once, when the process starts. A rotated value reaches the app on the next restart or redeploy, which the platform owns. |
| File secret (a TLS key pair, a Vault Agent file, a CSI volume) | reload: watch: the app rereads the file and nothing restarts. reload: restart: the platform rolls the pods when the source changes. Files are projected with items, never subPath, so updates reach the mounted file. |
| Dynamic secret with a lease (Vault database credentials) | Deliver it as a file written by Vault Agent or the CSI driver, with reload: watch, so each renewal reaches the app. Or the app fetches it itself, and the contract declares the Vault address and role instead of the credential. |
Injected env value (vault-env, op run) | Resolved when the process starts, so a rotated value needs a rollout. |
| API key with an overlap, two keys valid at once | Declare both keys; see below. |
Two valid keys during a rotation
An API key or signing key can be rotated without downtime if, for a while, both the old and the new key work. Who needs the overlap depends on the app's side of the call.
The app verifies keys (it accepts webhooks or API calls). Declare a keySet:
WEBHOOK_KEYS: {
type: "keySet"
secret: true
description: "Keys that verify the signature on incoming payment webhooks"
minKeys: 1 // the default
maxKeys: 2 // the default
keyMinLength: 32
keyMaxLength: 256
}The platform supplies it like any secret, as one secretKeyRef, and the Secret holds the keys joined by commas, old,new during the overlap. Because the value is a reference, the checks run at boot: an empty key (a trailing comma), a truncated one or a third key fails with out_of_range or too_many_items instead of locking callers out. SDKs expose the keys in order, and should offer a constant-time contains and a helper that tries every key with a check you supply, such as an HMAC comparison. To rotate:
- Add the new key (
old,new) and roll out. The app now accepts both. - Switch the sender (the caller, or the signer) to the new key.
- Remove the old key (
new) and roll out.
The generated docs print these steps for every key set, so an app's details need not repeat them.
A host that cannot offer a key set may declare two variables instead, a required API_KEY and an optional API_KEY_PREVIOUS, with the same length limits, and accept either:
API_KEY: {
type: "string"
description: "The key callers send: the current key"
required: true
secret: true
minLength: 32
maxLength: 256
}
API_KEY_PREVIOUS: {
type: "string"
description: "The previous key, still accepted while callers switch to API_KEY"
secret: true
minLength: 32
maxLength: 256
}The platform sets API_KEY_PREVIOUS (a secretKeyRef) only during a rotation: move the old key there and the new key to API_KEY, roll out, switch the callers, then drop API_KEY_PREVIOUS and roll out again.
v1alpha1 recommended a secret list of strings with minItems: 1, maxItems: 2 and item length limits. That still works, with the same wire format, but a keySet says what the list is for, rejects an empty key without a length limit, and gets its rotation steps in the docs. Changing a variable from such a list to a keySet changes its type, which docuconf diff reports as breaking for the contract; the Secret and the values need no change.
What the platform cannot check. A rotation is only safe if every rollout keeps one key in common with the one before it. The platform supplies a reference and never sees the keys, so neither #Validate nor docuconf vet can check this: following the steps in order is the operator's job.
The app sends a key (it calls another service). Declare one secret key. Rotate it by updating the Secret once the receiving service accepts the new key; the app picks it up on its next restart or redeploy.
Either way, docuconf's part is the contract, the check before deploy (the value must be a reference, never a literal) and the check at boot (count and length). Issuing the new key, storing it and deciding when to rotate stay with the secret manager.
Config-file overlays
Hosts such as .NET, Spring Boot and Rails layer configuration files under environment variables. A common cloud-native pattern keeps the app's baked-in files and adds one more file, mounted by the platform, between them and the environment:
var builder = WebApplication.CreateBuilder(args);
// 1. Baked into the image: defaults and per-environment values.
builder.Configuration.AddJsonFile("appsettings.json", optional: false);
// 2. Mounted by the platform from a ConfigMap, reloaded on change.
builder.Configuration.AddJsonFile("/app/config/appsettings.Production.json", optional: true, reloadOnChange: true);
// 3. Environment variables (including injected secrets) win.
builder.Configuration.AddEnvironmentVariables();A contract describes the overlay, and the platform renders it:
overlays: platform: {
format: "json"
path: "/app/config/appsettings.Production.json"
reload: "watch" // reloadOnChange: true
}- Precedence is fixed and the same on every host: baked-in base file, then profile file, then the platform overlay, then environment variables.
- The platform routes variables to the overlay in its values document.
docuconf renderwrites each value at the variable'sconfigKey(Orders:CheckoutTimeoutbecomes{"Orders": {"CheckoutTimeout": …}}) in native types — numbers, booleans, arrays — rather than env-var strings, and ships the file as a ConfigMap mounted atpath. - Validation is identical: overlay values are typed values checked by
#Validate, the same as env values. Secrets never go in an overlay, because overlays are ConfigMaps. - Overlays add, never replace. The overlay sits at its own path, so the app's baked-in
appsettings.Production.jsonand its values still apply. Mounting over the baked-in file would silently discard them, and the meta-schema rejects an overlay path that is also a baked-in config file. reload: watchmaps to the host's reload-on-change, so the app sees new values without a restart (in .NET, throughIOptionsMonitor<T>).restartrolls the pods instead.- The SDK reads the overlay exactly as
#Renderwrites it. It loads the file as optional, from underDOCUCONF_FILE_ROOTlike a file input: a missing overlay is not an error, and one that does not parse, or does not hold an object at its top level, isfile_malformed. Each variable's value is read at itsconfigKey, case-sensitively, converted to the wire string it stands for (50.0is50, anullis unset), and then parsed like an env value.
| Host | Baked-in files | Platform overlay | Reload |
|---|---|---|---|
| .NET (Microsoft.Extensions.Configuration) | appsettings.json, appsettings.{Environment}.json | AddJsonFile("/app/config/appsettings.Production.json", optional: true, reloadOnChange: true) | IOptionsMonitor<T> sees changes; IOptions<T> needs a restart |
| Spring Boot | application.yml, application-{profile}.yml | spring.config.additional-location=file:/app/config/ | restart, or Spring Cloud refresh |
| Rails (anyway_config) | config/<name>.yml | an extra YAML file read before env | restart |
| Rust (figment) | Config.toml with profiles | Toml::file("/app/config/Config.toml") merged before Env | restart |
| Kotlin (Hoplite) | application.conf / .yaml | an extra property source ahead of the defaults | restart, or Hoplite reloadable config |
docuconf render and the Helm library chart write the overlay's ConfigMap, volume and mount. The SDKs for hosts that layer files load it in the right place; see the SDK table.
Wire encodings
Environment values are strings. The platform always holds typed values — lists as lists, durations in Go syntax — and the renderer writes each one in the form the app's library parses. For most types every library agrees. Lists and durations differ, so the contract records the app's encoding:
| List encoding | Wire form | Native to |
|---|---|---|
csv (default) | a,b joined by separator | caarlos0/env, Spring Boot, anyway_config |
json | ["a","b"] | pydantic-settings |
indexed | NAME__0=a, NAME__1=b | Microsoft.Extensions.Configuration |
| Duration encoding | Wire form for 90s | Native to |
|---|---|---|
go (default) | 1m30s | Go time.ParseDuration |
iso8601 | PT1M30S | pydantic timedelta, ActiveSupport::Duration, java.time.Duration (Spring) |
seconds | 90 | anything that takes a number |
timespan | 00:01:30 | .NET TimeSpan.Parse |
Renderers double every $ in a literal ($ becomes $$), because Kubernetes expands $(NAME) in env values. Platform authors never see encodings: they write "90s" and ["a", "b"] for every app. A keySet uses the same encodings and separator as a list of strings, with keys for items.
Exact parsing
There is one parsing rule per type, and it is exact: an SDK accepts exactly the strings below, whatever its host library accepts on its own. Where the host is more lenient (strconv.ParseBool takes 1 and t, float() takes inf and spaces, Integer("010") is octal), the SDK adds a pre-check that rejects the extra forms with invalid_type. The platform only ever renders the canonical forms; these rules decide what an app accepts from values set outside the platform, so that every SDK accepts the same ones.
| Type | Accepted | Rejected (invalid_type) |
|---|---|---|
| every type | the value as given: never trimmed, ASCII digits only | " true", "8080\n", "5s " |
bool | true or false in any case: TRUE, False | 1, 0, t, yes, on |
int | ^[+-]?[0-9]+$, base 10: 007 is 7 | 0x10, 0o17, 1_000, 1e3, 1.0; outside 64 bits is out_of_range |
float | a digit on each side of the point, optional exponent: 0.5, 1e-3 | .5, 5., inf, NaN, 0x1p4, 0,5, 1e400 |
duration (go) | Go's time.ParseDuration grammar: 1m30s, 1.5h, -5s, 0 | 5, 5S, 1d, 1m 30s |
duration (iso8601) | P[nD][T[nH][nM][nS]], upper case, , or . for a fraction | years, months, weeks, a sign |
duration (seconds, timespan) | ^[0-9]+(\.[0-9]+)?$; [d.]hh:mm:ss[.f] | a sign, an exponent |
list and keySet items | split on every separator, without trimming: a, b is a and b | an empty or space-padded int item |
json | one JSON value, with only JSON whitespace around it | trailing text |
The conformance suite tests these rules under the strict-parsing tag. See SPEC.md §5 for the full grammar.
Profiles
Values in files the image ships with are part of what the app runs with, so the contract includes them. A value in an always-loaded base file (appsettings.json, application.yml) is a default. A value in a profile file (appsettings.Staging.json) goes in profiles.defaults, applied when the selector variable (ASPNETCORE_ENVIRONMENT, SPRING_PROFILES_ACTIVE, RAILS_ENV) selects that profile. A required variable is satisfied when the platform or the selected profile supplies it. A secret may never have a value in any file.