Convert JSON Schema



JSON Schema is a widely used, but unwieldy schema and validation format. CUE can import any JSON Schema, making it easier to read and maintain.

JSONSchema

A JSON Schema is just JSON itself. The difference is that the Schema is a DSL (domain specific language), meaning that it has special fields and a schema for itself. What inception! When we import this into CUE, the cue cli will notice this and handle it differently.

We are going to use the docker-compose spec from compose-spec. You can find many more examples on the Schema Store or use any custom ones you have around.

Download the docker-compose schema with this command:

wget https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json
compose-spec.json (800+ lines)

compose-spec.json

{
  "$schema": "http://json-schema.org/draft/2019-09/schema#",
  "id": "compose_spec.json",
  "type": "object",
  "title": "Compose Specification",
  "description": "The Compose file is a YAML file defining a multi-containers based application.",

  "properties": {
    "version": {
      "type": "string",
      "description": "Version of the Compose specification used. Tools not implementing required version MUST reject the configuration file."
    },

    "services": {
      "id": "#/properties/services",
      "type": "object",
      "patternProperties": {
        "^[a-zA-Z0-9._-]+$": {
          "$ref": "#/definitions/service"
        }
      },
      "additionalProperties": false
    },

    "networks": {
      "id": "#/properties/networks",
      "type": "object",
      "patternProperties": {
        "^[a-zA-Z0-9._-]+$": {
          "$ref": "#/definitions/network"
        }
      }
    },

    "volumes": {
      "id": "#/properties/volumes",
      "type": "object",
      "patternProperties": {
        "^[a-zA-Z0-9._-]+$": {
          "$ref": "#/definitions/volume"
        }
      },
      "additionalProperties": false
    },

    "secrets": {
      "id": "#/properties/secrets",
      "type": "object",
      "patternProperties": {
        "^[a-zA-Z0-9._-]+$": {
          "$ref": "#/definitions/secret"
        }
      },
      "additionalProperties": false
    },

    "configs": {
      "id": "#/properties/configs",
      "type": "object",
      "patternProperties": {
        "^[a-zA-Z0-9._-]+$": {
          "$ref": "#/definitions/config"
        }
      },
      "additionalProperties": false
    }
  },

  "patternProperties": {"^x-": {}},
  "additionalProperties": false,

  "definitions": {

    "service": {
      "id": "#/definitions/service",
      "type": "object",

      "properties": {
        "deploy": {"$ref": "#/definitions/deployment"},
        "build": {
          "oneOf": [
            {"type": "string"},
            {
              "type": "object",
              "properties": {
                "context": {"type": "string"},
                "dockerfile": {"type": "string"},
                "args": {"$ref": "#/definitions/list_or_dict"},
                "labels": {"$ref": "#/definitions/list_or_dict"},
                "cache_from": {"type": "array", "items": {"type": "string"}},
                "network": {"type": "string"},
                "target": {"type": "string"},
                "shm_size": {"type": ["integer", "string"]},
                "extra_hosts": {"$ref": "#/definitions/list_or_dict"},
                "isolation": {"type": "string"}
              },
              "additionalProperties": false,
              "patternProperties": {"^x-": {}}
            }
          ]
        },
        "blkio_config": {
          "type": "object",
          "properties": {
            "device_read_bps": {
              "type": "array",
              "items": {"$ref": "#/definitions/blkio_limit"}
            },
            "device_read_iops": {
              "type": "array",
              "items": {"$ref": "#/definitions/blkio_limit"}
            },
            "device_write_bps": {
              "type": "array",
              "items": {"$ref": "#/definitions/blkio_limit"}
            },
            "device_write_iops": {
              "type": "array",
              "items": {"$ref": "#/definitions/blkio_limit"}
            },
            "weight": {"type": "integer"},
            "weight_device": {
              "type": "array",
              "items": {"$ref": "#/definitions/blkio_weight"}
            }
          },
          "additionalProperties": false
        },
        "cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
        "cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
        "cgroup_parent": {"type": "string"},
        "command": {
          "oneOf": [
            {"type": "string"},
            {"type": "array", "items": {"type": "string"}}
          ]
        },
        "configs": {
          "type": "array",
          "items": {
            "oneOf": [
              {"type": "string"},
              {
                "type": "object",
                "properties": {
                  "source": {"type": "string"},
                  "target": {"type": "string"},
                  "uid": {"type": "string"},
                  "gid": {"type": "string"},
                  "mode": {"type": "number"}
                },
                "additionalProperties": false,
                "patternProperties": {"^x-": {}}
              }
            ]
          }
        },
        "container_name": {"type": "string"},
        "cpu_count": {"type": "integer", "minimum": 0},
        "cpu_percent": {"type": "integer", "minimum": 0, "maximum": 100},
        "cpu_shares": {"type": ["number", "string"]},
        "cpu_quota": {"type": ["number", "string"]},
        "cpu_period": {"type": ["number", "string"]},
        "cpu_rt_period": {"type": ["number", "string"]},
        "cpu_rt_runtime": {"type": ["number", "string"]},
        "cpus": {"type": ["number", "string"]},
        "cpuset": {"type": "string"},
        "credential_spec": {
          "type": "object",
          "properties": {
            "config": {"type": "string"},
            "file": {"type": "string"},
            "registry": {"type": "string"}
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        },
        "depends_on": {
          "oneOf": [
            {"$ref": "#/definitions/list_of_strings"},
            {
              "type": "object",
              "additionalProperties": false,
              "patternProperties": {
                "^[a-zA-Z0-9._-]+$": {
                  "type": "object",
                  "additionalProperties": false,
                  "properties": {
                    "condition": {
                      "type": "string",
                      "enum": ["service_started", "service_healthy", "service_completed_successfully"]
                    }
                  },
                  "required": ["condition"]
                }
              }
            }
          ]
        },
        "device_cgroup_rules": {"$ref": "#/definitions/list_of_strings"},
        "devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
        "dns": {"$ref": "#/definitions/string_or_list"},
        "dns_opt": {"type": "array","items": {"type": "string"}, "uniqueItems": true},
        "dns_search": {"$ref": "#/definitions/string_or_list"},
        "domainname": {"type": "string"},
        "entrypoint": {
          "oneOf": [
            {"type": "string"},
            {"type": "array", "items": {"type": "string"}}
          ]
        },
        "env_file": {"$ref": "#/definitions/string_or_list"},
        "environment": {"$ref": "#/definitions/list_or_dict"},

        "expose": {
          "type": "array",
          "items": {
            "type": ["string", "number"],
            "format": "expose"
          },
          "uniqueItems": true
        },
        "extends": {
          "oneOf": [
            {"type": "string"},
            {
              "type": "object",

              "properties": {
                "service": {"type": "string"},
                "file": {"type": "string"}
              },
              "required": ["service"],
              "additionalProperties": false
            }
          ]
        },
        "external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
        "extra_hosts": {"$ref": "#/definitions/list_or_dict"},
        "group_add": {
          "type": "array",
          "items": {
            "type": ["string", "number"]
          },
          "uniqueItems": true
        },
        "healthcheck": {"$ref": "#/definitions/healthcheck"},
        "hostname": {"type": "string"},
        "image": {"type": "string"},
        "init": {"type": "boolean"},
        "ipc": {"type": "string"},
        "isolation": {"type": "string"},
        "labels": {"$ref": "#/definitions/list_or_dict"},
        "links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
        "logging": {
          "type": "object",

          "properties": {
            "driver": {"type": "string"},
            "options": {
              "type": "object",
              "patternProperties": {
                "^.+$": {"type": ["string", "number", "null"]}
              }
            }
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        },
        "mac_address": {"type": "string"},
        "mem_limit": {"type": ["number", "string"]},
        "mem_reservation": {"type": ["string", "integer"]},
        "mem_swappiness": {"type": "integer"},
        "memswap_limit": {"type": ["number", "string"]},
        "network_mode": {"type": "string"},
        "networks": {
          "oneOf": [
            {"$ref": "#/definitions/list_of_strings"},
            {
              "type": "object",
              "patternProperties": {
                "^[a-zA-Z0-9._-]+$": {
                  "oneOf": [
                    {
                      "type": "object",
                      "properties": {
                        "aliases": {"$ref": "#/definitions/list_of_strings"},
                        "ipv4_address": {"type": "string"},
                        "ipv6_address": {"type": "string"},
                        "link_local_ips": {"$ref": "#/definitions/list_of_strings"},
                        "priority": {"type": "number"}
                      },
                      "additionalProperties": false,
                      "patternProperties": {"^x-": {}}
                    },
                    {"type": "null"}
                  ]
                }
              },
              "additionalProperties": false
            }
          ]
        },
        "oom_kill_disable": {"type": "boolean"},
        "oom_score_adj": {"type": "integer", "minimum": -1000, "maximum": 1000},
        "pid": {"type": ["string", "null"]},
        "pids_limit": {"type": ["number", "string"]},
        "platform": {"type": "string"},
        "ports": {
          "type": "array",
          "items": {
            "oneOf": [
              {"type": "number", "format": "ports"},
              {"type": "string", "format": "ports"},
              {
                "type": "object",
                "properties": {
                  "mode": {"type": "string"},
                  "host_ip": {"type": "string"},
                  "target": {"type": "integer"},
                  "published": {"type": "integer"},
                  "protocol": {"type": "string"}
                },
                "additionalProperties": false,
                "patternProperties": {"^x-": {}}
              }
            ]
          },
          "uniqueItems": true
        },
        "privileged": {"type": "boolean"},
        "profiles": {"$ref": "#/definitions/list_of_strings"},
        "pull_policy": {"type": "string", "enum": [
          "always", "never", "if_not_present", "build", "missing"
        ]},
        "read_only": {"type": "boolean"},
        "restart": {"type": "string"},
        "runtime": {
          "type": "string"
        },
        "scale": {
          "type": "integer"
        },
        "security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
        "shm_size": {"type": ["number", "string"]},
        "secrets": {
          "type": "array",
          "items": {
            "oneOf": [
              {"type": "string"},
              {
                "type": "object",
                "properties": {
                  "source": {"type": "string"},
                  "target": {"type": "string"},
                  "uid": {"type": "string"},
                  "gid": {"type": "string"},
                  "mode": {"type": "number"}
                },
                "additionalProperties": false,
                "patternProperties": {"^x-": {}}
              }
            ]
          }
        },
        "sysctls": {"$ref": "#/definitions/list_or_dict"},
        "stdin_open": {"type": "boolean"},
        "stop_grace_period": {"type": "string", "format": "duration"},
        "stop_signal": {"type": "string"},
        "storage_opt": {"type": "object"},
        "tmpfs": {"$ref": "#/definitions/string_or_list"},
        "tty": {"type": "boolean"},
        "ulimits": {
          "type": "object",
          "patternProperties": {
            "^[a-z]+$": {
              "oneOf": [
                {"type": "integer"},
                {
                  "type": "object",
                  "properties": {
                    "hard": {"type": "integer"},
                    "soft": {"type": "integer"}
                  },
                  "required": ["soft", "hard"],
                  "additionalProperties": false,
                  "patternProperties": {"^x-": {}}
                }
              ]
            }
          }
        },
        "user": {"type": "string"},
        "userns_mode": {"type": "string"},
        "volumes": {
          "type": "array",
          "items": {
            "oneOf": [
              {"type": "string"},
              {
                "type": "object",
                "required": ["type"],
                "properties": {
                  "type": {"type": "string"},
                  "source": {"type": "string"},
                  "target": {"type": "string"},
                  "read_only": {"type": "boolean"},
                  "consistency": {"type": "string"},
                  "bind": {
                    "type": "object",
                    "properties": {
                      "propagation": {"type": "string"},
                      "create_host_path": {"type": "boolean"}
                    },
                    "additionalProperties": false,
                    "patternProperties": {"^x-": {}}
                  },
                  "volume": {
                    "type": "object",
                    "properties": {
                      "nocopy": {"type": "boolean"}
                    },
                    "additionalProperties": false,
                    "patternProperties": {"^x-": {}}
                  },
                  "tmpfs": {
                    "type": "object",
                    "properties": {
                      "size": {
                        "oneOf": [
                          {"type": "integer", "minimum": 0},
                          {"type": "string"}
                        ]
                      }
                    },
                    "additionalProperties": false,
                    "patternProperties": {"^x-": {}}
                  }
                },
                "additionalProperties": false,
                "patternProperties": {"^x-": {}}
              }
            ]
          },
          "uniqueItems": true
        },
        "volumes_from": {
          "type": "array",
          "items": {"type": "string"},
          "uniqueItems": true
        },
        "working_dir": {"type": "string"}
      },
      "patternProperties": {"^x-": {}},
      "additionalProperties": false
    },

    "healthcheck": {
      "id": "#/definitions/healthcheck",
      "type": "object",
      "properties": {
        "disable": {"type": "boolean"},
        "interval": {"type": "string", "format": "duration"},
        "retries": {"type": "number"},
        "test": {
          "oneOf": [
            {"type": "string"},
            {"type": "array", "items": {"type": "string"}}
          ]
        },
        "timeout": {"type": "string", "format": "duration"},
        "start_period": {"type": "string", "format": "duration"}
      },
      "additionalProperties": false,
      "patternProperties": {"^x-": {}}
    },
    "deployment": {
      "id": "#/definitions/deployment",
      "type": ["object", "null"],
      "properties": {
        "mode": {"type": "string"},
        "endpoint_mode": {"type": "string"},
        "replicas": {"type": "integer"},
        "labels": {"$ref": "#/definitions/list_or_dict"},
        "rollback_config": {
          "type": "object",
          "properties": {
            "parallelism": {"type": "integer"},
            "delay": {"type": "string", "format": "duration"},
            "failure_action": {"type": "string"},
            "monitor": {"type": "string", "format": "duration"},
            "max_failure_ratio": {"type": "number"},
            "order": {"type": "string", "enum": [
              "start-first", "stop-first"
            ]}
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        },
        "update_config": {
          "type": "object",
          "properties": {
            "parallelism": {"type": "integer"},
            "delay": {"type": "string", "format": "duration"},
            "failure_action": {"type": "string"},
            "monitor": {"type": "string", "format": "duration"},
            "max_failure_ratio": {"type": "number"},
            "order": {"type": "string", "enum": [
              "start-first", "stop-first"
            ]}
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        },
        "resources": {
          "type": "object",
          "properties": {
            "limits": {
              "type": "object",
              "properties": {
                "cpus": {"type": ["number", "string"]},
                "memory": {"type": "string"}
              },
              "additionalProperties": false,
              "patternProperties": {"^x-": {}}
            },
            "reservations": {
              "type": "object",
              "properties": {
                "cpus": {"type": ["number", "string"]},
                "memory": {"type": "string"},
                "generic_resources": {"$ref": "#/definitions/generic_resources"},
                "devices": {"$ref": "#/definitions/devices"}
              },
              "additionalProperties": false,
              "patternProperties": {"^x-": {}}
            }
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        },
        "restart_policy": {
          "type": "object",
          "properties": {
            "condition": {"type": "string"},
            "delay": {"type": "string", "format": "duration"},
            "max_attempts": {"type": "integer"},
            "window": {"type": "string", "format": "duration"}
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        },
        "placement": {
          "type": "object",
          "properties": {
            "constraints": {"type": "array", "items": {"type": "string"}},
            "preferences": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "spread": {"type": "string"}
                },
                "additionalProperties": false,
                "patternProperties": {"^x-": {}}
              }
            },
            "max_replicas_per_node": {"type": "integer"}
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        }
      },
      "additionalProperties": false,
      "patternProperties": {"^x-": {}}
    },

    "generic_resources": {
      "id": "#/definitions/generic_resources",
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "discrete_resource_spec": {
            "type": "object",
            "properties": {
              "kind": {"type": "string"},
              "value": {"type": "number"}
            },
            "additionalProperties": false,
            "patternProperties": {"^x-": {}}
          }
        },
        "additionalProperties": false,
        "patternProperties": {"^x-": {}}
      }
    },

    "devices": {
      "id": "#/definitions/devices",
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
            "capabilities": {"$ref": "#/definitions/list_of_strings"},
            "count": {"type": ["string", "integer"]},
            "device_ids": {"$ref": "#/definitions/list_of_strings"},
            "driver":{"type": "string"},
            "options":{"$ref": "#/definitions/list_or_dict"}
          },
        "additionalProperties": false,
        "patternProperties": {"^x-": {}}
      }
    },

    "network": {
      "id": "#/definitions/network",
      "type": ["object", "null"],
      "properties": {
        "name": {"type": "string"},
        "driver": {"type": "string"},
        "driver_opts": {
          "type": "object",
          "patternProperties": {
            "^.+$": {"type": ["string", "number"]}
          }
        },
        "ipam": {
          "type": "object",
          "properties": {
            "driver": {"type": "string"},
            "config": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "subnet": {"type": "string", "format": "subnet_ip_address"},
                  "ip_range": {"type": "string"},
                  "gateway": {"type": "string"},
                  "aux_addresses": {
                    "type": "object",
                    "additionalProperties": false,
                    "patternProperties": {"^.+$": {"type": "string"}}
                  }
                },
                "additionalProperties": false,
                "patternProperties": {"^x-": {}}
              }
            },
            "options": {
              "type": "object",
              "additionalProperties": false,
              "patternProperties": {"^.+$": {"type": "string"}}
            }
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        },
        "external": {
          "type": ["boolean", "object"],
          "properties": {
            "name": {
              "deprecated": true,
              "type": "string"
            }
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        },
        "internal": {"type": "boolean"},
        "enable_ipv6": {"type": "boolean"},
        "attachable": {"type": "boolean"},
        "labels": {"$ref": "#/definitions/list_or_dict"}
      },
      "additionalProperties": false,
      "patternProperties": {"^x-": {}}
    },

    "volume": {
      "id": "#/definitions/volume",
      "type": ["object", "null"],
      "properties": {
        "name": {"type": "string"},
        "driver": {"type": "string"},
        "driver_opts": {
          "type": "object",
          "patternProperties": {
            "^.+$": {"type": ["string", "number"]}
          }
        },
        "external": {
          "type": ["boolean", "object"],
          "properties": {
            "name": {
              "deprecated": true,
              "type": "string"
            }
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        },
        "labels": {"$ref": "#/definitions/list_or_dict"}
      },
      "additionalProperties": false,
      "patternProperties": {"^x-": {}}
    },

    "secret": {
      "id": "#/definitions/secret",
      "type": "object",
      "properties": {
        "name": {"type": "string"},
        "file": {"type": "string"},
        "external": {
          "type": ["boolean", "object"],
          "properties": {
            "name": {"type": "string"}
          }
        },
        "labels": {"$ref": "#/definitions/list_or_dict"},
        "driver": {"type": "string"},
        "driver_opts": {
          "type": "object",
          "patternProperties": {
            "^.+$": {"type": ["string", "number"]}
          }
        },
        "template_driver": {"type": "string"}
      },
      "additionalProperties": false,
      "patternProperties": {"^x-": {}}
    },

    "config": {
      "id": "#/definitions/config",
      "type": "object",
      "properties": {
        "name": {"type": "string"},
        "file": {"type": "string"},
        "external": {
          "type": ["boolean", "object"],
          "properties": {
            "name": {
              "deprecated": true,
              "type": "string"
            }
          }
        },
        "labels": {"$ref": "#/definitions/list_or_dict"},
        "template_driver": {"type": "string"}
      },
      "additionalProperties": false,
      "patternProperties": {"^x-": {}}
    },

    "string_or_list": {
      "oneOf": [
        {"type": "string"},
        {"$ref": "#/definitions/list_of_strings"}
      ]
    },

    "list_of_strings": {
      "type": "array",
      "items": {"type": "string"},
      "uniqueItems": true
    },

    "list_or_dict": {
      "oneOf": [
        {
          "type": "object",
          "patternProperties": {
            ".+": {
              "type": ["string", "number", "boolean", "null"]
            }
          },
          "additionalProperties": false
        },
        {"type": "array", "items": {"type": "string"}, "uniqueItems": true}
      ]
    },

    "blkio_limit": {
      "type": "object",
      "properties": {
        "path": {"type": "string"},
        "rate": {"type": ["integer", "string"]}
      },
      "additionalProperties": false
    },
    "blkio_weight": {
      "type": "object",
      "properties": {
        "path": {"type": "string"},
        "weight": {"type": "integer"}
      },
      "additionalProperties": false
    },

    "constraints": {
      "service": {
        "id": "#/definitions/constraints/service",
        "anyOf": [
          {"required": ["build"]},
          {"required": ["image"]}
        ],
        "properties": {
          "build": {
            "required": ["context"]
          }
        }
      }
    }
  }
}

