Tutorial10 min read

From Zero to Production: Deploying Your First Aroha Agent on Fly.io

By the end of this post you will have a real Aroha agent running on Fly.io, registered on the Hub, and protected with a spending mandate. The full process takes about 15–20 minutes — I timed it, and the slowest step was waiting for Fly's builder.

A terminal mid-install — where every fifteen-minute promise gets tested
Photo: Unsplash
Prerequisites: Node.js 22+, a free Fly.io account, and an Aroha API key (free, takes 30 seconds to generate in Studio).
1

Scaffold the agent

Run the scaffold command to create a working agent with sensible defaults:

npx create-aroha-agent my-agent --template weather
cd my-agent
npm install

The scaffold creates a message-handling agent. Open src/index.ts to see the structure:

import { serve } from "@aroha-sdk/run";

// The handler receives { message, context, ... } and returns a string.
const agent = serve("Weather Agent", async ({ message }) => {
  const city = message.trim();
  // Replace with a real weather API call
  return `${city}: 22°C, sunny`;
});

agent.start(8080);

Run npm run dev to verify it starts. You should see Agent listening on :8080.

2

Register on the Hub

Add your Aroha API key to a .env file:

AROHA_API_KEY=sk-aroha-...

Every serve() agent has a register() method. Call it once before start(), in src/index.ts:

// register on the Hub, then start serving
const record = await agent.register("https://my-agent.fly.dev", {
  apiKey: process.env.AROHA_API_KEY!,
});
console.log("Registered:", record.did, "→", record.didHash);
console.log("Hub URL: https://www.aroha-labs.com/hub/agents/" + record.didHash);

agent.start(8080);

Run npm run dev. Save the DID — you will need it for the mandate in step 4.

3

Deploy to Fly.io

The scaffold already includes a Dockerfile and fly.toml. Deploy:

fly launch --name my-weather-agent --region lax
fly secrets set AROHA_API_KEY=sk-aroha-...
fly deploy

Once deployed, verify the health endpoint:

curl https://my-weather-agent.fly.dev/health
# {"status":"ok","did":"did:aroha:my-weather-agent","capabilities":["get-weather"]}
4

Issue a spending mandate

Before any orchestrator can call your agent with a payment capability, it needs a mandate. Issue one from your orchestrator (or test it from a script):

import { issueMandateToken } from "@aroha-sdk/credentials";

const mandate = await issueMandateToken({
  to: "did:aroha:my-weather-agent",
  allowed: ["get-weather"],
  spendLimit: 10,      // $10 max per session
  currency: "USD",
  expiresInSeconds: 3600,
});

// Pass this mandate in the Authorization header of your ArohaRequest
console.log(mandate.token);
For a weather agent that does not actually charge money, the spend limit is informational. It becomes enforced when you connect a settlement backend — see the Settlement docs for Stripe and quota backends.
5

Call your agent

With the agent deployed and a mandate in hand, call it from any Aroha-compatible orchestrator or directly:

import { callAgent } from "@aroha-sdk/run";

// register() gave you the didHash — that is what you call with.
const { message, sessionId } = await callAgent(
  didHash,
  "What is the weather in Auckland?",
  { context: { mandate: mandate.token } },
);

console.log(message);
// "It's 14°C and cloudy in Auckland."

callAgent resolves the endpoint from the registry and POSTs to /v1/run, returning { message, sessionId }. Anything the agent needs beyond the prompt — a mandate token, a correlation id — travels in context and is verified server-side.

If the agent requires signed callers, pass your identity instead of a token and the SDK signs each request: signAs: { did, privateKey }. See Private Agents.

What is in the Dockerfile

The scaffold generates a minimal multi-stage Dockerfile — builder stage installs dependencies, runtime stage runs the compiled output:

FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
RUN npm run build

FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 8080
CMD ["node", "dist/index.js"]