Skip to main content
Project a build and a Helm chart | ESSCompile a component's build graph and runtime model, then project BuildKit inputs and a configuration-neutral Helm chart without running either.ESShow-toesshow-toadopterdevelopertry-spec-driven-developmentdeploy

Project a build and a Helm chart

A component repository describes how its image is built (ess-build/1) and how it runs (ess-runtime/1). ESS compiles both into canonical IR and projects them into files other tools consume: Dockerfile and Bake inputs for BuildKit, and a Helm chart. The projections run nothing. Independent component delivery describes where these files sit in the whole delivery path.

The commands on this page were run against the examples/oracle-fixture specification with the documents shown here.

Compile the build graph​

A build graph is a list of nodes (a pinned base image, a source directory, a command, an image, a file taken from a build step) and the outputs a release publishes:

format: ess-build/1
build: oracle-runtime
platforms:
- os: linux
architecture: amd64
nodes:
- id: base
kind: oci_base
reference: docker.io/library/alpine
digest: sha256:0000000000000000000000000000000000000000000000000000000000000000
- id: source
kind: source
path: .
destination: /src
- id: compile
kind: run
base: base
argv: [cp, /src/oracle, /usr/local/bin/oracle]
mounts:
- kind: input
from: source
target: /src
- id: runtime-image
kind: image
rootfs: compile
config:
entrypoint: [/usr/local/bin/oracle]
user: "10001"
- id: chart-file
kind: artifact
from: compile
path: /src/chart.tgz
outputs:
- name: app
release_unit: oracle-runtime
node: runtime-image
kind: oci_image
repository: registry.example/oracle
- name: chart
release_unit: oracle-chart
node: chart-file
kind: helm_chart
$ ess generate build compile --path build.yaml --out build-ir.json
oracle-runtime — 5 node(s), 2 output(s), compiled to build-ir.json
$ ess generate build graph --path build.yaml
flowchart LR
subgraph build_graph["oracle-runtime build graph"]
n0["base<br/><small>pinned OCI base</small>"]
...

Compilation refuses a secret mount that the graph does not declare under secrets:, and a cycle between nodes. build graph renders the same validated graph as Mermaid source.

Project BuildKit inputs​

$ ess generate project buildkit --ir build-ir.json --out buildkit
3 BuildKit file(s) projected to buildkit without executing them

The directory holds Dockerfile.ess, docker-bake.hcl and a copy of the build IR. Every base is pinned by digest, and a run step has no network unless it declares network: sandbox:

FROM docker.io/library/alpine@sha256:0000…0000 AS base

FROM scratch AS source
COPY [".","/src"]

FROM base AS compile
RUN --network=none --mount=type=bind,from=source,source=/,target=/src,ro ["cp","/src/oracle","/usr/local/bin/oracle"]

ess generate build execute compiles, projects and then runs Docker Buildx Bake over the result, with --target, --set, --push and --load passed to Bake. It is an explicit executor command: it needs Docker Buildx and, with --push, registry credentials. It was not run for this page.

Compile the runtime model​

The runtime model says which processes, containers and workloads run the component. It is bound by digest to three things: the compiled specification, the realization and the build IR.

format: ess-runtime/1
runtime: oracle-runtime
semantic_digest: sha256:7ca8e1ba…
realization_digest: sha256:527b40ae…
build_digest: sha256:8f82170b…
processes:
- name: server
image: app
containers:
- name: server
process: server
http_port: 8080
readiness_path: /ready
liveness_path: /live
config:
- name: log-level
environment: LOG_LEVEL
kind: optional
secrets:
- name: database-password
environment: DATABASE_PASSWORD
key: password
volume_mounts:
- volume: data
mount_path: /var/lib/oracle
workloads:
- name: oracle
components: [order-service, dispatch-service]
containers: [server]
replicas: 1
volumes:
- name: data
size: 1Gi
provided_endpoints:
- name: api
workload: oracle
container: server
scheme: http

Where each digest comes from:

FieldValue
semantic_digestthe compiled specification's digest. ess specify realization compile names it when a realization carries another one: SpecificationMismatch at specification: expected oracle v1 sha256:…
realization_digestthe realization_digest field of the realization IR that ess specify realization compile --out writes (Record a physical realization)
build_digestthe SHA-256 of the build-ir.json file build compile --out wrote, for example sha256sum build-ir.json
$ ess specify runtime compile --path runtime.yaml --system examples/oracle-fixture \
--realization realization.yaml --build-ir build-ir.json --out runtime-ir.json
oracle-runtime — 1 process(es), 1 container role(s), 1 workload(s), compiled to runtime-ir.json