Converting to Cuelang

Converting JSON Schema to CUE is a one liner and a no brainer.

cue import -f -p compose -l '#ComposeSpec:' compose-spec.json
  • -f forces overwriting the output file (compose-spec.cue)
  • -p sets the package name in the output
  • -l is the label to put the schema under
  • the last argument is the input schema

While there are a lot of fields in a docker-compose.yaml file, and still 400+ lines, this CUE version is far easier to read and understand.

compose-spec.json (400+ lines)

compose-spec.cue

package compose

import "list"

#ComposeSpec: {
	// Compose Specification
	//
	// The Compose file is a YAML file defining a multi-containers
	// based application.
	@jsonschema(schema="http://json-schema.org/draft/2019-09/schema#")

	// Version of the Compose specification used. Tools not
	// implementing required version MUST reject the configuration
	// file.
	version?: string
	services?: {
		{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: #service}
	}
	networks?: {
		{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: #network}
		...
	}
	volumes?: {
		{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: #volume}
	}
	secrets?: {
		{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: #secret}
	}
	configs?: {
		{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: #config}
	}

	{[=~"^x-" & !~"^(version|services|networks|volumes|secrets|configs)$"]: _}

	#service: {
		deploy?: #deployment
		build?:  string | {
			context?:    string
			dockerfile?: string
			args?:       #list_or_dict
			labels?:     #list_or_dict
			cache_from?: [...string]
			network?:     string
			target?:      string
			shm_size?:    int | string
			extra_hosts?: #list_or_dict
			isolation?:   string

			{[=~"^x-" & !~"^(context|dockerfile|args|labels|cache_from|network|target|shm_size|extra_hosts|isolation)$"]: _}
		}
		blkio_config?: {
			device_read_bps?: [...#blkio_limit]
			device_read_iops?: [...#blkio_limit]
			device_write_bps?: [...#blkio_limit]
			device_write_iops?: [...#blkio_limit]
			weight?: int
			weight_device?: [...#blkio_weight]
		}
		cap_add?:       list.UniqueItems() & [...string]
		cap_drop?:      list.UniqueItems() & [...string]
		cgroup_parent?: string
		command?:       string | [...string]
		configs?: [...string | {
			source?: string
			target?: string
			uid?:    string
			gid?:    string
			mode?:   number

			{[=~"^x-" & !~"^(source|target|uid|gid|mode)$"]: _}
		}]
		container_name?: string
		cpu_count?:      int & >=0
		cpu_percent?:    int & >=0 & <=100
		cpu_shares?:     number | string
		cpu_quota?:      number | string
		cpu_period?:     number | string
		cpu_rt_period?:  number | string
		cpu_rt_runtime?: number | string
		cpus?:           number | string
		cpuset?:         string
		credential_spec?: {
			config?:   string
			file?:     string
			registry?: string

			{[=~"^x-" & !~"^(config|file|registry)$"]: _}
		}
		depends_on?: #list_of_strings | {
			{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: condition: "service_started" | "service_healthy" | "service_completed_successfully"}
		}
		device_cgroup_rules?: #list_of_strings
		devices?:             list.UniqueItems() & [...string]
		dns?:                 #string_or_list
		dns_opt?:             list.UniqueItems() & [...string]
		dns_search?:          #string_or_list
		domainname?:          string
		entrypoint?:          string | [...string]
		env_file?:            #string_or_list
		environment?:         #list_or_dict
		expose?:              list.UniqueItems() & [...number | string]
		extends?:             string | {
			service: string
			file?:   string
		}
		external_links?: list.UniqueItems() & [...string]
		extra_hosts?:    #list_or_dict
		group_add?:      list.UniqueItems() & [...number | string]
		healthcheck?:    #healthcheck
		hostname?:       string
		image?:          string
		init?:           bool
		ipc?:            string
		isolation?:      string
		labels?:         #list_or_dict
		links?:          list.UniqueItems() & [...string]
		logging?: {
			driver?: string
			options?: {
				{[=~"^.+$" & !~"^()$"]: null | number | string}
				...
			}

			{[=~"^x-" & !~"^(driver|options)$"]: _}
		}
		mac_address?:     string
		mem_limit?:       number | string
		mem_reservation?: int | string
		mem_swappiness?:  int
		memswap_limit?:   number | string
		network_mode?:    string
		networks?:        #list_of_strings | {
			{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: {
				aliases?: #list_of_strings, ipv4_address?: string, ipv6_address?: string, link_local_ips?: #list_of_strings, priority?: number

				{[=~"^x-" & !~"^(aliases|ipv4_address|ipv6_address|link_local_ips|priority)$"]: _}
			} | null
			}
		}
		oom_kill_disable?: bool
		oom_score_adj?:    int & >=-1000 & <=1000
		pid?:              null | string
		pids_limit?:       number | string
		platform?:         string
		ports?:            list.UniqueItems() & [...number | string | {
			mode?:      string
			host_ip?:   string
			target?:    int
			published?: int
			protocol?:  string

			{[=~"^x-" & !~"^(mode|host_ip|target|published|protocol)$"]: _}
		}]
		privileged?:   bool
		profiles?:     #list_of_strings
		pull_policy?:  "always" | "never" | "if_not_present" | "build" | "missing"
		read_only?:    bool
		restart?:      string
		runtime?:      string
		scale?:        int
		security_opt?: list.UniqueItems() & [...string]
		shm_size?:     number | string
		secrets?: [...string | {
			source?: string
			target?: string
			uid?:    string
			gid?:    string
			mode?:   number

			{[=~"^x-" & !~"^(source|target|uid|gid|mode)$"]: _}
		}]
		sysctls?:           #list_or_dict
		stdin_open?:        bool
		stop_grace_period?: string
		stop_signal?:       string
		storage_opt?: {
			...
		}
		tmpfs?: #string_or_list
		tty?:   bool
		ulimits?: {
			{[=~"^[a-z]+$" & !~"^()$"]: int | {
				hard: int, soft: int

				{[=~"^x-" & !~"^(hard|soft)$"]: _}
			}}
			...
		}
		user?:        string
		userns_mode?: string
		volumes?:     list.UniqueItems() & [...string | {
			type:         string
			source?:      string
			target?:      string
			read_only?:   bool
			consistency?: string
			bind?: {
				propagation?:      string
				create_host_path?: bool

				{[=~"^x-" & !~"^(propagation|create_host_path)$"]: _}
			}
			volume?: {
				nocopy?: bool

				{[=~"^x-" & !~"^(nocopy)$"]: _}
			}
			tmpfs?: {
				size?: int & >=0 | string

				{[=~"^x-" & !~"^(size)$"]: _}
			}

			{[=~"^x-" & !~"^(type|source|target|read_only|consistency|bind|volume|tmpfs)$"]: _}
		}]
		volumes_from?: list.UniqueItems() & [...string]
		working_dir?:  string

		{[=~"^x-" & !~"^(deploy|build|blkio_config|cap_add|cap_drop|cgroup_parent|command|configs|container_name|cpu_count|cpu_percent|cpu_shares|cpu_quota|cpu_period|cpu_rt_period|cpu_rt_runtime|cpus|cpuset|credential_spec|depends_on|device_cgroup_rules|devices|dns|dns_opt|dns_search|domainname|entrypoint|env_file|environment|expose|extends|external_links|extra_hosts|group_add|healthcheck|hostname|image|init|ipc|isolation|labels|links|logging|mac_address|mem_limit|mem_reservation|mem_swappiness|memswap_limit|network_mode|networks|oom_kill_disable|oom_score_adj|pid|pids_limit|platform|ports|privileged|profiles|pull_policy|read_only|restart|runtime|scale|security_opt|shm_size|secrets|sysctls|stdin_open|stop_grace_period|stop_signal|storage_opt|tmpfs|tty|ulimits|user|userns_mode|volumes|volumes_from|working_dir)$"]: _}
	}

	#healthcheck: {
		disable?:      bool
		interval?:     string
		retries?:      number
		test?:         string | [...string]
		timeout?:      string
		start_period?: string

		{[=~"^x-" & !~"^(disable|interval|retries|test|timeout|start_period)$"]: _}
	}

	#deployment: null | {
		mode?:          string
		endpoint_mode?: string
		replicas?:      int
		labels?:        #list_or_dict
		rollback_config?: {
			parallelism?:       int
			delay?:             string
			failure_action?:    string
			monitor?:           string
			max_failure_ratio?: number
			order?:             "start-first" | "stop-first"

			{[=~"^x-" & !~"^(parallelism|delay|failure_action|monitor|max_failure_ratio|order)$"]: _}
		}
		update_config?: {
			parallelism?:       int
			delay?:             string
			failure_action?:    string
			monitor?:           string
			max_failure_ratio?: number
			order?:             "start-first" | "stop-first"

			{[=~"^x-" & !~"^(parallelism|delay|failure_action|monitor|max_failure_ratio|order)$"]: _}
		}
		resources?: {
			limits?: {
				cpus?:   number | string
				memory?: string

				{[=~"^x-" & !~"^(cpus|memory)$"]: _}
			}
			reservations?: {
				cpus?:              number | string
				memory?:            string
				generic_resources?: #generic_resources
				devices?:           #devices

				{[=~"^x-" & !~"^(cpus|memory|generic_resources|devices)$"]: _}
			}

			{[=~"^x-" & !~"^(limits|reservations)$"]: _}
		}
		restart_policy?: {
			condition?:    string
			delay?:        string
			max_attempts?: int
			window?:       string

			{[=~"^x-" & !~"^(condition|delay|max_attempts|window)$"]: _}
		}
		placement?: {
			constraints?: [...string]
			preferences?: [...{
				spread?: string

				{[=~"^x-" & !~"^(spread)$"]: _}
			}]
			max_replicas_per_node?: int

			{[=~"^x-" & !~"^(constraints|preferences|max_replicas_per_node)$"]: _}
		}

		{[=~"^x-" & !~"^(mode|endpoint_mode|replicas|labels|rollback_config|update_config|resources|restart_policy|placement)$"]: _}
	}

	#generic_resources: [...{
		discrete_resource_spec?: {
			kind?:  string
			value?: number

			{[=~"^x-" & !~"^(kind|value)$"]: _}
		}

		{[=~"^x-" & !~"^(discrete_resource_spec)$"]: _}
	}]

	#devices: [...{
		capabilities?: #list_of_strings
		count?:        int | string
		device_ids?:   #list_of_strings
		driver?:       string
		options?:      #list_or_dict

		{[=~"^x-" & !~"^(capabilities|count|device_ids|driver|options)$"]: _}
	}]

	#network: null | {
		name?:   string
		driver?: string
		driver_opts?: {
			{[=~"^.+$" & !~"^()$"]: number | string}
			...
		}
		ipam?: {
			driver?: string
			config?: [...{
				subnet?:   string
				ip_range?: string
				gateway?:  string
				aux_addresses?: {
					{[=~"^.+$" & !~"^()$"]: string}
				}

				{[=~"^x-" & !~"^(subnet|ip_range|gateway|aux_addresses)$"]: _}
			}]
			options?: {
				{[=~"^.+$" & !~"^()$"]: string}
			}

			{[=~"^x-" & !~"^(driver|config|options)$"]: _}
		}
		external?: bool | {
			name?: string @deprecated()

			{[=~"^x-" & !~"^(name)$"]: _}
		}
		internal?:    bool
		enable_ipv6?: bool
		attachable?:  bool
		labels?:      #list_or_dict

		{[=~"^x-" & !~"^(name|driver|driver_opts|ipam|external|internal|enable_ipv6|attachable|labels)$"]: _}
	}

	#volume: null | {
		name?:   string
		driver?: string
		driver_opts?: {
			{[=~"^.+$" & !~"^()$"]: number | string}
			...
		}
		external?: bool | {
			name?: string @deprecated()

			{[=~"^x-" & !~"^(name)$"]: _}
		}
		labels?: #list_or_dict

		{[=~"^x-" & !~"^(name|driver|driver_opts|external|labels)$"]: _}
	}

	#secret: {
		name?:     string
		file?:     string
		external?: bool | {
			name?: string
			...
		}
		labels?: #list_or_dict
		driver?: string
		driver_opts?: {
			{[=~"^.+$" & !~"^()$"]: number | string}
			...
		}
		template_driver?: string

		{[=~"^x-" & !~"^(name|file|external|labels|driver|driver_opts|template_driver)$"]: _}
	}

	#config: {
		name?:     string
		file?:     string
		external?: bool | {
			name?: string @deprecated()
			...
		}
		labels?:          #list_or_dict
		template_driver?: string

		{[=~"^x-" & !~"^(name|file|external|labels|template_driver)$"]: _}
	}

	#string_or_list: string | #list_of_strings

	#list_of_strings: list.UniqueItems() & [...string]

	#list_or_dict: {
		{[=~".+" & !~"^()$"]: null | bool | number | string}
	} | list.UniqueItems() & [...string]

	#blkio_limit: {
		path?: string
		rate?: int | string
	}

	#blkio_weight: {
		path?:   string
		weight?: int
	}

	#constraints: _
}

