You bring three artifacts. The pipeline returns an attested, contained deployment your buyer’s security team can verify without trusting you or Ember.
WHAT YOU BRING
name@sha256:… only; a tag can move after review, a digest
cannot. The image must be linux/amd64 — on Apple Silicon
that means docker buildx build --platform linux/amd64, or
the CVM refuses to boot it. It must be pullable without credentials (see
PUSHING YOUR IMAGE below), and it must boot straight to serving
with env-only configuration — there is no command or args field, so an entrypoint that needs a
subcommand means rebuilding the image.
env as name-value pairs — and those values are
public: they are baked into the measured config your buyer
verifies, so nothing sensitive belongs there. Secrets go in
secretEnv as names
only, never values — the values are read from your shell env at deploy and sealed to the
enclave. A secret value written anywhere in ember.json
is a secret disclosed.
"egress": []) is valid: the deployment gets no
network exit at all — it answers on its ports and reaches nothing.
The whole submission is one file, ember.json:
{
"name": "acme-rag",
"spec": {
"image": "ghcr.io/acme/rag@sha256:9f2c…",
"env": { "MODEL": "claude-sonnet-5" },
"secretEnv": ["ANTHROPIC_API_KEY"],
"ports": [8080],
"egress": [{ "host": "api.anthropic.com", "port": 443 }],
"resources": { "cpus": 8, "memoryMb": 16384, "diskGb": 100 }
}
}
ports lists where your container serves; each one is published
at an attested HTTPS endpoint on the gateway (port 80/443 at
https://<app-id>.<gateway>, other ports with a
-<port> suffix). An optional
storagePath mounts a persistent volume at that path.
resources sizes the confidential VM and is optional —
omit it and you get the smallest tier: 1 vCPU / 2 GB RAM / 20 GB disk (fine for a typical
single-container web app — a paid CVM bills hourly whether or not it’s busy, so the default
errs cheap). Size for your whole stack: the VM runs
your single image plus the policy proxy, and the disk holds your image, model weights, and data.
Limits today: 16 cpus, 32768
memoryMb, 200
diskGb (need more? ask). Sizing is set at create and is
not part of the measurement — changing it later means a new deployment.
WHAT THE PIPELINE DOES
The pipeline renders a contained deployment: your image runs in an attested confidential VM whose only network exit is a policy proxy — with an empty manifest, no exit is rendered at all — and your egress manifest is inlined into the deployment measurement. That measurement is approved on-chain by the governing smart contract account before anything boots. Secrets are sealed; silicon↔chain evidence is captured.
A config edit cannot widen your access. Any change to the image or egress list produces a new measurement, which requires a new on-chain approval.
GET SET UP
npm install -g @ember-sovereignty/provisioner export EMBER_API=https://provisioner-production-b5b7.up.railway.app ember login # prints a URL + code; approve it in the console
ember login leaves an org credential at
~/.config/ember/credentials.json. Revoking that key in the
console cuts the CLI’s access with it.
If your org pins a registry cooldown (npm’s
min-release-age), a fresh CLI release can sit inside the
window (npm error No versions available); install the exact
tarball instead — npm view reads the registry directly,
so it always names the latest one:
npm install -g "$(npm view @ember-sovereignty/provisioner dist.tarball)"
GIVE YOUR AGENT THE SKILL
Everything on this page is written up as an agent skill — the
ember.json schema, the env-vs-secrets rule, the command
sequences, the debugging playbook. If you work with
Claude Code, install it once and your agent knows the whole
pipeline in every project:
mkdir -p ~/.claude/skills/ember-deploy curl -fsSL https://embersovereignty.com/onboarding/SKILL.md -o ~/.claude/skills/ember-deploy/SKILL.md
That’s a personal skill — it rides along in every repo you open. To pin it to one project instead,
put the same file at .claude/skills/ember-deploy/SKILL.md
in the repo. Re-run the curl any time to pick up the
latest version, or just read it yourself:
SKILL.md →
PUSHING YOUR IMAGE
The deployment pipeline pulls your image anonymously, by digest. If you don’t already have an org registry with public pulls, use one of these — both are free for public images and take minutes to set up:
docker login ghcr.io with a personal access token that has
write:packages, push
ghcr.io/you/app:tag, then set the package’s
visibility to public in its settings (new packages default to
private, which the pipeline can’t pull). Know the trap: the visibility toggle is
web-UI-only — a registry token can push but cannot flip it, and
there is no API. If all you hold is a token, publish a seed tag from a GitHub Actions workflow in a
public repo using the workflow’s own
GITHUB_TOKEN — a package first published that way inherits
the repo’s public visibility — then push your real image to the same name with your token.
docker login, push, done.
Avoid throwaway registries with expiring tags (ttl.sh and friends): your release is pinned to the digest, and when the image expires — ttl.sh deletes after 24 hours — the pinned digest becomes unpullable and the deployment can no longer be recreated. Private registries via sealed pull credentials are on the roadmap; today the image must be public.
The push prints the digest to pin (sha256:…); you can always
re-read it with
docker buildx imagetools inspect ghcr.io/you/app:tag — which
also confirms the manifest carries linux/amd64.
THE WALKTHROUGH
Onboarding runs through the ember CLI:
ember init # scaffold ember.json, the index card ember deploy # secrets from your shell env, sealed ember status # state + attestation evidence ember egress add api.example.com # a governed ceremony, not a config edit ember release name@sha256:… # a new version, the same ceremony ember verify # quote + chain, trusting no one
ember deploy reads
ember.json from the current directory, takes secret
values from your shell env, and boots the first version. Anything
your image fetches at runtime — model weights included — must be on the egress manifest or the proxy
denies it.
The create ceremony answers in a minute or two; a large image then keeps
pulling and booting for tens of minutes while the CLI follows the
status (Ctrl-C is safe — ember status resumes the view). A
dropped connection can’t strand a deploy: rerun
ember deploy and it resumes tracking the ceremony that
already ran, by name. Names are lowercase-kebab, 5–40
characters; ember delete <id> removes a record
that never provisioned.
Each port you declare is published at an attested HTTPS endpoint on
the confidential-VM gateway: port 80/443 at
https://<app-id>.<gateway>, other ports with a
-<port> suffix. The exact URLs appear on the deployment
in the console’s Releases view and in
ember status once the app is running — that’s the
address you smoke-test and hand to your buyer.
One expectation worth setting: the first deploy’s measurement is allowlisted as part of creation — the signing ceremony starts with your first change.
Env changes ride a release too. The whole
ember.json spec is re-measured on every governed change, so
to change plain env or secret-env names, edit ember.json
and ship it with your next ember release (the CLI has no
env-only verb yet, and a release needs a new digest — rebuilding the same image with a bumped label is
enough). Secret values are the exception twice over: they are sealed from your shell at deploy and
never re-read, so rotating one is not yet self-serve —
contact us.
ember release and
ember egress add return
pending-approval — nothing changes until the ceremony
completes. In the console’s Releases view you sign the
endorsement and approve the new measurement with your security key (gas is sponsored; you fund nothing).
The pipeline commits the moment the approval lands on-chain. When the buyer’s institution holds the
governing account, the same ceremony runs with their approval instead of yours.
Your security key is that ceremony’s signing authority, and nobody — Ember included — can recover it if it’s lost. Register it in a synced passkey provider (iCloud Keychain, Google Password Manager, a password manager) so losing a device never means losing the key; a 1-of-1 org with a lost key can no longer approve releases.
NO RUNTIME LOGS — DEBUG LOCALLY FIRST
Runtime logs are unavailable by design — nothing readable leaves
the enclave, so there is no ember logs today (a bounded,
off-by-default version is on the roadmap). Between “running” and a correct HTTP response, the
only window you have is what your app serves. Budget for that before you deploy:
docker run — same env names, same entrypoint — and
confirm it boots to serving on its own. If it doesn’t serve locally, it won’t serve in the
CVM, and there you won’t see why. Exercise every route you’ll actually demo, not just the
landing page — a page that renders can hide an API route that crashes on first use (a
production-bundle-only failure is the classic shape), and each fix you discover in production costs a
full release ceremony.
ember status is deployment state plus attestation
evidence — it says the enclave is running the approved measurement, not that your app inside it is
healthy. ember verify checks quote and chain. App health
is only ever the attested HTTPS endpoint itself. And remember egress is fail-closed: a dependency
missing from the manifest looks like a hang from inside, so check the manifest before suspecting a
crash.
docker run suggested —
in the CVM runtime that inheritance is where multi-service bundles most often break, and it fails as
a silent crash-loop you can’t see. Have PID 1 capture its env to a file first (e.g.
export -p > /run/app.env) and have every service
source it before starting. Cheap locally, and it saves a blind release ceremony per guess in
production.
IF YOUR IMAGE DOESN’T FIT YET
The one-image, env-only model is deliberate — it’s what makes the measurement meaningful — and most real products need a small adaptation to meet it. The common shapes:
FROM your-image +
ENTRYPOINT ["your-bin","serve"], push it somewhere publicly
pullable (see PUSHING YOUR IMAGE above), and pin the new digest.
secretEnv and are
sealed to the enclave.
PROVE IT, DON’T PROMISE IT
ember verify fetches the live attestation quote, hashes the
measured workload it carries, and compares that hash to the approved-measurement list on the blockchain —
the chain read is direct, trusting neither your infrastructure nor Ember’s records. By default
the quote comes from the deployment record; for a fully independent check, set
PHALA_CLOUD_API_KEY (a free Phala Cloud account) and the quote
is fetched live from the CVM platform instead.
The full method is in the technical whitepaper → If a CAIQ-style questionnaire is in your deal thread, we’ve answered it →
START ONBOARDING
Bring the image you’d ship and the hosts it needs — the steps above are self-serve, end to end. Want a guided first deployment instead? A briefing gets you one.
Request a briefing