> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agno.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Fly.io Reference

> Commands, customization, environment variables, and troubleshooting for the Fly.io template.

Fly app names are global, so `up.sh` generates a unique name (`agentos-<suffix>`), records it in `fly.toml`, and pairs it with a Postgres app named `<app>-db`. Later `fly` commands read the app name from `fly.toml`, so no `--app` flag is needed.

## Manage

| Task                | Command                                                                                       |
| ------------------- | --------------------------------------------------------------------------------------------- |
| Deploy code changes | `./scripts/fly/redeploy.sh`                                                                   |
| Sync env variables  | `./scripts/fly/env-sync.sh` (defaults to `.env.production`; pass `.env` to sync that instead) |
| Tail logs           | `fly logs`                                                                                    |
| Resize the machine  | Edit `[[vm]]` in `fly.toml`, then `./scripts/fly/redeploy.sh`                                 |
| Tear down           | `./scripts/fly/down.sh` (add `--yes` to skip the confirmation)                                |

The platform runs one machine by design. Both deploy scripts pass `fly deploy --ha=false` because the Fly default creates two machines, which doubles cost and runs two in-process schedulers double-firing every cron. `fly.toml` keeps the machine warm with `auto_stop_machines = "off"` and `min_machines_running = 1`; with scale-to-zero, scheduled jobs silently stop firing.

## Production auth

Token-Based Authorization is on by default. Without a `JWT_VERIFICATION_KEY` or `JWT_JWKS_FILE`, the app refuses to serve traffic in production. The platform's job is to keep your data private, so the safe default is refuse to start.

Token-Based Auth gives you three things:

1. **No public access.** The server rejects requests without a valid token.
2. **Per-request identity.** Middleware parses the token and extracts the `user_id`, `session_id`, and custom claims. Each request is tied to a user and session, giving you auditability and traceability.
3. **Granular permissions.** User tokens can run an agent and view their own sessions. Admin tokens read everyone's sessions and test any agent.

To opt out (not recommended), set `authorization=False` in `app/main.py` and redeploy. Use this only inside a private network behind another auth layer. Without it, anyone who guesses your Fly URL can access your platform.

## Customize

<AccordionGroup>
  <Accordion title="Add an agent">
    Ask your coding agent to run `/create-new-agent`, or do it by hand. Create `agents/my_agent.py`:

    ```python theme={null}
    from agno.agent import Agent

    from app.settings import default_model
    from db import get_postgres_db

    INSTRUCTIONS = """\
    What the agent does, which tools it uses, the rules to follow when answering.
    """

    my_agent = Agent(
        id="my-agent",
        name="My Agent",
        model=default_model(),
        db=get_postgres_db(),
        instructions=INSTRUCTIONS,
        enable_agentic_memory=True,
        add_datetime_to_context=True,
        add_history_to_context=True,
        num_history_runs=5,
    )
    ```

    Register it in `app/main.py`:

    ```python theme={null}
    from agents.my_agent import my_agent

    agent_os = AgentOS(
        ...
        agents=[agent_builder, platform_manager, web_search, my_agent],
    )
    ```

    Local containers hot-reload on save. For production, run `./scripts/fly/redeploy.sh`.
  </Accordion>

  <Accordion title="Change the model">
    `app/settings.py` defines `default_model()`, used by every agent. Change it in one place:

    ```python theme={null}
    from agno.models.anthropic import Claude

    def default_model():
        return Claude(id="claude-sonnet-5")
    ```

    Add `anthropic` to `pyproject.toml`, set the provider key in your env, and regenerate pins:

    ```bash theme={null}
    ./scripts/generate_requirements.sh
    ```

    Rebuild locally with `docker compose up -d --build`. For production:

    ```bash theme={null}
    ./scripts/fly/env-sync.sh
    ./scripts/fly/redeploy.sh
    ```
  </Accordion>

  <Accordion title="Add tools">
    Agno ships 100+ toolkits. See [Toolkits](/tools/toolkits/overview).

    ```python theme={null}
    from agno.tools.slack import SlackTools

    my_agent = Agent(
        ...
        tools=[SlackTools()],
    )
    ```
  </Accordion>

  <Accordion title="Add dependencies">
    1. Edit `pyproject.toml`.
    2. Regenerate pins: `./scripts/generate_requirements.sh` (add `upgrade` to refresh every pin).
    3. Rebuild locally with `docker compose up -d --build`, or redeploy with `./scripts/fly/redeploy.sh`.
  </Accordion>

  <Accordion title="Enable Slack">
    Set both variables in your env file:

    ```bash theme={null}
    SLACK_BOT_TOKEN=xoxb-...
    SLACK_SIGNING_SECRET=...
    ```

    Sync with `./scripts/fly/env-sync.sh`. The interface activates automatically and routes messages to Agent Builder; change the `agent=` argument in `app/main.py` to point at another agent. See [Slack setup](/deploy/interfaces/slack/overview).
  </Accordion>

  <Accordion title="Toggle scheduled workflows">
    The deployment check runs daily by default (`ENABLE_DEPLOY_CHECK=True`); it is deterministic and free. Scheduled evals are off by default (`ENABLE_SCHEDULED_EVALS=False`) because they use model calls. Both workflows stay runnable on demand regardless.
  </Accordion>

  <Accordion title="Enable pgvector for knowledge bases">
    Fly's stock `postgres-flex` image does not ship pgvector, so sessions and memory work but knowledge bases (RAG) fail at `CREATE EXTENSION` time. The fix is a two-line Dockerfile:

    ```dockerfile theme={null}
    FROM flyio/postgres-flex:17
    RUN apt-get install -y postgresql-17-pgvector
    ```

    Set `FLY_PG_IMAGE` to that image before running `./scripts/fly/up.sh`. The image applies when the Postgres cluster is created; `up.sh` reuses an existing cluster, so switching later means tearing down with `./scripts/fly/down.sh` and provisioning fresh.
  </Accordion>
