Rendering a Baseline from git
A Baseline either declares its services inline in spec.services or points at a
repository in spec.source. Given a source the operator clones the repository. It renders
the deployment definition and derives the service list. Inline entries then act as
per-service overrides by name.
spec:
application: shop
source:
url: https://git.acme.internal/retail/shop.git
ref: release/2026.09 # branch, tag or commit sha
path: deploy/chart
kind: helm # helm | kustomize | manifests
secretRef: {name: shop-git}
helm:
valuesFiles: [values-prod.yaml]
values: {logLevel: info}
pollInterval: 5m # optional; re-resolve the ref this often
services:
- name: shop-postgres # override: rendered as StatefulSet, keep it shared
shared: true
Credentials Secret
| Key | Use |
|---|---|
token |
HTTPS token or password. username is optional and defaults to nezal. |
ssh-privatekey |
SSH key. known_hosts is required. passphrase optional. |
ca.crt |
PEM bundle for a private certificate authority on the git server. |
The operator contacts nothing but this git server.
Formats
- helm: chart at
path. Dependencies must be vendored undercharts/(runhelm dependency buildand then commit). Hooks are skipped with a warning and so are tests and NOTES. - kustomize: kustomization at
path. Bases must be inside the repository. URL references andhelmChartsare rejected. - manifests: every
.yamland.ymland.jsonfile anywhere underpath.
Docker Compose is not a supported format. A compose file describes a laptop and leaves out most of
what a cluster needs. Converting one meant guessing. Describe the application as a Helm
chart or a Kustomize overlay or plain manifests. A Baseline still of kind compose reports
that on its status and deploys nothing new.
How Kubernetes objects become services
Each Service that selects a Deployment or StatefulSet becomes one nezal service named
after the Service. Image and command and args and env come from the first container. So do
resources and replicas and the working directory and the numeric uid. Port protocol comes
from appProtocol or the nezal.dev/protocol annotation and is otherwise http. Well-known
database ports default to tcp instead. The annotation nezal.dev/shared: "true" marks a
shared dependency and so does being a StatefulSet.
Health checks
A readiness probe in the source is carried over.
Nothing is ever rendered as a liveness probe. A liveness probe restarts the pod. A check written for production or for a laptop will restart a half-built preview environment forever. A readiness probe only decides whether traffic is sent and that is recoverable. When a workload declares only a liveness probe that probe becomes the readiness check and a warning says so.
Without any declared check nezal infers a TCP probe on the service's first port. The first
request after nezal up then does not reach a process that has not started listening yet.
Volumes and mounted files
| In the source | Becomes | Note |
|---|---|---|
emptyDir |
emptyDir |
Ephemeral. Nothing survives a restart. |
configMap or secret |
file mount | The object must already exist in the namespace. |
persistentVolumeClaim |
emptyDir with a warning |
Mark the service shared if it owns real data. |
Two rules behind that table. A preview environment is rebuilt from the repository. State that survived a rebuild would make it irreproducible so nezal never provisions persistent storage for one. And nezal never copies secret material out of a git repository into the cluster: a mounted Secret is one that an admin creates.
A ConfigMap or Secret mounted as a whole directory is not carried over because its keys are
only known at deploy time. List them under items in the source or use subPath.
Every rendered pod has a read-only root filesystem so a service that writes outside a
declared scratch path will fail. Add an emptyDir mount in the source for the paths it needs.
Ordering
Kubernetes has no start order. Everything starts at once and a service restarts until its dependencies answer. That usually converges within seconds. A service that reads its database once at startup and exits rather than retrying will crash-loop instead. Make startup connections retry.
What to look at
status.source: the commit and the service count and when it was resolved and rendered.status.resolvedServices: the effective list after overrides.status.renderWarnings: what was dropped or guessed.- Conditions
SourceResolvedandRenderedwith reasonsResolvingandRenderingandRenderedandRenderedWithWarningsandResolveFailedandRenderFailed.
Failures retry every two minutes. Rendered output is cached by commit so restarting the operator or re-applying the same Baseline does not re-render.