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.
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.
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.
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"]}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);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"]