</AccordionGroup>

## Format, validate, and run evals

The format, validate, and eval scripts run on the host and need a venv. Set it up once:

```bash theme={null}
./scripts/venv_setup.sh
source .venv/bin/activate
```

| Task                | Command                       |
| ------------------- | ----------------------------- |
| Format              | `./scripts/format.sh`         |
| Lint and type-check | `./scripts/validate.sh`       |
| Run smoke evals     | `python -m evals --tag smoke` |

`./scripts/mcp_check.sh` runs inside the container, so it needs no venv.

## Environment variables

| Variable                                                      | Required   | Default                 | Description                                                                                                                                                                                                                                                                                                                                                  |
| ------------------------------------------------------------- | ---------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `OPENAI_API_KEY`                                              | Yes        | -                       | Models and embeddings.                                                                                                                                                                                                                                                                                                                                       |
| `RUNTIME_ENV`                                                 | No         | `prd`                   | `dev` disables JWT. Compose sets it for local. Never put it in an env file that syncs to Fly, or production deploys unauthenticated.                                                                                                                                                                                                                         |
| `JWT_VERIFICATION_KEY`                                        | Production | -                       | Public key from os.agno.com. Quote the value so the multi-line PEM parses as one variable.                                                                                                                                                                                                                                                                   |
| `JWT_JWKS_FILE`                                               | Production | -                       | Path to a JWKS file. Alternative to `JWT_VERIFICATION_KEY`.                                                                                                                                                                                                                                                                                                  |
| `MCP_CONNECT_SECRET`                                          | No         | generated by `up.sh`    | OAuth consent secret (16+ chars) for connecting claude.ai and ChatGPT to `/mcp`. `up.sh` generates one on deploy and writes it to `.env.production`.                                                                                                                                                                                                         |
| `AGENTOS_MCP_SIGNING_KEY`                                     | No         | generated               | Optional high-entropy signing-key material (32+ chars) for OAuth tokens. Unset, a strong key is generated and persisted in the database. Rotating it invalidates outstanding tokens.                                                                                                                                                                         |
| `AGENTOS_URL`                                                 | No         | `http://127.0.0.1:8000` | Scheduler base URL. `up.sh` sets it to `https://<app>.fly.dev`. Scheduled jobs never fire if it stays at the default in production. Re-running `up.sh` resets a hand-set value to the generated fly.dev URL, so re-pin the domain (or re-run `env-sync.sh`) afterwards. Also the public origin OAuth metadata derives from when `MCP_CONNECT_SECRET` is set. |
| `ENABLE_DEPLOY_CHECK`                                         | No         | `True`                  | Daily deployment-check cron.                                                                                                                                                                                                                                                                                                                                 |
| `ENABLE_SCHEDULED_EVALS`                                      | No         | `False`                 | Daily run-evals cron. Uses model calls.                                                                                                                                                                                                                                                                                                                      |
| `EVALS_TAG`                                                   | No         | `smoke`                 | Eval tag the run-evals workflow runs.                                                                                                                                                                                                                                                                                                                        |
| `EVALS_CASE_TIMEOUT_SECONDS`                                  | No         | `90`                    | Per-case timeout for run-evals runs.                                                                                                                                                                                                                                                                                                                         |
| `EVALS_SUITE_TIMEOUT_SECONDS`                                 | No         | `900`                   | Whole-suite timeout for run-evals runs.                                                                                                                                                                                                                                                                                                                      |
| `PARALLEL_API_KEY`                                            | No         | -                       | WebSearch uses the Parallel SDK when set, keyless MCP otherwise.                                                                                                                                                                                                                                                                                             |
| `SLACK_BOT_TOKEN`                                             | No         | -                       | Set with the signing secret to enable Slack.                                                                                                                                                                                                                                                                                                                 |
| `SLACK_SIGNING_SECRET`                                        | No         | -                       | Set with the bot token to enable Slack.                                                                                                                                                                                                                                                                                                                      |
| `DB_HOST` / `DB_PORT` / `DB_USER` / `DB_PASS` / `DB_DATABASE` | No         | matches compose         | Postgres connection. `up.sh` sets these as Fly secrets pointing at `<app>-db.flycast`.                                                                                                                                                                                                                                                                       |
| `DB_DRIVER`                                                   | No         | `postgresql+psycopg`    | SQLAlchemy driver.                                                                                                                                                                                                                                                                                                                                           |
| `AGNO_DEBUG`                                                  | No         | `False`                 | Verbose Agno logs. Compose sets it for dev.                                                                                                                                                                                                                                                                                                                  |
| `WAIT_FOR_DB`                                                 | No         | `False`                 | If `True`, the entrypoint blocks on the database before starting. Compose sets it.                                                                                                                                                                                                                                                                           |

