Deploy with Hono
Run the canonical Lucid route plan as a long-lived Fetch-native Bun service.
Hono is the most direct deployment for a Bun/Fetch-native Lucid API. The
adapter creates a Hono app and binds every route from runtime.http.routes.
It does not install its own paywall, parser, or task registry.
Prerequisites
- Build the Next workspace set or generate a matching Hono project.
- Configure one public origin/base path, payment provider, receiving address, network, secrets, and required durable stores.
- Use a long-lived process when you depend on SQLite, in-process streams, owned task execution, or scheduler workers.
Production entrypoint
import { app, runtime } from './lib/agent';
const port = Number(process.env.PORT ?? 3000);
const server = Bun.serve({
port,
fetch: app.fetch,
});
console.log(`Listening on ${server.url}`);
let shutdownPromise: Promise<void> | undefined;
function shutdown(signal: string): Promise<void> {
shutdownPromise ??= (async () => {
console.log(`Received ${signal}; draining`);
await server.stop(false);
await runtime.close();
})();
return shutdownPromise;
}
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
process.on(signal, () => {
void shutdown(signal).then(
() => process.exit(0),
error => {
console.error('Shutdown failed', error);
process.exit(1);
}
);
});
}Export runtime and app from a server-only agent module:
import { createAgentApp } from '@lucid-agents/hono';
export const runtime = await buildRuntime();
const bound = await createAgentApp(runtime, {
beforeMount(app) {
app.use('*', securityHeadersMiddleware);
},
afterMount(app) {
app.get('/ready', async context => {
const ready = await checkRequiredDependencies();
return context.json({ ready }, ready ? 200 : 503);
});
},
});
export const app = bound.app;/health is Lucid liveness. Keep /ready private or minimally descriptive and
include the database schema/atomic probe, facilitator /supported, and critical
downstream dependency checks appropriate to this deployment.
Build an OCI container
Use the package's actual Bun version and pin the base image by digest in your deployment repository. A generated Hono project can use this shape:
FROM oven/bun:1.3.10 AS app
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun run type-check && bun run build
ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000
CMD ["bun", "run", "start"]Build and smoke it locally:
docker build -t lucid-hono:canary .
docker run --rm -p 3000:3000 --env-file .env lucid-hono:canary
curl --fail http://localhost:3000/healthDo not bake .env, private keys, database passwords, or facilitator tokens
into the image. Use the orchestrator's secret mount/injection and make the
filesystem read-only except for explicit persistent paths.
Storage topology
- In-memory stores are preview-only.
- SQLite payment/SIWX files require persistent disk, one compatible writer topology, backups, and migration/restore tests.
- Multiple replicas need shared Postgres payment/SIWX state plus custom shared HTTP idempotency, task, and scheduler stores.
- The package ships no durable task or scheduler implementation.
- MPP services must inject the Postgres challenge store for shared replicas; process-local defaults are for development only.
Run migrations in a separate deployment job or one elected instance before readiness. Do not let every replica race destructive migrations at startup.
Reverse proxy and streaming
Forward the original Host and scheme so generated Agent Card URLs are public
and correct. For SSE routes:
- disable proxy buffering and response caching;
- avoid middleware that buffers/compresses the entire body;
- flush headers immediately and preserve
text/event-stream; - set idle/request timeouts beyond the bounded stream duration;
- propagate disconnect/abort to the Bun Request;
- cap concurrent streams separately from ordinary invokes.
Expose PAYMENT-REQUIRED, PAYMENT-RESPONSE, Payment-Receipt, and any SIWX
headers through CORS only to browser origins that genuinely need them.
Scaling and workers
Web replicas can scale horizontally only after every shared invariant is moved out of process. Run scheduler polling and recoverable long-running task work in separate long-lived worker processes; do not start a scheduler timer in every web replica unless each occurrence is protected by a correct shared lease.
Autoscale within database connection, facilitator/provider, wallet, and chain RPC limits. A burst of replicas can otherwise turn one traffic spike into a provider outage.
Canary verification
curl --fail https://service.example/health
curl --fail https://service.example/.well-known/agent-card.json
curl -i https://service.example/entrypoints/quote/invoke \
-H 'content-type: application/json' \
-H 'idempotency-key: deploy-hono-canary-000001' \
--data '{"input":{"symbol":"ETH"}}'The third call must return 402 before the funded canary. Then execute one
low-limit paid call, assert schema-valid output plus PAYMENT-RESPONSE, locate
the external transaction and local payment/idempotency record, restart the
container, and replay the same key.
Failure and rollback
| Failure | Response |
|---|---|
| Readiness cannot reach required store/provider | Keep instance out of load balancer |
| SSE buffers or disconnect is ignored | Fix proxy/runtime before production traffic |
| Settlement outcome is unknown during shutdown | Stop paid admission and reconcile; preserve staged/idempotency state |
| Store migration is incompatible | Roll forward/restore with tested procedure; do not erase payment state |
| Canary charges without expected fulfillment | Halt rollout, retain evidence, follow refund/incident runbook |
Roll back to a schema-compatible image after draining. Keep state and evidence intact. See the Hono package reference, durable storage, and deployment overview.