uniac.yaml, the CLI resolves it into a
deployable and ships container images to a remote project, and the platform
runs them. Three nouns carry the whole model.
Service
A reusable definition of one workload’s shape: where its container comes from, which environment variables, which start command. A service is never directly deployable — targeting one is an error. Its container comes from one of two sources: a prebuilt OCI image named byimage:, or a Docker build of your own tree pointed at by build:.
The manifest has the
source rule and the rest of the service fields.
uniac plan names the source it resolved for each instance the deployment
instantiates.
build: takes the build root, or an object refining root, context,
dockerfile, and target; the manifest
has the field table. plan checks those paths against your tree and nothing
else — a missing root, context, or Dockerfile fails offline, while the
Dockerfile’s contents stay unread until deploy. The build then runs on your
machine, against your local Docker daemon, targeting linux/amd64. What it
produces is pushed exactly like a pulled image, so nothing downstream knows
which source a service used.
type: stateful is the same definition capped at a single running instance,
for workloads like databases where two concurrent instances would corrupt
state. That cap is the precondition for durable storage: only a stateful
service may declare volumes:, and a type: service that tries fails with
service "db" declares volumes but is not stateful — durable storage needs the single-writer guarantee (`type: stateful`). A volume declares a name, a
size, and the path it mounts at; the manifest
has the keys.
A service declares no port. The project’s internal network is
port-transparent: other services reach yours by name, on whatever port your
process binds. Liveness is observed on the container process, never by
probing a port.
Deployment
An instantiation: a map of instance names to the definitions they draw from, plus the public exposure claimed for each instance. Resolving a deployment yields the deployable, the thing the platform consumes. The instance name is the identity. It is the deployed service’s name, its internal hostname, its platform identity, the name every${{...}} reference resolves against, and
the prefix on any volume it mounts — a definition’s data volume mounted by
the instance db is the disk db.data. The definition’s name is only a
local label for from:.
uniac deploy rejects a deployment instantiating more than one service, so a
multi-service system is several deployments in one manifest, deployed one at
a time and wired by
cross-deployment references.
Project
The remote destination, bound to your directory byuniac link
(.uniac/deploy.json). A project is a flat list of running services; the
platform holds no grouping above them. Deployments are a client-side
composition resolved on the way in, so the platform never sees one — what a
running service is serving is its own version number.
Volumes are project entities of their own, not properties of a container: the
whole-project uniac status lists each with its size and its own lifecycle
state — including whether a service is holding it.
What is running is not the manifest: deleting a resource from uniac.yaml
does not remove the service, and the CLI has no removal command — retire
services from the dashboard at uniac.ai, which also
covers scaling replicas and deleting projects.
The loop
plan is the verification loop. It needs no network, no credentials, and
no Docker daemon; it applies the manifest’s local rules — the ones
the manifest page tabulates, some
against the whole file and some, the on-disk existence of a build: source
among them, only against the deployment it resolves — and previews the
resolved deployable, naming it by a shortened digest; --json prints the
artifact itself. That same page lists
what waits for deploy — the rules
plan passes and deploy applies. Run it after every manifest edit — a
manifest that fails plan fails deploy with the same message, before
anything is sent anywhere.
Identical inputs yield an identical digest. The deployable is
content-addressed: reindenting YAML or reordering resources leaves the digest
unchanged, so a digest that moved means the declaration actually changed. It
keys the declaration only — editing the code or the Dockerfile behind a
build: leaves the digest identical, and that build runs on every deploy
regardless.
Ownership
The container environment is exactly the resolved declared
env —
nothing is injected beyond it, and nothing of Uniac is added to your image: a
build: service is your Dockerfile, built by your daemon, pushed as it came
out.
