Skip to Content
Agent computeDeploy an agent
Raw

Deploy an agent to Aetherfy

This tutorial takes you from an empty directory to a deployed Aetherfy agent that answers HTTP requests, then shows you its logs. It uses the afy CLI throughout and asks you to make no design decisions along the way.

You need the afy CLI installed before you start. Installation instructions are on /cli.

Get an Aetherfy API key

Create an API key at https://app.aetherfy.com/dashboard/settings/api-keys . Keys look like afy_live_xxxxx. Copy it when it is shown.

The Aetherfy CLI reads your key from its stored login, and it also honours the AETHERFY_API_KEY environment variable if one is set.

Sign in to Aetherfy with afy login

Run afy login and paste the key when prompted:

afy login

For CI or any non-interactive shell, pass the key directly:

afy login --api-key afy_live_xxxxx

Confirm the CLI is talking to your Aetherfy account:

afy whoami

Create the agent’s files for Aetherfy

Make a directory and add two files. This example is a service agent — a long-lived process that answers HTTP requests.

mkdir hello-agent cd hello-agent

aetherfy.yaml — the configuration Aetherfy reads. It must sit at the root of the directory you deploy:

name: hello-agent runtime: python3.12 type: service memory_mb: 256

Write the runtime version in full. runtime: python is not valid on Aetherfy and fails the deploy; the Python values Aetherfy accepts are python3.11, python3.12, and python3.13.

main.py — the entrypoint. main.py is the default entrypoint for every python3.* runtime on Aetherfy, so you do not need to declare it.

A service agent on a Python runtime exports a FastAPI application in a variable named app, and does not start a server itself. Aetherfy runs the server for you: it imports your entrypoint, serves the app it finds there on port 8080, and answers its own GET /health. This is the one contract the tutorial asks you to follow exactly — the next section explains what happens if you do not.

"""A minimal Aetherfy service agent: a FastAPI app exported as `app`.""" import os from fastapi import FastAPI # Aetherfy imports this module and serves THIS object. The name must be `app`. app = FastAPI() # Runs at import time, when Aetherfy starts the agent. Anything printed to # stdout becomes this agent's logs on Aetherfy. print("hello-agent starting", flush=True) @app.get("/") def index(): return { "message": "hello from Aetherfy", "agent": os.environ.get("AETHERFY_AGENT_NAME", "unknown"), "region": os.environ.get("AETHERFY_REGION", "unknown"), } @app.post("/echo") def echo(body: dict): """Reads a JSON request body and returns it. Aetherfy adds no routing of its own — every route you declare on `app` is served as you wrote it.""" return {"you_sent": body}

Both routes are yours. Aetherfy serves whatever you declare on app and adds only GET /health, so the request shapes, paths and status codes are the framework’s business and not Aetherfy’s — reach for FastAPI’s or Express’s own documentation when you need path parameters, validation or custom responses.

There is no second file to write. Aetherfy installs FastAPI and uvicorn into every Python service image itself, so this example needs no requirements.txt at all. Add one when your agent has dependencies of its own; Aetherfy installs from it when it is present.

The Aetherfy service contract, and what breaks without it

The export above is not a style preference. Aetherfy imports your entrypoint as a module and looks for one specific name:

Runtime familyWhat Aetherfy importsWhat you export
python3.11, python3.12, python3.13your entrypoint moduleapp — a FastAPI application
node20, node22, node20-ts, node22-tsyour entrypoint moduleapp — an Express application, via module.exports = { app } or a default export
bunyour entrypoint moduleapp — an Express application
dockerfilenothing — you own the containersee /agents/dockerfile

Three consequences follow, and the third is the one that costs an afternoon:

  • Do not start a server yourself. Aetherfy runs uvicorn (or Node) against your app. Code guarded by if __name__ == "__main__": never executes, because Aetherfy imports the module rather than running it as a script.
  • Do not implement GET /health. Aetherfy answers that path itself, in front of your app, so a /health route of your own is never reached.
  • A missing app export fails the deploy, and says so. Aetherfy checks for the export before building, and checks again at startup: an entry point that does not load is never reported healthy, so the deploy stops and its reason carries your interpreter’s own error message. Read the deploy’s reason first — it names the file and what went wrong with it.

This contract applies to service agents only. A job agent is executed as a plain script, exports nothing, and is covered by /agents/task-contract.

Letting afy init write the configuration instead

afy init inspects a directory, detects the project type, and generates aetherfy.yaml for you. Every prompt it asks has a matching flag — --name, --runtime, --entrypoint, --type, --region, --memory, --keep-alive, --workspace, and --schedule. Two of those are boolean toggles rather than value flags: --keep-alive and --workspace answer yes/no prompts and take no argument. -y / --yes accepts all defaults, which makes it usable in CI:

afy init --name hello-agent --runtime python3.12 --type service --yes

-f / --force overwrites an existing aetherfy.yaml. Note that -y does not imply --force, so afy init -y will not clobber a file you already have.

Deploy the agent to Aetherfy

From inside the directory, run:

afy deploy

afy deploy uploads the current directory, builds an image, launches machines, waits for completion, and streams status as it goes. It prompts once to confirm cost before it starts.

The name: field in aetherfy.yaml is what this deploy targets. With no --agent flag, Aetherfy’s CLI reads name from the file — hello-agent here — and that is the agent the deploy goes to, and the name it is created under if you consent to the prompt below. If you pass neither, the command stops with Agent name not found. Use --agent flag or set 'name' in aetherfy.yaml. Changing name later retargets the deploy at a different agent rather than renaming this one; renaming is afy rename.

