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
PORTenvironment variable (defaults to8080in containers) - Accept health checks at
/health - Read
API_SECRETfrom 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 honoIn 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/dataBoth should work. Now push to GitHub.
Deploy with the CLI
Install the PandaStack CLI if you haven't:
npm install -g @pandastack/cli
panda loginThe login flow opens your browser and stores a session token. From your repo directory:
panda projects createYou'll see prompts:
- Project name:
hono-api-demo - Repository: auto-detected from
git remote -v, or pasteowner/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-demoThe 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-hereThe 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/dataYou'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:
- 1Variables with a
descriptionbut novaluetrigger a prompt on the deploy screen. You can't skip them. - 2Variables with a
valueare set automatically. No prompt, no forgettingNODE_ENV.
Commit and push:
git add pandastack.json
git commit -m "Add PandaStack config with env prompts"
git pushThe 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-demoGo 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-demoCopy 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)