Back to Blog
Tutorial12 min read2026-08-02

Ship a Hono API to Production with the CLI and pandastack.json

Deploy a Hono API using the panda CLI and a config file that prompts for environment variables. Port binding, health checks, and live logs in under ten minutes.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

Most lightweight Node.js API frameworks ship a demo that binds to localhost:3000 and breaks the moment you push to production. The container starts, the health check pings 0.0.0.0:8080, gets nothing, and the deployment fails. Hono is no exception — fast, tiny, edge-ready, and completely silent about port binding until you hit this.

This walkthrough fixes that before you deploy, shows you two ways to get Hono live (the panda CLI for quick iteration, and a committed pandastack.json file for reproducible setups), and wires in environment variables without hardcoding secrets in your repo.

What you're building

A Hono API with a /health endpoint, a protected /api/data route that checks an API key from the environment, and a custom startup message. The app will:

  • Bind to the port PandaStack assigns via the PORT environment variable (defaults to 8080 in containers)
  • Accept health checks at /health
  • Read API_SECRET from an environment variable you'll set during deploy
  • Log to stdout so PandaStack's live log stream picks it up

You'll deploy it twice: first with the CLI to see the process, then with a pandastack.json file that makes the deploy screen prompt for the API secret instead of letting you forget it.

The Hono app

Create a new directory and initialize:

mkdir hono-api-demo && cd hono-api-demo
npm init -y
npm install hono

In index.js:

import { Hono } from 'hono'

const app = new Hono()

app.get('/health', (c) => c.text('OK'))

app.get('/api/data', (c) => {
  const authHeader = c.req.header('Authorization')
  const expectedSecret = process.env.API_SECRET || 'default-secret'

  if (authHeader !== `Bearer ${expectedSecret}`) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  return c.json({ message: 'Data retrieved', timestamp: new Date().toISOString() })
})

const port = parseInt(process.env.PORT || '3000', 10)
const hostname = '0.0.0.0'

console.log(`Hono API starting on ${hostname}:${port}`)

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

Add a package.json start script:

{
  "type": "module",
  "scripts": {
    "start": "node index.js"
  }
}

The critical detail: hostname: '0.0.0.0'. Hono's default server binds to this automatically when you export the config, but the explicit PORT environment variable read and the startup log make debugging deployments trivial when something goes wrong.

Test locally:

npm start
curl http://localhost:3000/health
curl -H "Authorization: Bearer default-secret" http://localhost:3000/api/data

Both should work. Now push to GitHub.

Deploy with the CLI

Install the PandaStack CLI if you haven't:

npm install -g @pandastack/cli
panda login

The login flow opens your browser and stores a session token. From your repo directory:

panda projects create

You'll see prompts:

  • Project name: hono-api-demo
  • Repository: auto-detected from git remote -v, or paste owner/repo
  • Branch: main
  • Type: container (Node.js apps are containerized by default)
  • Auto-deploy on push: yes

The CLI creates the project and triggers the first build. Watch the logs:

panda projects logs hono-api-demo

