agenthost.yaml
Four settings a project commits in its own tree to control its build. runtime, install, build and start, what each runtime does with them, and what happens when one is wrong.
The runtime conventions are fixed on purpose: one entrypoint, one dependency file, no start command and no port in a tool call. That fits source an agent just wrote. It is too rigid for a repository that already exists, which is usually the reason for connecting one.
agenthost.yaml is the step past our conventions for when they don't quite fit. Commit
it at the root of your project and it replaces individual build steps while agenthost keeps the base
image, the port wiring and the layering:
runtime: node:22
install: npm ci --omit=dev
build: |
npm run build
node scripts/postbuild.js
start: node dist/server.jsEvery key is optional. An empty file is a project that says nothing, and the conventions apply
unchanged. agenthost.yml is accepted too, and read the same way.
It applies to every source, not just GitHub.
Inline files, a streamed upload and a cloned repository all arrive at the build as the same tree,
so a committed build file is honoured whichever way the source got there.
The four settings
| Setting | What it does |
|---|---|
runtime | The runtime to build on, as "<language>[:<version>]". Same syntax as the tool argument: node:22, python:3.13, php:8.4, static. |
install | Replaces the runtime's dependency-install command. |
build | Replaces the runtime's build command. |
start | Replaces the command the container runs. |
Every value is a shell command, run from the project root. npm run build,
./scripts/build.sh and a ten-line block are all the same kind of thing. A committed script is
reached by path like any other command, and the executable bit travels with the file, so
./scripts/build.sh works without a chmod step of yours.
Use a YAML block for several lines:
build: |
npm run build
node scripts/postbuild.jsA multi-line command runs with set -e, so the first failing line fails the build. Without that the
exit status would be the last line's, and a failed install followed by a successful build would ship
stale output as a green deploy.
What each runtime does with it
| Runtime | install replaces | build replaces | start replaces |
|---|---|---|---|
node | npm ci || npm install | npm run build --if-present | npm start |
python | pip install --no-cache-dir -r requirements.txt | nothing runs by default | gunicorn app:app -b 0.0.0.0:8000 |
php | composer install when composer.json is present | nothing runs by default | not accepted |
static | not accepted | not accepted | not accepted |
A php app is served by Apache from its document root and a static site is files served by nginx.
Neither has a start command of ours to replace, and a static site has no build to apply steps to, so
setting those fails the deploy rather than quietly ignoring what you committed. That is a fixed limit
of those two runtimes, not something to work around — agenthost ignores any Dockerfile in your tree
and always builds from its own recipe.
The port is not one of the settings. It stays fixed per runtime and your process must listen on it:
3000 for node, 8000 for python, 80 for php and static. Node and Python builds also get PORT
in the environment, so node dist/server.js reading process.env.PORT lands on the right one.
What runtime decides
runtime in the file is taken ahead of anything inferred from your file names, and it is re-read on
every deploy. Commit a change to it and the next push builds on the new one, with no tool call to
re-run.
An explicit runtime argument on create_app or deploy_app still wins over the file. That order
is deliberate: the file is what the project says about itself, and the argument is someone
overriding it for this deploy.
See Choosing a version for what a version pins and which ones are supported.
A custom install costs the dependency cache
By default the build copies your manifest first, installs from it, and only then copies the rest of the tree. An unchanged dependency set reuses the install layer, so most deploys skip it.
A custom install may read files the manifest doesn't name (a lock file, a second requirements file,
a workspace layout), so the whole tree is copied before it runs. That means it runs on every deploy.
It is usually a fair trade for a build that works at all, but it is worth knowing why a repository
with an install line builds slower than one without.
When something is wrong
The file is strict, and every failure names the file and stops the deploy. A build that quietly ignored a setting someone committed and believed would be worse than a failed one.
| What you wrote | What happens |
|---|---|
| Invalid YAML | The deploy fails with the parser's own message, including the line and column. |
| A key that isn't one of the four | Rejected by name, with the supported settings listed. A misspelling is not silently dropped. |
| A value that isn't a command, or is empty | Rejected, with the block syntax shown for multi-line commands. |
| A document that isn't a mapping (a list, a bare string) | Rejected, with an example of the shape it expects. |
install, build or start on static, or start on php | Rejected, naming the setting that runtime doesn't accept. |
A committed Dockerfile is ignored.
agenthost always builds from its own generated recipe. A Dockerfile in your tree is stripped
before the build, so it never takes over — install, build and start from your agenthost.yaml
are what shape the build. runtime still counts, because it decides which port traffic is routed
to.
When four settings aren't enough
install, build and start are the whole of what you can customize. Anything beyond them —
system packages, a second process, a language we don't have a runtime for, a multi-stage build of
your own — isn't supported. agenthost always builds from its own recipe, and a Dockerfile committed
to your tree is ignored rather than used. If your project genuinely needs one of those, agenthost
isn't the right home for it.
Where to go next
Choosing a version
Append a version to the runtime as "<language>:<version>", or leave it off for a supported default.
Environment variables
Settings and secrets your app reads at run time. You set them yourself in a browser at /secrets-manager, so a secret never passes through your agent — which can only ever see their names.