Forgeon Docs

Documentation, guides, and patterns to help you build on Forgeon — from your first deploy to running serious infrastructure.

Nixpacks Builds

Deploy from source with auto-detection (no Dockerfile), plus clear repo rules and easy overrides.

Nixpacks builds your project by detecting your language/framework and generating a build plan:

install → build → start

It’s Forgeon’s “no Dockerfile” path: fast, automatic, and customizable when you need to override commands.

Choose Nixpacks if you want auto-detection + minimal config. Choose Template if you want a curated “known-good” stack. Choose Dockerfile if you need full control (system deps, multi-stage, custom init).

Quick start (most users)

Your repo should have:

  • A valid entry file (server, app, or framework default)
  • A standard project file:
    • Node/Bun: package.json
    • Go: go.mod
    • Python: pyproject.toml / requirements.txt
  • A start command that runs a web server

Your app must:

  • Bind to 0.0.0.0
  • Use the injected PORT env var

Repo requirements (the “don’t make the platform guess” rules)

1) package.json must declare scripts (Node/Bun)

Forgeon expects a real start command, not “whatever works on my laptop”.

At minimum:

  • start — runs the HTTP server (production mode)
  • (recommended) build — compiles assets if needed

Don’t use dev scripts as production start (e.g. next dev, vite, nodemon). Use a real production server command.

Example: Node/Bun (Hono/Express/Fastify/etc.)

package.json
{
  "scripts": {
    "build": "bun run build",
    "start": "bun run start"
  }
}

2) Your server must listen on 0.0.0.0:$PORT

Forgeon injects:

  • PORT → the internal port your app must listen on
  • APP_HOST → usually 0.0.0.0

Node example

server.js
server.listen(process.env.PORT, '0.0.0.0')

Go example

main.go
http.ListenAndServe("0.0.0.0:"+os.Getenv("PORT"), handler)

If your app binds to localhost / 127.0.0.1, Forgeon can’t reach it and the deploy will fail readiness.

Nixpacks is smart, but your repo can be smarter.

Create a .forgeon.json in your repo root to avoid surprises:

.forgeon.json
{
  "builder": "nixpacks",
  "nixpacks": {
    "install": "bun install --no-save",
    "build": "bun run build",
    "start": "bun run start",
    "nodeVersion": "22"
  },
  "runtime": {
    "internalPort": 3000,
    "healthcheckPath": "/healthz"
  }
}

What this does

  • Locks install/build/start commands (no guessing)
  • Sets a stable internalPort
  • Makes your deployment reproducible across machines

If you’re using Bun, always set install/build/start explicitly. It prevents the platform from picking the wrong default command.

Nixpacks overrides (without .forgeon.json)

You can also override via environment variables:

  • NIXPACKS_INSTALL_CMD
  • NIXPACKS_BUILD_CMD
  • NIXPACKS_START_CMD
  • NIXPACKS_NODE_VERSION (Node projects)
example overrides
$NIXPACKS_INSTALL_CMD="bun install --no-save"
$NIXPACKS_BUILD_CMD="bun run build"
$NIXPACKS_START_CMD="bun run start"

Framework recipes (copy/paste)

Next.js

package.json

package.json
{
  "scripts": {
    "build": "next build",
    "start": "next start -p $PORT -H 0.0.0.0"
  }
}

Recommended .forgeon.json

.forgeon.json
{
  "builder": "nixpacks",
  "nixpacks": {
    "install": "npm ci",
    "build": "npm run build",
    "start": "npm run start",
    "nodeVersion": "20"
  },
  "runtime": { "internalPort": 3000 }
}

Vite (React/Vue/Svelte)

Vite’s preview is not always production-safe. Prefer static hosting or a proper server.

Options:

  • If it’s purely frontend → use Static output (Template/Static flow)
  • If you must run preview server → ensure it binds correctly
package.json
{
  "scripts": {
    "build": "vite build",
    "start": "vite preview --host 0.0.0.0 --port $PORT"
  }
}

Hono / Bun server

package.json
{
  "scripts": {
    "build": "bun run build",
    "start": "bun run start"
  }
}

And make sure your server reads:

  • PORT
  • binds to 0.0.0.0

Go

Requirements:

  • go.mod
  • a main package
  • server listens on $PORT
.forgeon.json
{
  "builder": "nixpacks",
  "nixpacks": {
    "build": "go build -o app .",
    "start": "./app"
  },
  "runtime": { "internalPort": 8080 }
}

Port model (how Forgeon maps traffic)

Forgeon uses two ports:

  • Internal Port: what your app listens on inside the container (via PORT)
  • External Port: a platform-managed host binding (you don’t control this)

So: your app should only care about PORT.

If you hardcode ports, you’re basically telling Forgeon:
“please surprise me at 3AM.” XD

Pre-flight checklist (before you deploy)

  • [ ] package.json has start (and build if needed)
  • [ ] start runs a real production server
  • [ ] app binds to 0.0.0.0
  • [ ] app listens on process.env.PORT / $PORT
  • [ ] (recommended) .forgeon.json sets install/build/start explicitly
  • [ ] health check path returns 200 when ready (if enabled)

Debugging: “pull access denied … run:latest”

If your runtime log shows it tried to run run:latest, that means Forgeon received an invalid/missing image reference.

This usually happens when:

  • Build didn’t publish an image ref back to the deployment record
  • A runtime boot payload lost/overwrote image_ref
  • A default fallback accidentally set image_ref="run:latest"

A valid deploy must boot with image_ref like fgcr.forgeon.io/project-xxxx:build-id. If it’s empty or becomes run:latest, the platform should fail fast before running Docker.

What users should check:

  • Build logs → final image ref + tag
  • Deploy detail → “Image” field is set
  • Runtime logs → docker run ... IMAGE_REF ends with the correct image

Common gotchas

  • Using preview as production
    vite preview / bun preview may work locally but can behave differently in containers.
  • Framework needs explicit host
    Some frameworks require --host 0.0.0.0.
  • Build output mismatch
    Start expects dist/ but build didn’t produce it → fix build command.
  • Monorepo
    If your app isn’t in repo root, set workdir in .forgeon.json (if supported) or use Template/Dockerfile.

Bun

  • install: bun install --no-save
  • build: bun run build
  • start: bun run start

Next.js

  • build: next build
  • start: next start -p $PORT -H 0.0.0.0

If you can run locally with PORT=3000 and host 0.0.0.0, you can run on Forgeon. Same song, bigger speakers.