---
slug: agents/quickstart
title: Deploy an agent
kind: tutorial
surface: agents
summary: Deploy a working Aetherfy agent in one sitting — create an API key, run afy login, generate aetherfy.yaml with afy init, deploy with afy deploy, and read the agent's output with afy logs.
sources:
  - aetherfy-cli:cmd/login.go
  - aetherfy-cli:cmd/init.go
  - aetherfy-cli:cmd/deploy.go
  - aetherfy-cli:cmd/logs.go
  - aetherfy-control-plane:shared/config_parser.py
---

# 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](/cli).

## Get an Aetherfy API key

Create an API key at
[https://app.aetherfy.com/dashboard/settings/api-keys](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:

```bash
afy login
```

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

```bash
afy login --api-key afy_live_xxxxx
```

Confirm the CLI is talking to your Aetherfy account:

```bash
afy whoami
```

## Create the agent's files for Aetherfy

Make a directory and add three files. This example is a `service` agent — a
long-lived HTTP server — written with the Python standard library only, so
there is nothing to install and nothing that can break during the build.

```bash
mkdir hello-agent
cd hello-agent
```

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

```yaml
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 must listen on port **8080**, and Aetherfy health-checks it with
`GET /health` on that port:

```python
"""A minimal Aetherfy service agent: answers HTTP requests on port 8080."""

import json
import os
from http.server import BaseHTTPRequestHandler, HTTPServer

PORT = 8080


class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/health":
            self._respond({"status": "ok"})
            return
        self._respond(
            {
                "message": "hello from Aetherfy",
                "agent": os.environ.get("AETHERFY_AGENT_NAME", "unknown"),
                "region": os.environ.get("AETHERFY_REGION", "unknown"),
            }
        )

    def _respond(self, body):
        payload = json.dumps(body).encode("utf-8")
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(payload)))
        self.end_headers()
        self.wfile.write(payload)

    def log_message(self, fmt, *args):
        # Anything printed to stdout becomes this agent's logs on Aetherfy.
        print(fmt % args, flush=True)


if __name__ == "__main__":
    print(f"hello-agent listening on 0.0.0.0:{PORT}", flush=True)
    HTTPServer(("0.0.0.0", PORT), Handler).serve_forever()
```

`requirements.txt` — present but empty, because this example imports only the
standard library. Aetherfy installs from it if it lists anything:

```text
# No third-party dependencies: this agent uses the Python standard library only.
```

### 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` — and `-y` / `--yes` accepts all defaults, which
makes it usable in CI:

```bash
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:

```bash
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 the agent is created
under on this first run. 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 agents rename`.

Useful flags on the Aetherfy deploy command:

| Flag | Effect |
|---|---|
| `--detach` / `-d` | Return as soon as the upload finishes instead of waiting |
| `--agent` / `-a` | Deploy to a named agent, overriding the `name` in `aetherfy.yaml` |
| `--yes` / `-y` | Skip 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:

```bash
afy agents list
```

You can see the same thing in the dashboard at
[https://app.aetherfy.com/dashboard/agents](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](/agents/runs-and-logs) for
what each state means.

## 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:

```bash
afy logs hello-agent
```

Follow the logs as they arrive:

```bash
afy logs hello-agent --follow
```

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

```bash
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](/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:

```bash
afy agents diff hello-agent
```

`afy agents 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 step | Page |
|---|---|
| Every configuration field and the merge-patch rules | [/agents/aetherfy-yaml](/agents/aetherfy-yaml) |
| Turn this into a run-once task on a schedule | [/agents/scheduled-tasks](/agents/scheduled-tasks) |
| How a task reads input, exits, and is retried (it is not) | [/agents/task-contract](/agents/task-contract) |
| Give the agent API keys and other secrets | [/agents/secrets](/agents/secrets) |
| Deploy automatically on every push | [/agents/github](/agents/github) |
| The rest of the Aetherfy CLI surface | [/cli/agents](/cli/agents) |
