Nickel contracts for a Lambda canary deploy config
Type the deploy config; let export refuse 150 percent.
Config is code that nobody tests. Give it a contract, export JSON only when the contract holds, and let the export fail loudly when someone types 150 percent.
§I. Frame
C-days bridge Ops to config languages. Nickel is the first row: a configuration language with contracts (checks attached to fields), merging with priorities, and export to JSON, YAML, or TOML. The anchor names HedronOS/Facet config as the long target; today's referent is smaller and real: the deploy settings for a Lambda canary, which the paired DOP lesson walks through on the AWS side.
Done bar: one Nickel file holding prod and staging deploy settings that share a base, a contract that rejects bad values at export, and a JSON file a pipeline can read.
§II. Three techniques
Contract at the Field (named technique). Attach the check to the field, not to a later script.
let Percent = std.contract.from_predicate (fun n => std.is_number n && n > 0 && n < 100) in
let Strategy = [| 'Canary, 'Linear, 'AllAtOnce |] in
let Deploy = {
function_name | String,
alias | String | default = "live",
strategy | Strategy,
step_percent | Percent,
interval_minutes | Number | default = 10,
alarms | Array String,
memory_mb | Number | default = 512,
} in
Strategy is an enum: a typo like 'Canery fails. Percent is a predicate contract.
Merge with Priorities (named technique). & merges records. A field marked default yields to any plain value; force overrides a plain value. Environments become small overlays on one base:
let base | Deploy = {
function_name = "orders-api", strategy = 'Canary,
step_percent = 10, alarms = ["orders-api-5xx", "orders-api-p99"],
} in
{
prod | Deploy = base & { memory_mb | force = 1024 },
staging | Deploy = base & { alarms | force = [], strategy | force = 'AllAtOnce,
step_percent | force = 50 },
}
Contrast Nix //: it replaces right-biased, silently, with no types.
Export Is the Gate (named technique). nickel export evaluates, applies every contract, and only then prints. A contract failure is a nonzero exit, so the pipeline stops before CodeDeploy ever sees the value.
§III. Run it
nickel export deploy.ncl (nickel 1.18.0, real, exit 0, trimmed to prod):
"prod": {
"alarms": ["orders-api-5xx", "orders-api-p99"],
"alias": "live",
"function_name": "orders-api",
"interval_minutes": 10,
"memory_mb": 1024,
"step_percent": 10,
"strategy": "Canary"
}
staging came out with memory_mb: 512 (the default held), alarms: [], strategy: "AllAtOnce", step_percent: 50. Defaults filled in, overrides applied, contracts passed.
bad.ncl sets step_percent = 150. Real output, exit 1:
error: contract broken by the value of `step_percent`
┌─ .../nickel/bad.ncl:4:66
3 │ let Deploy = { function_name | String, step_percent | Percent, alarms | Array String } in
│ ------- expected type
4 │ { prod | Deploy = { function_name = "orders-api", step_percent = 150, alarms = [] } }
│ ^^^ applied to this expression
The error names the field, the contract, and the line.
§IV. Into the pipeline
- Commit
deploy.ncl; generatedeploy.jsonin CI withnickel export. Do not hand-edit the JSON. strategyplusstep_percentplusinterval_minutesmap onto a CodeDeploy Lambda config name (for example Canary, 10 percent, 10 minutes). The DOP lesson covers what CodeDeploy does with it.staginghas no alarms. That is a choice the config makes visible; a contract could forbid emptyalarmsfor'Canaryif prod policy requires it.- Nickel runs from nixpkgs (
nix run nixpkgs#nickel), so the Nix flake from 2026-10-12 can carry it as a check input.
§V. Drills
- Someone writes
strategy = 'Canery. Where does it fail? (At export: not a member of the enum.) - Base sets
memory_mbas a default and prod writesmemory_mb = 1024with no annotation. Who wins? (Prod: a plain value beats a default.) - Why is exit code 1 on export useful? (The pipeline stops before deploy.)
- Name one rule a contract could add for prod. (Non-empty
alarmswhen strategy is Canary or Linear.)
Close
Contract at the Field, Merge with Priorities, Export Is the Gate. Type the deploy config, build environments as overlays, and let nickel export refuse bad values before AWS sees them.
Related
- Tome: Nix language basics (referenced, for the // contrast)
- Web: Nickel contracts · merging · typing · CodeDeploy deployment configurations
- Paired Cert: AWS DOP Lambda aliases and CodeDeploy traffic shifting (2026-10-13)