Back to Blog
Tutorial9 min read2026-07-23

Deploy a Bun & Hono API with Postgres in Minutes on PandaStack

A complete, practical guide to deploying a production-ready Bun and Hono API on PandaStack. Covers build configuration, database migrations, environment variables, and going live via Git push.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

Bun's speed and Hono's minimalist design make them a powerful combination for building high-performance APIs. But moving from localhost to a live, production environment introduces new challenges: build processes, database connections, environment management, and reliable deployments.

This guide provides a complete, no-fluff walkthrough for deploying a Bun and Hono API on PandaStack, complete with a managed PostgreSQL database. We'll cover the specific configurations needed to get it right on the first try.

What We'll Build and Deploy

We'll start with a minimal Hono API, configure it for a production build, connect it to a managed Postgres database using Drizzle ORM, and deploy it via a simple git push.

Prerequisites

* [Bun](https://bun.sh/) installed on your local machine.

* A [GitHub](https://github.com/), GitLab, or Bitbucket account.

* A PandaStack account. The [Free tier](https://pandastack.io) is more than enough to follow along; it includes web services, a managed database, and a generous build minute allowance.

Step 1: Create a Basic Bun + Hono API

First, let's scaffold a new project and create a simple API.

# Create a new Bun project
bun init

# Add Hono as a dependency
bun add hono

Now, replace the contents of src/index.ts (or create it if it doesn't exist) with a basic Hono server. The critical part here is to respect the PORT environment variable, which is how platforms like PandaStack tell your application which port to listen on.

// src/index.ts
import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => {
  return c.json({ message: 'Hello from Bun and Hono!' })
})

const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000

console.log(`Server is running on port ${port}`)

export default {
  port,
  fetch: app.fetch,
}

Update your package.json to include a start script.

{
  "name": "bun-hono-api",
  "module": "src/index.ts",
  "type": "module",
  "scripts": {
    "start": "bun run src/index.ts"
  },
  "devDependencies": {
    "@types/bun": "latest"
  },
  "peerDependencies": {
    "typescript": "^5.0.0"
  },
  "dependencies": {
    "hono": "^4.4.6"
  }
}

You can test it locally by running bun start. The server will be available at http://localhost:3000.

Step 2: Configure for a Production Deployment

Running TypeScript source directly with bun run is great for development, but for production, it's better to create an optimized JavaScript build. This reduces startup time and eliminates the need for a TypeScript transpiler in the production container.

First, let's create a build script in package.json.

# No need to install anything, Bun has a built-in bundler.

Update your package.json scripts. We'll add a build script and modify the start script to run the compiled output.

{
  ...
  "scripts": {
    "build": "bun build ./src/index.ts --outdir ./dist --target bun",
    "start": "bun run ./dist/index.js"
  },
  ...
}

* "build": This command tells Bun to take our entrypoint (src/index.ts), bundle it and its dependencies into a single file, and place it in the ./dist directory.

* "start": This now points to the compiled JavaScript artifact. This is the command PandaStack will run to start our application.

Commit these changes and push them to your Git repository. Now we're ready to deploy.

Step 3: Deploy the API to PandaStack

PandaStack's deployment process is Git-native. You connect a repository, configure the build and start commands, and the platform handles the rest.

  1. 1 Create a New Web Service: In your PandaStack dashboard, click "New" -> "Web Service".
  2. 2 Connect Your Git Repository: Authenticate with your Git provider and select the bun-hono-api repository.
  3. 3 Configure the Service: This is where we tell PandaStack how to run our app.

* Runtime: PandaStack will auto-detect a Node.js environment. Since Bun aims for Node.js compatibility and we're using bun commands, this works perfectly.

* Build Command: bun install && bun run build

* Start Command: bun run ./dist/index.js

* Plan: Select the Free tier.

Click "Create Service". PandaStack will pull your code, run the build command in a secure, ephemeral container, and deploy the resulting image. You can watch the build logs in real-time. Once complete, your API will be live at a provided .pandastack.app URL.

Step 4: Add and Connect a PostgreSQL Database

Most APIs need a database. Let's add a managed PostgreSQL instance and connect it to our Hono app using Drizzle, a popular TypeScript ORM.

First, add Drizzle and the postgres driver to your project:

# Add Drizzle ORM and the postgres.js driver
bun add drizzle-orm postgres

# Add drizzle-kit for migrations as a dev dependency
bun add -d drizzle-kit

Creating the Database on PandaStack

  1. 1 In your PandaStack project dashboard, go to the "Databases" tab and click "New Database".
  2. 2 Choose PostgreSQL (e.g., version 16.x) and select a plan (the Free tier includes one database).
  3. 3 Crucially, in the "Connected Services" dropdown, select your bun-hono-api service.

This last step is the magic. PandaStack will automatically inject the database connection string into your API service as an environment variable named DATABASE_URL.

Using DATABASE_URL in the App

Now, let's update our code to use this connection string. Create a schema file for Drizzle.

// src/db/schema.ts
import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  email: text('email').notNull().unique(),
  createdAt: timestamp('created_at').defaultNow().notNull(),
});

Next, create a file to initialize the Drizzle client.

// src/db/index.ts
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';

if (!process.env.DATABASE_URL) {
  throw new Error('DATABASE_URL environment variable is not set');
}

const client = postgres(process.env.DATABASE_URL);
export const db = drizzle(client, { schema });

Finally, let's add a new route to our index.ts to fetch and create users.

// src/index.ts
import { Hono } from 'hono';
import { db } from './db';
import { users } from './db/schema';

const app = new Hono();

// ... (existing '/' route)

app.get('/users', async (c) => {
  const allUsers = await db.select().from(users);
  return c.json(allUsers);
});

const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;

console.log(`Server is running on port ${port}`);

export default {
  port,
  fetch: app.fetch,
};

Handling Database Migrations (The Right Way)

How do we get our users table into the database? You should never run migrations from your application's start command. The best practice is to run migrations as part of your deployment's build step.

First, add a migrate script to package.json.

{
  ...
  "scripts": {
    "build": "bun build ./src/index.ts --outdir ./dist --target bun",
    "start": "bun run ./dist/index.js",
    "migrate": "drizzle-kit push:pg"
  },
  ...
}

We also need a drizzle.config.ts file in the project root:

// drizzle.config.ts
import type { Config } from 'drizzle-kit';

export default {
  schema: './src/db/schema.ts',
  out: './drizzle',
  driver: 'pg',
  dbCredentials: {
    connectionString: process.env.DATABASE_URL!,
  },
} satisfies Config;

Now, update the Build Command in your PandaStack service settings to include the migration step.

* Build Command: bun install && bun run migrate && bun run build

This ensures that every time you push a change, PandaStack will:

  1. 1 Install dependencies.
  2. 2 Run database migrations against your live database.
  3. 3 Build the production version of your application.
  4. 4 Deploy the new version.

Commit all your database-related changes and git push. PandaStack will detect the push, trigger a new build, run your migrations, and deploy the updated API. You can then test your new /users endpoint.

Step 5: Managing Environment Variables

DATABASE_URL is injected automatically, but you'll need other secrets like API keys or a JWT_SECRET.

In your service's dashboard on PandaStack, go to the "Environment Variables" tab. Here you can add secrets securely. For example, add a new variable JWT_SECRET with a secure random string.

These variables are available in your Bun application via process.env.JWT_SECRET. Adding or updating a variable automatically triggers a new, secure deployment of your service.

From Development to Production

With that last push, you've successfully deployed a production-ready Bun and Hono API. PandaStack provides the live URL, automatic SSL, and handles the underlying infrastructure.

Some things to note about the platform:

* Honest Limitations: The Free tier is designed for hobby projects and development. It uses preemptible nodes and scales services to zero after a period of inactivity, which can cause a short cold start on the first request. Paid plans run on stable nodes and do not scale to zero, making them suitable for production workloads.

* Observability: The "Logs" tab in your service dashboard provides real-time streams for both build and application logs, which is invaluable for debugging.

* Rollbacks: If a deployment introduces a bug, you can instantly roll back to any previous successful deployment from the "Deployments" tab.

By defining our application's needs—build steps, start commands, and database migrations—declaratively, we can leverage the power of a GitOps workflow. PandaStack takes care of the automation, letting you focus on writing code.

Ready to deploy your own Bun API? [Get started on PandaStack's free tier](https://pandastack.io) and push your code live in minutes.

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also