Schema validation
values.schema.json asserts that a package’s merged values are well-formed
before render. Hull validates the values — after every layer, environment,
profile, and CLI override has been applied — and aborts with a precise error if
validation fails. It runs during hull template, hull install, and
hull upgrade.
This guide covers authoring patterns. The supported keyword subset is in the
values.schema.json reference.
Why it pays off
Without a schema, a misconfigured replicas: "three" is caught only by the API
server — after the install has already started applying. With a schema:
- Typos (
replicaa: 3) are rejected before render. - Type mismatches (
replicas: "three") are rejected with a path and reason. - Missing required fields are listed by full path.
- Out-of-range numbers, bad patterns, and unknown keys are all caught.
The schema also documents the package: hull config <pkg> walks it
interactively, using description and default for its prompts.
Authoring a schema
A pragmatic schema for a web app:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "my-app values",
"type": "object",
"required": ["image"],
"additionalProperties": false,
"properties": {
"replicas": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 1,
"description": "Replica count for the main Deployment."
},
"image": { "$ref": "#/$defs/image" },
"service": {
"type": "object",
"additionalProperties": false,
"properties": {
"type": { "enum": ["ClusterIP", "NodePort", "LoadBalancer"], "default": "ClusterIP" },
"port": { "type": "integer", "minimum": 1, "maximum": 65535 }
}
}
},
"$defs": {
"image": {
"type": "object",
"required": ["repository"],
"additionalProperties": false,
"properties": {
"repository": { "type": "string", "minLength": 1 },
"tag": { "type": "string", "default": "latest" },
"pullPolicy": { "enum": ["Always", "IfNotPresent", "Never"], "default": "IfNotPresent" }
}
}
}
}
Note the moves:
additionalProperties: falseon every object catches typos.$reffactors out repeated shapes.defaultvalues surface inhull config.
Patterns
Either / or
A user must supply either an existing Secret name or an inline password:
{
"auth": {
"oneOf": [
{ "type": "object", "required": ["existingSecret"], "additionalProperties": false,
"properties": { "existingSecret": { "type": "string", "minLength": 1 } } },
{ "type": "object", "required": ["password"], "additionalProperties": false,
"properties": { "password": { "type": "string", "minLength": 8 } } }
]
}
}
Supplying both is rejected — the value matches both branches and oneOf
requires exactly one.
Discriminated union
A const key selects between alternative bodies:
{
"storage": {
"oneOf": [
{ "type": "object", "required": ["type", "size"], "additionalProperties": false,
"properties": { "type": { "const": "pvc" }, "size": { "type": "string" } } },
{ "type": "object", "required": ["type", "bucket"], "additionalProperties": false,
"properties": { "type": { "const": "s3" }, "bucket": { "type": "string" } } }
]
}
}
Cross-field constraint
dependentRequired reads as “if tls is set, also require host”:
{
"tls": { "type": "boolean", "default": false },
"host": { "type": "string" },
"dependentRequired": { "tls": ["host"] }
}
Pattern validation
{
"tag": { "type": "string", "pattern": "^[A-Za-z0-9_][A-Za-z0-9._\\-]{0,127}$" },
"digest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" }
}
What schemas can’t enforce
- Cluster references (“this must name a Service that exists”) — use a
pre-installhook that checks at run time. - Complex cross-field rules beyond
oneOf/dependentRequired— write a policy rule underpolicies/. - Derived defaults — schemas express valid shapes, not computed values.
Use template expressions (
${values.image.tag | default package.version}).
Composition with layers
When a parent has layers, each layer’s values.schema.json is loaded and merged
into the parent’s schema under the layer’s namespace, so a layer’s image
becomes a valid path at <layer-name>.image.
Errors
Validation errors carry the full JSON-pointer path, the violating value, and the keyword, one per line:
hull template . --set replicas=100 --set service.type=Bad
Error: values failed schema validation:
- $.service.type: value not in enum
- $.replicas: 100 greater than maximum 50
Other typical messages:
- $.image.repository: required property missing
- $.replicas: expected integer, got string
- $.replicaa: additional property not allowed
- $.tag: string does not match pattern "^[a-z0-9.]+$"
- $: property "tls" requires "host" to be set
- $.storage: value matches 0 of oneOf (expected exactly 1)
Inspecting and gating
hull config . # interactive walker that builds a values file from the schema
Walking schema. Press <enter> to keep defaults.
image.repository (string) *: ...
hull lint does not run schema validation — it only checks that the schema
file is valid JSON. To gate values in CI, render them so the check runs:
hull template . -f ci-values.yaml # non-zero exit on any violation
hull install --dry-run server does the same against a live API server.
See also
values.schema.jsonreference — the supported keyword subset.- Values — how the values being validated are assembled.
hull configandhull lint.