The first deploy offers to create the agent

Your code deploys to an agent: a named record on Aetherfy that owns the deployments, the machines and the logs. hello-agent has no such record yet, so this first deploy comes back reporting it missing and asks before going further:

Agent 'hello-agent' doesn't exist. Create it as service/python3.12 (from aetherfy.yaml)? [y/N]

Answer y and the CLI creates the agent, then finishes the same deploy into it. The service and python3.12 in the question are read from the aetherfy.yaml you just wrote — neither is guessed. The question comes before the cost confirmation, because the agent has to exist before Aetherfy can price it.

Creating is never automatic. The prompt defaults to No, so pressing Enter declines it and the deploy ends the way it always has, with nothing created on Aetherfy and nothing deployed:

Error: Deployment failed: [404] Agent 'hello-agent' not found (AGENT_NOT_FOUND)

Anywhere without a terminal — CI, a script, a pipe — the CLI does not ask at all, and --create is the only way to consent. A pipeline deploying a brand-new agent passes it alongside --yes, which answers the separate cost prompt:

afy deploy --create --yes

Those two flags are separate consents and --yes never implies --create. /cli/deploy has the complete rules, including what --yes does to this question at a terminal and why a UUID target is never created.

You can also create the agent up front, once, and leave --create out of the deploy entirely — see /cli/agents for its flags:

afy create hello-agent --runtime python3.12 --type service

Useful flags on the Aetherfy deploy command:

FlagEffect
--detach / -dReturn as soon as the upload finishes instead of waiting
--agent / -aDeploy to a named agent, overriding the name in aetherfy.yaml
--createCreate the agent from aetherfy.yaml’s type and runtime if it does not exist. Required to create without a terminal
--yes / -ySkip the cost confirmation prompt

Aetherfy does not upload everything in the directory. Files matching .afyignore are excluded, along with built-in defaults including .git, .env, __pycache__, node_modules, venv, .DS_Store, and *.log.

Confirm the Aetherfy agent is running

When the deploy finishes, the deployment reaches the active state and the agent’s status becomes running. Check it from the CLI:

afy list

You can see the same thing in the dashboard at https://app.aetherfy.com/dashboard/agents .

If the deploy failed instead, the deployment ends in the failed state and the error is recorded against it. List the history with afy deployments hello-agent to read the failure, and see /agents/runs-and-logs for what each state means.

Call the Aetherfy agent

A deployed service agent has its own address, shown by afy list and on the agent in the dashboard:

curl -s https://hello-agent-k3m7x2.aetherfy.dev/
{"message": "hello from Aetherfy", "agent": "hello-agent", "region": "iad"}

The POST /echo route from main.py is served exactly as written:

curl -s https://hello-agent-k3m7x2.aetherfy.dev/echo \ -H "Content-Type: application/json" \ -d '{"hello": "world"}'
{"you_sent": {"hello": "world"}}

No API key. Your agent’s address is public and serves whoever calls it — the key authenticates you to Aetherfy, not your callers to your agent. If your agent needs to authenticate its callers, that is a route you write.

The address is fixed at your first deploy

The six characters after the name are drawn at random the first time the agent deploys, and the address never changes afterwards — including when you rename the agent. An agent renamed hello-agent to greeter keeps serving at hello-agent-k3m7x2.aetherfy.dev.

That is deliberate: a rename that moved the address would break every integration, link and webhook already pointing at it. The practical rule is rename before your first deploy and the address follows the new name; rename after and it stays as it was.

The name: in your aetherfy.yaml does move, and Aetherfy’s CLI moves it for you: run afy rename from the project directory and it rewrites that one key, because afy deploy resolves its target from it. Run the rename from somewhere else and nothing on disk is touched — the command then tells you which file to update by hand. Full behaviour is on /cli/agents.

There is no way to issue a second address for the same agent, and there will not be one — the address is derived from the agent rather than looked up, so one agent has exactly one address. job agents have none at all: they run to completion and exit, so nothing is listening.

Read the Aetherfy agent’s logs

Everything your program writes to stdout and stderr becomes the agent’s logs on Aetherfy. The startup line from main.py above appears here:

afy logs hello-agent

Follow the logs as they arrive:

afy logs hello-agent --follow

Show more history, or narrow to a time window and a severity:

afy logs hello-agent --tail 200 --since 1h --level ERROR,WARN

Aetherfy retains logs for 7 days. The full flag list, the per-line and volume caps, and the dropped-line marker are documented on /agents/runs-and-logs.

Preview a change before deploying it to Aetherfy

Aetherfy applies aetherfy.yaml as a merge patch, so fields you leave out keep their existing values rather than resetting. Before a deploy you are unsure about, preview exactly what would change and what would be preserved:

afy diff

afy diff exits non-zero when there are changes, which makes it usable as a CI gate that fails a pipeline on unintended configuration drift.

What to do next with Aetherfy

Next stepPage
Every configuration field and the merge-patch rules/agents/aetherfy-yaml
Turn this into a run-once task on a schedule/agents/scheduled-tasks
How a task reads input, exits, and is retried (it is not)/agents/task-contract
Give the agent API keys and other secrets/agents/secrets
Deploy automatically on every push/agents/github
The rest of the Aetherfy CLI surface/cli/agents
Last updated on