Compilation checks that the digests match the supplied documents, that every selected component is placed in a workload, the replica bounds, and stateful storage. It does not check that a cluster can provision any of it.

Project a Helm chart​

$ ess generate project helm --ir runtime-ir.json --chart oracle --version 1.0.0 --out chart
5 Helm chart file(s) projected to chart without applying them
$ helm lint chart
1 chart(s) linted, 0 chart(s) failed

--chart is the chart's name and --version its SemVer, which is independent of the runtime's own release version. The chart holds Chart.yaml, values.yaml, values.schema.json and templates for the Services and workloads. A workload with volumes becomes a StatefulSet with volume claims and mounts, with a headless Service; each provided endpoint becomes a Service.

The chart carries no environment. Every image, secret and storage class is an empty slot a deployment fills in, and the service account defaults to default, so a fresh chart passes helm lint on its own:

serviceAccount:
name: default
images:
app:
repository: ""
digest: ""
config: {}
secrets:
database-password:
name: ""
key: "password"
endpoints: {}
workloads:
oracle:
replicas: 1
volumes:
data:
class: ""
size: "1Gi"

Scheduled workloads and pod settings​

format: ess-runtime/2 reads everything ess-runtime/1 does and compiles to ess-runtime-ir/2; a /1 document keeps its compiled bytes and digest, and refuses the keys below by name. A workload with a schedule: runs as a Kubernetes CronJob instead of a Deployment or StatefulSet:

format: ess-runtime/2
# …digests as above…
processes:
- name: runner
image: example.invalid/runner@sha256:0123…cdef # pinned by digest, rendered verbatim
containers:
- name: runner
process: runner
read_only_root_filesystem: true
resources:
requests: {cpu: 50m, memory: 64Mi}
limits: {memory: 128Mi}
secrets:
- name: credentials
mount: # a read-only secret volume; no environment variable
path: /etc/ingest
mode: "0400"
items:
- key: credentials.json
path: credentials.json
workloads:
- name: ingest-a
components: [ingest]
containers: [runner]
service_account_token: false
security: {run_as_user: 10001, run_as_group: 10001, fs_group: 10001, run_as_non_root: true}
schedule:
cron: "2-59/10 * * * *"
time_zone: Europe/Berlin # optional
overlap: forbid # forbid (default), replace or allow
successful_runs: 3 # optional history limits
failed_runs: 1
suspended: true # optional
start_deadline_seconds: 120 # optional
run_deadline_seconds: 540 # required: a run without one is refused as unbounded
retry_limit: 2 # optional
restart: never # never (default) or on_failure
volumes: # optional: a claim every run mounts
- name: state
size: 1Gi

A scheduled workload takes no replicas, no legacy storage, no provided endpoint and no readiness probe; each is refused naming the workload. Its named volumes become standalone PersistentVolumeClaims, sized and classed in the values file like a StatefulSet's. The pod settings (security, service_account_token, resources, read_only_root_filesystem, secret mounts and pinned images) are taken by Deployments and StatefulSets too, and so are these:

containers:
- name: runner
process: runner
readiness_tcp_port: 5432 # a tcpSocket probe; or readiness_path, not both
liveness_tcp_port: 5432
workloads:
- name: ingest-a
annotations: # pod annotations
example.invalid/team: ingest
affinity:
spread_across: kubernetes.io/hostname # no two pods of this workload on one node
spread: preferred # preferred (default) or required
node_labels: # every pod's node carries these labels
example.invalid/pool: batch

Resources and placement are not overridden per environment. A string the chart renders as written (an image reference, a path, a literal, an annotation) may not carry {{, which Helm would evaluate at install time.

The output directory also holds .ess-output/state.json, which records the files the projection owns, so running the same command again rewrites them in place (repeated generation).

What these projections do not do​

They do not build an image, push anything, install a chart or contact a cluster. A projected chart says nothing about whether its resources can be created or become ready. Building and publishing are the explicit executor commands build execute and release publish; installing is deployment reconcile.

Next: resolve a stack from the releases these files produce.