The build will succeed, the container will start, but the app won't respond to requests yet because you haven't set API_SECRET. The health check passes (it doesn't need the secret), so the deployment goes live. Test it:

curl https://hono-api-demo-abc123.pandastack.io/health
# OK

curl -H "Authorization: Bearer default-secret" \
  https://hono-api-demo-abc123.pandastack.io/api/data
# {"message":"Data retrieved","timestamp":"..."}

It works with the default secret because the code falls back to 'default-secret' when API_SECRET is unset. In production, you'd never do that. Fix it:

panda projects env hono-api-demo set API_SECRET=your-actual-secret-here

The CLI updates the environment variables and triggers a redeploy. The new container picks up API_SECRET, and the old bearer token stops working. Test with the new one:

curl -H "Authorization: Bearer your-actual-secret-here" \
  https://hono-api-demo-abc123.pandastack.io/api/data

You've deployed with the CLI and set a secret. This is fast for personal projects, but if you're building a template repo or deploying from CI, the interactive prompts are friction. The pandastack.json file removes that.

Prompt for secrets with pandastack.json

Create pandastack.json in your repo root:

{
  "type": "container",
  "name": "hono-api-demo",
  "language": "nodejs",
  "startCommand": "npm start",
  "healthCheckPath": "/health",
  "env": [
    {
      "key": "API_SECRET",
      "description": "Bearer token for /api/data endpoint. Generate with: openssl rand -hex 32"
    },
    {
      "key": "NODE_ENV",
      "value": "production"
    }
  ]
}

The env array does two things:

  1. 1Variables with a description but no value trigger a prompt on the deploy screen. You can't skip them.
  2. 2Variables with a value are set automatically. No prompt, no forgetting NODE_ENV.

Commit and push:

git add pandastack.json
git commit -m "Add PandaStack config with env prompts"
git push

The auto-deploy triggers. The project already exists, so it redeploys with the new config. Open the dashboard at https://dashboard.pandastack.io and check the project's Deployments tab. You'll see the latest build used the pandastack.json settings.

Now delete the project and redeploy from scratch to see the prompt in action:

panda projects delete hono-api-demo

Go to https://dashboard.pandastack.io/deploy?repo=yourname/hono-api-demo in your browser. The deploy screen reads the pandastack.json from your repo's main branch and shows a form field for API_SECRET with the help text you wrote. NODE_ENV is pre-filled. Click Deploy, paste a secret, and the app goes live with both variables set.

Why this matters

Most deploy-from-Git platforms let you forget environment variables until the app crashes in production. The pandastack.json config prevents that by enforcing prompts at deploy time. It's the same pattern Vercel uses, and it works because:

  • New developers cloning your repo see exactly what secrets are required
  • The deploy button in your README works without manual setup steps
  • CI scripts can still override variables via the API (covered below)

Deploy from CI with the API

If you're running deployments from GitHub Actions or another CI system, the CLI and the deploy screen aren't options. The REST API is the right path. Generate a project-scoped token:

panda projects token hono-api-demo

Copy the psk_live_xxx token and store it as a GitHub Actions secret (PANDASTACK_TOKEN). In .github/workflows/deploy.yml:

name: Deploy to PandaStack
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Trigger deploy
        run: |
          curl -X POST https://api.pandastack.io/v1/projects/${{ secrets.PROJECT_ID }}/deploy \
            -H "Authorization: Bearer ${{ secrets.PANDASTACK_TOKEN }}" \
            -H "Content-Type: application/json" \
            -d '{
              "branch": "main",
              "env": [
                { "name": "API_SECRET", "value": "${{ secrets.API_SECRET }}" },
                { "name": "NODE_ENV", "value": "production" }
              ]
            }'

The env array in the API payload overrides the pandastack.json defaults for this specific deploy. The config file still documents what variables are required, but CI supplies the actual values from secrets.

What breaks and how to fix it

Health check fails even though the app starts: You're binding to localhost instead of 0.0.0.0. PandaStack's health check probes come from outside the container and can't reach 127.0.0.1. The Hono config above fixes this with hostname: '0.0.0.0'.

Logs show the app started but requests time out: The PORT environment variable is unread, so your app is listening on 3000 but the container runtime is routing traffic to 8080. Always read process.env.PORT and fall back to 3000 for local development only.

Environment variables don't appear in the running container: Check the project's Settings tab in the dashboard. If the variable isn't listed there, it wasn't set. Redeploy after adding it via panda projects env or the API.

Next steps

You've deployed a Hono API with both the CLI and a config file that prevents missing secrets. The same pattern works for any Node.js framework — Express, Fastify, Koa — just swap the server setup. For production workloads, consider:

  • Connecting a managed PostgreSQL or MySQL database via the dashboard
  • Setting up preview deployments for pull requests
  • Wiring the KV store (managed Redis) for session management or rate limiting

PandaStack handles the infrastructure; you write the routes.

References

  • [Hono documentation](https://hono.dev/)
  • [PandaStack CLI commands](https://docs.pandastack.io/cli/commands)
  • [PandaStack API reference](https://docs.pandastack.io/api)
  • [Environment variables guide](https://docs.pandastack.io/projects/env)

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also