Back to Blog
Tutorial9 min read2026-06-28

How to Deploy a Hono API to the Cloud

Hono is a tiny, blazing-fast web framework that runs everywhere. This tutorial shows how to deploy a Hono API as a long-running container, with the runtime choices and database wiring you need.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

# How to Deploy a Hono API to the Cloud

Hono (Japanese for "flame") is a small, fast web framework built on web standards. Its claim to fame is portability — the same code runs on Cloudflare Workers, Deno, Bun, Node.js, and more. That flexibility is great, but it means you have a decision to make at deploy time: which runtime, and which adapter. This tutorial focuses on deploying Hono as a long-running container, the most universal target.

Why Hono is interesting

Hono is built on the standard Request/Response Web APIs rather than Node-specific primitives. That is why it runs across so many runtimes, and why it is fast — a trie-based router with minimal overhead. For a portable, lightweight API, it is hard to beat.

Step 1: a runtime-aware entrypoint

Hono's app definition is identical everywhere; only the adapter that serves it differs. For a Node.js container, use @hono/node-server:

import { Hono } from 'hono';
import { serve } from '@hono/node-server';

const app = new Hono();

app.get('/', (c) => c.json({ status: 'ok' }));
app.get('/users/:id', (c) => c.json({ id: c.req.param('id') }));

serve({
  fetch: app.fetch,
  port: Number(process.env.PORT) || 8080,
});

For Bun, the entrypoint is even simpler — Bun serves app.fetch natively:

export default { port: Number(process.env.PORT) || 8080, fetch: app.fetch };

Pick the runtime first; it drives the adapter and the Dockerfile.

Step 2: middleware and structure

Hono ships batteries-included middleware. A production API usually wants logging, CORS, and error handling:

import { logger } from 'hono/logger';
import { cors } from 'hono/cors';

app.use('*', logger());
app.use('/api/*', cors({ origin: 'https://myapp.com' }));

app.onError((err, c) => {
  console.error(err);
  return c.json({ error: 'Internal Server Error' }, 500);
});

Step 3: wire a database

Hono is just the HTTP layer; your data access is ordinary Node/Bun code. Read the connection string from the environment:

import pg from 'pg';
const db = new pg.Pool({ connectionString: process.env.DATABASE_URL });

app.get('/todos', async (c) => {
  const { rows } = await db.query('SELECT * FROM todos ORDER BY id');
  return c.json(rows);
});

If you target a serverless runtime later, swap to an HTTP-based or pooled driver, but for a long-running container a standard pool is ideal.

Step 4: containerize

For Node:

FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
RUN npm run build   # if using TypeScript
ENV PORT=8080
EXPOSE 8080
CMD ["node", "dist/index.js"]

For Bun, the base image and start command change but the app code does not:

FROM oven/bun:1
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --production
COPY . .
ENV PORT=8080
EXPOSE 8080
CMD ["bun", "src/index.ts"]

Runtime tradeoffs

TargetBest forNotes
Node containerUniversal, long-runningUse @hono/node-server
Bun containerSpeed, simple depsServes fetch natively
Cloudflare WorkersEdge, scale-to-zeroDifferent DB drivers; no long-lived connections
DenoWeb-standard purityFirst-class fetch

For a database-backed API that holds a connection pool, a long-running container (Node or Bun) is usually the right call. Edge runtimes are fantastic for stateless, globally-distributed endpoints but complicate persistent database connections.

Step 5: health check

Platforms want a probe endpoint:

app.get('/health', (c) => c.json({ ok: true }));

Common pitfalls

  • Wrong adapter for the runtime. @hono/node-server on Node, native fetch export on Bun/Deno/Workers. Mixing them up is the most common error.
  • Hardcoded port. Always read process.env.PORT.
  • Edge runtime + classic DB driver. TCP database drivers do not work on Workers; you would need an HTTP driver. Avoid the trap by using a container for DB-backed APIs.
  • Forgetting the build step for TypeScript before node dist/....

Deploying on PandaStack

PandaStack auto-detects Node apps and supports Bun, or builds straight from your Dockerfile, so the Hono container deploys from a git push with no special configuration. Provision a managed PostgreSQL and DATABASE_URL is injected automatically into your pg.Pool. You get custom domains with automatic SSL, live app logs (great for watching Hono's logger() output), plus rollbacks and deploy history. Since Hono containers are small and start fast, they are a natural fit for the free tier (5 web services + 1 database), where scale-to-zero keeps idle APIs cost-free.

References

  • [Hono documentation](https://hono.dev/)
  • [Hono Node.js adapter](https://github.com/honojs/node-server)
  • [Bun documentation](https://bun.sh/docs)
  • [node-postgres pooling](https://node-postgres.com/features/pooling)

---

Hono's portability is its superpower — just match the adapter to your runtime. PandaStack builds Node or Bun Hono apps from one git push and auto-wires the database — try it free at [dashboard.pandastack.io](https://dashboard.pandastack.io).

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also