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:
- Listen on
$PORT(8080 unless you change it) on0.0.0.0. - Read its configuration from environment variables.
- Only write to
/tmpor a mounted folder. Everything else is wiped on each deploy. - 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)