Berth

Deploying an app

berth create myapp --repo you/myapp

This adds a webhook to the repo, gives the app its hostnames and deploys main. From then on, every push to main deploys.

-----> Registered webhook on github.com/you/myapp
-----> https://myapp.example.com
-----> https://myapp.your-mac.local
-----> Fetching you/myapp (main)
-----> Building myapp v1 from main@a1b2c3d (detected node)
-----> Built berth/myapp:1 in 21s
-----> Starting berth-myapp-1-x7k2 on 127.0.0.1:8001
-----> Healthy in 1.0s (HTTP 200)

myapp is live at https://myapp.example.com

What your app must do

The same four rules as Heroku and Dokku, so anything written for them (or by an agent that knows them) just works:

  1. Listen on $PORT (8080 unless you change it) on 0.0.0.0.
  2. Read its configuration from environment variables.
  3. Only write to /tmp or a mounted folder. Everything else is wiped on each deploy.
  4. Log to stdout and stderr.

Dockerfiles (or not)

If there's a Dockerfile (or Containerfile) at the root of the repo, Berth builds it. If it's somewhere else: berth config:set myapp BERTH_DOCKERFILE=deploy/Dockerfile.

Otherwise Berth works out the stack and writes one for you:

Found in the repo Stack Starts with
bun.lock / bun.lockb Bun bun run start, or your entry file
package.json Node (npm, pnpm or yarn by lockfile; version from engines.node) the start script, or server.js / index.js
pyproject.toml / requirements.txt Python, installed with uv uvicorn, gunicorn or flask run for FastAPI/Flask/Starlette; gunicorn for Django; else python main.py
go.mod Go (multi-stage, small Alpine image) the binary from . or ./cmd/<name>
Cargo.toml Rust (multi-stage) the package's binary
index.html Static site, served by Caddy โ€”

A build script in package.json runs during the build. To see what Berth generates, or take it over:

berth dockerfile myapp > Dockerfile    # commit it and edit as you like

Branches, staging and monorepos

berth create myapp-staging --repo you/myapp --branch staging     # another app for another branch
berth create api --repo you/mono --path services/api             # build from a subfolder

A monorepo app only deploys when a push touches files under its path.

To point an existing app at a different repo or branch (say you started from a local folder and have now pushed it to GitHub):

berth app:set-repo myapp you/myapp [--branch main]

Config, databases, sign-in and release history all stay put. Berth moves the webhook over and only redeploys if the branch is ahead of what's live.

How deploys get triggered

Trigger When
GitHub webhook Pushes deploy within seconds. berth create sets it up if you have a tunnel and a GitHub login.
Polling Every 60 seconds Berth checks the branch. Always on, so it catches missed webhooks, --no-webhook, --no-tunnel and repos that aren't on GitHub.
git push berth main Push straight to the Mac over SSH, Dokku-style, and watch the build in your terminal.
berth deploy myapp Deploy right now. `--ref <sha

--repo also takes any git URL (https://gitlab.com/you/app.git) or a local path. Or leave it out for an app you only ever push to:

berth create myapp                 # no repo
cd ~/code/myapp && berth git:remote myapp
git push berth main

git push berth needs Remote Login turned on on the Mac (System Settings โ†’ General โ†’ Sharing).

Private repos

The GitHub login from init covers private repos. If you'd rather use a read-only SSH deploy key for a particular repo:

berth app:set-key myapp     # makes a key and adds it to the repo (or prints it for you to add)

Edit this page on GitHub (it's a section of the README)