`up.sh` also reads three variables from your shell when provisioning:

| Variable       | Default               | Description                                                                                                        |
| -------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `FLY_REGION`   | `iad`                 | Region for the app and its Postgres. Re-runs keep the region already in `fly.toml` unless you set this explicitly. |
| `FLY_ORG`      | `personal`            | Fly org for both apps. They must share an org or the private network between them does not exist.                  |
| `FLY_PG_IMAGE` | stock `postgres-flex` | Postgres image. Point it at a pgvector-enabled derivative to support knowledge bases.                              |

## Troubleshooting

<AccordionGroup>
  <Accordion title="flyctl: command not found">
    Install [flyctl](https://fly.io/docs/flyctl/install/), then run `fly auth login`. The scripts accept either binary name, `flyctl` or `fly`.
  </Accordion>

  <Accordion title="up.sh pauses asking for a JWT key">
    Expected. Mint the key at [os.agno.com](https://os.agno.com): connect your OS (**Connect OS** → **Live**, enter your Fly URL), then turn on **Token-Based Authorization (JWT)** under **Settings** → **OS & Security** and paste the full PEM. To do it later, skip the prompt, add `JWT_VERIFICATION_KEY` or `JWT_JWKS_FILE` to `.env.production`, and run `./scripts/fly/env-sync.sh`.
  </Accordion>

  <Accordion title="up.sh exits: fly.toml carries an app name this script doesn't manage">
    The script only manages names it generated. A different name means continuing would overwrite `fly.toml` and abandon that app. Restore the `agentos` placeholder (or an `agentos-*` name from a previous run), or tear down first with `./scripts/fly/down.sh`.
  </Accordion>

  <Accordion title="App refuses to serve in production">
    JWT auth is on whenever `RUNTIME_ENV` is not `dev`. Set `JWT_VERIFICATION_KEY` or `JWT_JWKS_FILE` and sync. To opt out inside a private network behind another auth layer, set `authorization=False` in `app/main.py`.
  </Accordion>

  <Accordion title="Knowledge bases fail at CREATE EXTENSION">
    The stock `postgres-flex` image has no pgvector; sessions and memory are unaffected. Set `FLY_PG_IMAGE` to a pgvector-enabled image and recreate the cluster. `up.sh` reuses an existing cluster, so tear down first with `./scripts/fly/down.sh`. See [Enable pgvector for knowledge bases](#customize).
  </Accordion>

  <Accordion title="Scheduled jobs never fire">
    `AGENTOS_URL` may still be the localhost default. `up.sh` sets it to `https://<app>.fly.dev` automatically; for a custom domain or tunnel, set it by hand and run `./scripts/fly/env-sync.sh`. Re-running `up.sh` resets a hand-set value to the generated fly.dev URL, so re-pin it afterwards. The other cause is the machine scaling to zero: keep `auto_stop_machines = "off"` and `min_machines_running = 1` in `fly.toml`.
  </Accordion>

  <Accordion title="Every cron fires twice">
    A plain `fly deploy` created a second machine, and each runs its own scheduler. Deploy with the scripts; they pass `--ha=false`.
  </Accordion>
</AccordionGroup>
