Skip to content
homeport

Docs · Start

What homeport detects

homeport reads the files your repo already has, picks a toolchain from them, and either builds a Linux binary to run or a folder of files to serve. Nothing in your repo is run to decide.

The order it checks in

Detection looks at the app’s folder (the repo root, unless you chose a folder inside it) and takes the first match:

Detection order
If the folder hasIt is built asInstallBuild
build.imageYour own build image (from homeport.yaml)—your build.command
composer.jsonPHP on FrankenPHP (Laravel included)composer install --no-dev --optimize-autoloaderA FrankenPHP binary with your app embedded
go.modGo—CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o server .
package.json + bun.lockBunbun install --frozen-lockfilebun run build
package.json + package-lock.jsonNodenpm cinpm run build
index.htmlA static site, served as it is—Nothing

You see this plan before anything builds. If none of these files is there, the build stops and asks you to set the build and output in the app’s build settings.

Go

The Go version comes from your go.mod: its toolchain line if it has one, otherwise its go line. The build runs in the official golang image for that version and writes a static binary called server, with CGO off, paths trimmed and symbols stripped. Your app runs that binary.

Laravel and PHP, on FrankenPHP

Any folder with a composer.json is a PHP app, Laravel included. A composer.lock is required, so the build installs exactly what you tested. The build:

  1. checks your platform requirements against the lockfile (composer check-platform-reqs --no-dev --lock),
  2. installs your dependencies without dev packages,
  3. builds your front-end assets with Bun when package.json has a build script (bun install then bun run build),
  4. embeds the app into a single FrankenPHP binary, PHP 8.5 with the standard extensions.

.git, node_modules and tests are left out of the embedded app.

The binary is FrankenPHP, so your homeport.yaml says how it starts: its php-server command serves the app from public/, on homeport’s port. Artisan runs through its php-cli, so a release command or a worker is arguments to the same binary, not php …:

homeport.yaml
run: php-server --listen :$PORT
release: php-cli artisan migrate --force
processes:
  worker: php-cli artisan queue:work

Bun

A package.json with a bun.lock or bun.lockb (or a packageManager of bun@…) builds with Bun. The version is the one in packageManager, then .bun-version, then the latest Bun 1. What comes out decides what runs:

  • a binary at server (for example from bun build --compile --outfile server) runs as your app;
  • otherwise, a recognised static build is served as files (below).

Node projects with a package-lock.json are built the same way with npm ci and npm run build, on the Node version in .nvmrc (22 when there is none). Yarn and pnpm lockfiles are not detected.

Static sites

A JavaScript project is treated as a static site when it uses a static builder and no server framework:

Static builds homeport recognises
Your project usesThe folder served
@sveltejs/adapter-staticbuild
Astro, without output: 'server' or an adapterdist
Vite (not SvelteKit)dist
A plain index.html, no buildthe app’s folder

A dependency on a server framework (for example next, nuxt, hono, express, elysia, fastify, @sveltejs/adapter-node or @astrojs/node) keeps the app a binary. When the build produces no binary, homeport looks for a site in build, dist and out, in that order, and serves the first one that has an index.html.

A site with a 200.html, or with exactly one HTML file, is served as a single-page app: any path that is not a file gets the shell.

rsc-kit

A project that depends on @rsc-kit/core is built with Bun, and then homeport reads the marker rsc-kit writes at the end of its build, .output/rsc-kit.json, to learn what it made:

.output/rsc-kit.json
{ "output": "export", "dir": "dist" }                                   // a static site
{ "output": "server", "compile": "compile", "binary": "dist/app" }     // a server
  • export: the folder in dir is served as a static site.
  • server: homeport runs the package.json script named in compile, then runs the binary at binary.

The marker needs rsc-kit 0.29.6 or newer. Setting static, build.command or build.artifact in homeport.yaml (or the build settings in the dashboard) skips it.

When the guess is wrong

Change the build in the app’s settings in the dashboard (the app’s folder, whether it is a binary or a static site, and its install, build, output and run commands), or commit a homeport.yaml. The dashboard’s settings win over the file.

What your app must do

  • Listen on the port in $PORT, on 0.0.0.0 (also in $HOST). The port is assigned, so do not hard-code one.
  • Answer GET / with a status below 400 within 30 seconds of starting. A redirect counts. If it does not, the deploy is rolled back to the previous release.
  • Write anything that must outlive a release to the folder in $STATE_DIR. The release folder your binary runs from is read-only.