Validating with CUE

You can now validate your docker-compose.yaml files like we did in the Validate Configuration. In this case, we will want to specify the schema with the -d flag.

cue vet -d '#ComposeSpec' docker-compose.yaml compose-spec.cue

Drawbacks

CUE’s ability to import JSON Schema makes it easy to get started. However, the generated CUE doesn’t always have the greatest ergonomics for building up and validating values. This is often because the source schema is underspecified or there are unusual types. As such, you will likely find yourself building out and refining the imported schemas.

The following are taken from the docker-compose output generated here.

Missing constraints

Sometimes a schema does not add validation. We know there is a fixed format and valid versions for docker-compose, it’s just not in this source.

#version?: string
Any fields

For more complex fields, we can end up with _ (top) and have no validation for the field at all. You’ll see this more often when importing Go that has types with custom marshal functions.

#constraints: _
Duplication

CUE cannot easily detect code duplication and shared structure if it is not already present in the source schema. The following show missed opportunity to have a more succinct schema with CUE. This also happens with the regexp for the fields near the top like services and volumes.

duplication.cue

#secret: {
	name?:     string
	file?:     string
	external?: bool | {
		name?: string
		...
	}
	labels?: #list_or_dict
	driver?: string
	driver_opts?: {
		{[=~"^.+$" & !~"^()$"]: number | string}
		...
	}
	template_driver?: string

	{[=~"^x-" & !~"^(name|file|external|labels|driver|driver_opts|template_driver)$"]: _}
}

#config: {
	name?:     string
	file?:     string
	external?: bool | {
		name?: string @deprecated()
		...
	}
	labels?:          #list_or_dict
	template_driver?: string

	{[=~"^x-" & !~"^(name|file|external|labels|template_driver)$"]: _}
}

shared.cue

#shared: {
	name?:     string
	file?:     string
	external?: bool | {
		name?: string
		...
	}
	labels?:          #list_or_dict
	template_driver?: string
}

improved.cue

#secret: {
	#shared

	driver?: string
	driver_opts?: {
		{[=~"^.+$" & !~"^()$"]: number | string}
		...
	}

	{[=~"^x-" & !~"^(name|file|external|labels|driver|driver_opts|template_driver)$"]: _}
}

#config: {
	#shared

	{[=~"^x-" & !~"^(name|file|external|labels|template_driver)$"]: _}
}
We'll never share your email with anyone else.
2022 Hofstadter, Inc