pi-foundry-runtime is the versioned contract product of this repo. The
skill-managed deployment expects a published runtime image referenced from the
user repo's thin Dockerfile:
ARG PI_FOUNDRY_RUNTIME_IMAGE=<acr>.azurecr.io/pi-foundry-runtime:<tag>
FROM ${PI_FOUNDRY_RUNTIME_IMAGE}
WORKDIR /app
COPY . /workspaceThe skill's bootstrap.mjs writes that Dockerfile but does not ship a
default image. You publish your own to an ACR your Foundry project can pull
from.
Dockerfile.runtime includes:
- Node Pi invocations host (
src/backend.mjs) — the single Foundry-facing process; servesGET /readinessandPOST /invocationsdirectly onPORT(default8088). There is no separate Python host or internal proxy port. piCLI (@earendil-works/pi-coding-agent)pi-foundryCLI (/usr/local/bin/pi-foundry) wrappingsrc/cli.mjs/app/src(contract.mjs, cli.mjs, backend.mjs, adapters, runtime helpers)
It deliberately does not include user Pi agent assets (.agents/skills,
prompts, MCP config, workspace). Those come from the user repo and are layered
into /workspace by the thin adapter Dockerfile bootstrapped into that repo.
Inside the image, two commands surface the runtime contract without booting the backend:
pi-foundry contract # full env / tier / reserved-prefix contract as JSON
pi-foundry doctor # validate current env; exit 1 on missing required vars (redacts secrets)
pi-foundry versionThe contract is the same data structure consumed by src/backend.mjs for
startup fail-fast validation and by .agents/skills/pi-foundry/references/contract.json
for the skill. The skill's JSON is regenerated from src/contract.mjs via
npm run emit:contract, so there is one source of truth.
PI_FOUNDRY_RUNTIME_IMAGE=pi-foundry-runtime:local npm run runtime:buildPI_FOUNDRY_RUNTIME_IMAGE=pi-foundry-runtime:local npm run runtime:smokeThe smoke test runs the container with PI_MOCK=1, mounts a throwaway
tempdir as /workspace (override with WORKSPACE=<path> to point at a real
agent workspace), polls /readiness, and posts a mock invocation.
Use az acr build directly against this repo and Dockerfile.runtime:
az acr build \
--registry <acr> \
--image pi-foundry-runtime:<tag> \
--file Dockerfile.runtime \
.Requires az login and AcrPush on the target registry.
Tag and push to the registry your Foundry project can pull from:
docker push <acr>.azurecr.io/pi-foundry-runtime:<tag>Or, for a non-ACR registry:
PI_FOUNDRY_RUNTIME_IMAGE=ghcr.io/<org>/pi-foundry-runtime:<tag> npm run runtime:build
docker push ghcr.io/<org>/pi-foundry-runtime:<tag>If users will rely on ACR remote builds via azd deploy, make sure the registry
holding the runtime image is one the Foundry agent identities have AcrPull
on. The skill's bootstrap.mjs --runtime-image <ref> is where that reference
lands; users can change it at any time by editing Dockerfile.
.github/workflows/runtime-image.yml builds Dockerfile.runtime and pushes to
ghcr.io/<owner>/pi-foundry-runtime. Triggers:
- Push a tag
v<X.Y.Z>→ publishes:<X.Y.Z>,:<X.Y>, and:latest. workflow_dispatch→ publishes:sha-<short>and:manual-<run-number>(never:latest).
git tag v0.1.0
git push origin v0.1.0Gotcha — the workflow file must already exist on the default branch before the
tag push. GitHub only runs a workflow for a tag if that workflow is present in
the commit the tag points at and reachable from the default branch. If you add
runtime-image.yml on a feature branch and push a tag from there, no run is
triggered. Fix: merge the workflow to main first, then (re)create and push
the tag:
git checkout main && git merge --ff-only <branch> && git push origin main
git tag -d v0.1.0 && git push origin :refs/tags/v0.1.0 # if the tag already existed
git tag v0.1.0 && git push origin v0.1.0Make the package public so Foundry (or anyone) can pull without auth: GitHub
→ your profile/org → Packages → pi-foundry-runtime → Package settings → Change
visibility → Public. Verify anonymously:
docker logout ghcr.io && docker pull ghcr.io/<owner>/pi-foundry-runtime:<tag>pi-foundry-runtime:<tag> is the contract surface. Breaking changes to env
contracts or SSE shape should bump the tag. The
skill's references/contract.json should be regenerated and the new image
referenced from bootstrap.mjs defaults (currently: user supplies on every
deploy; no in-skill default).