Skip to content
podby

Docs

Deploy from your terminal, or call the API.

The command line and the API do the same things. Use whichever fits your build.

The command line is in early release. Commands and output may change before version 1.

Install

You need Node 24.21 or newer, plus zip and tar, which most systems already have.

npm install -g @podby/cli

Log in

Sign up at app.podby.com first and fill in your billing details. Then:

podby login

It asks for your email, sends you a code that lasts 10 minutes, and asks for the code. Each one is cleared from the screen once it is accepted. Five wrong codes use the code up, so ask for a new one by running podby login again. Your token lasts 30 days and is kept in ~/.config/podby/credentials.json.

Deploy a static site

Run it from the folder you want online, usually your build output. The name becomes part of the address.

cd dist
podby deploy --static docs

The folder is zipped, without .git, and uploaded. Deploying the same name again replaces the site: files that are not in the new folder are removed. To keep the files already online and add or overwrite only the ones in the folder, add --append.

podby deploy --static docs --append

Append starts from the last successful deployment. Pending and rejected uploads are excluded. If those successful files are unavailable, deploy once without --append and wait for success before appending again.

Either way, the new files are copied next to the live site first and swapped in once they are all there, so visitors never see a half-deployed site. You can also upload a zip from the dashboard. It replaces the site too. On an existing site's page, tick Keep files already online to upload like --append.

Follow a deploy

After the upload, the command prints a link to the site or pod in the dashboard and keeps checking the deploy every two seconds. The status moves from queued to deploying, then ends at success or error.

Uploaded docs. The deploy is queued on PodBy.
You can stop this command at any time (Ctrl+C) without stopping the deploy.
Follow it at https://app.podby.com/sites/…
Status: success
Live at https://docs.….podby.app

The deploy runs on PodBy, not on your machine, so stopping the command or closing the terminal does not stop it. A deploy that fails is tried up to three times before it ends at error. A deploy stopped by the upload check below ends at error right away, and the command prints the files it flagged. The command exits with 0 on success and 1 on error.

It stops watching after 5 minutes and exits with 1, while the deploy keeps going. Use --timeout to wait a different number of seconds, or --no-wait to exit right after the upload.

podby deploy --static docs --timeout 900
podby deploy --pod api --no-wait

Deploy a pod

Run it from a folder with a Dockerfile at its top level. PodBy builds and runs it.

podby deploy --pod api

The folder is uploaded without .git and without anything your ignore file leaves out. Dockerfile.dockerignore takes precedence over .dockerignore when present. Files like .env or node_modules stay on your machine when the active ignore file lists them.

The app has to answer HTTP on the port in PORT, which is 8000 unless you set PORT in the pod's env. A new version gets 3 minutes to answer before the deploy fails, and the version already online keeps running until then.

The first deploy of a new pod name prints a payment link. Pay for the pod, then run the same command again.

Deploy an image

If you already build images, deploy one instead of a folder. It needs a linux/amd64 variant.

podby deploy --pod api --image ghcr.io/you/api:1.2.3

For a private registry, set PODBY_REGISTRY_USERNAME and PODBY_REGISTRY_PASSWORD when you deploy. A read-only token is enough. PodBy keeps the login, encrypted, for later deploys of that pod and only uses it with the registry it was given for. Remove it from the pod's page in the dashboard.

PODBY_REGISTRY_USERNAME=you PODBY_REGISTRY_PASSWORD=$GHCR_TOKEN \
  podby deploy --pod api --image ghcr.io/you/api:1.2.3

Deploying from a folder again switches the pod back to building its Dockerfile.

Persistent storage

Files written outside mounted volumes are ephemeral: they disappear when the app container is replaced, including on deployment. In the pod's dashboard settings, add the container paths that need persistent storage under Volume mount paths, one absolute path per line, up to 16.

/data
/app/uploads

Each path has its own volume, kept across deployments and stops. Removing a path from settings keeps its data; adding the same path again reconnects it. A different path gets a different volume. Adding a volume does not migrate files previously written outside volumes. Deleting the pod deletes all its storage. Managed PostgreSQL 18 and MySQL 8.4 databases already store their data in persistent volumes.

Deployments keep the old and new app versions running together briefly. Both can read and write the same volumes, so your app must support concurrent access. Single-writer storage such as SQLite may not be suitable. A failed deployment can still change shared data.

Upload check

Every static site upload and every pod folder is scanned with ClamAV before it goes online. If a file is flagged, the deploy ends at error without retrying, the version already online stays as it is, and the flagged files are listed in the command output and on the site or pod page. Fix or remove them and deploy again. Images are not scanned.

See usage

podby stats

Prints the latest usage sample for each site and pod as JSON: disk and egress, plus CPU and RAM for pods. A site or pod shows null until it has been sampled.

Names

Site and pod names use lowercase letters, digits, and hyphens, up to 63 characters. They start and end with a letter or digit.

Every request

Base URL
https://app.podby.com/api/v1
Version header, required
API-Version: 2026-10-11
Authentication
Authorization: Bearer <token>

Responses are JSON. A request without the version header, or with a version we do not know, gets a 400. Tokens last 30 days.

Get a token

Ask for a code, then trade it for a token. The account has to exist already.

curl https://app.podby.com/api/v1/login/email \
  -H 'API-Version: 2026-10-11' -H 'content-type: application/json' \
  -d '{"email":"you@example.com"}'

curl https://app.podby.com/api/v1/login/code \
  -H 'API-Version: 2026-10-11' -H 'content-type: application/json' \
  -d '{"email":"you@example.com","code":"123456"}'
# {"token":"..."}

Endpoints

POST /login/email
Body {"email"}. Emails a code that lasts 10 minutes. Returns {"ok": true}.
POST /login/code
Body {"email", "code"}. Returns {"token"}.
GET /sites
Your sites: name, status, hostname, paid until, usage.
GET /sites/{name}
One site, with its latest deploy, domains, and appPath, its page on https://app.podby.com.
POST /sites/{name}/deploys
Body is a zip of the site, sent as application/zip. Creates the site if the name is new. Replaces the site's files by default; add ?mode=append to keep the files already online and add or overwrite the ones in the zip.
GET /sites/{name}/deploys/{id}
One deploy of a site. Returns {"deploy": {"id", "status"}}, plus "error" when the upload check stopped it.
GET /pods
Your pods: name, status, hostname, paid until, usage.
GET /pods/{name}
One pod, with its latest deploy, domains, database, volumes (container mount paths), image when it runs one, and appPath.
POST /pods/{name}/deploys
Body is a gzipped tar of a folder with a Dockerfile at its top level, sent as application/gzip. Or, to run an image, {"image"} sent as application/json, with "registryUsername" and "registryPassword" for a private registry.
GET /pods/{name}/deploys/{id}
One deploy of a pod. Returns {"deploy": {"id", "status"}}, plus "error" when the upload check stopped it.
GET /usage
The latest usage sample for every site and pod, or null before the first one.

A deploy returns the resource, the deploy, and the resource's page on https://app.podby.com, for example {"site": {"name", "status", "hostname"}, "deploy": {"id", "status"}, "appPath"}. Uploads can be up to 200 MB.

A deploy's status is queued, deploying, success, or error. Check it with GET /sites/{name}/deploys/{id} or GET /pods/{name}/deploys/{id} until it is success or error. A failed attempt is retried up to three times in total, and shows as queued until the next one starts. An upload the check flags is not retried.

Errors

Errors come back as {"error": "code"}.

400
api_version_required or unknown_api_version for the version header. invalid_name, invalid_archive, invalid_mode, missing_dockerfile, invalid_image, invalid_body, or disk_limit when deploying. invalid_body or invalid_email when logging in.
401
unauthorized without a valid token. When logging in, invalid_code for a wrong or expired code, or missing when no account uses the email.
402
billing_required or payment_required. The response has a payPath: open it on https://app.podby.com to pay, then deploy again.
404
not_found for a site, pod, or deploy you do not have, or a path the API does not know.
405
method_not_allowed for a method the path does not take.
409
account_closing when the account is closing, name_in_use when a site name is already reserved on its server, or append_base_unavailable when append needs one successful full replacement first.
429
rate_limited. Too many login codes: five in ten minutes is the limit. Wait ten minutes.
413
body_too_large. Archive uploads are limited to 200 MB; JSON and ordinary form bodies are limited to 64 KiB.
500
internal_error. Something broke on our side. Try again, or email us.
503
upload_busy. Another upload or fleet update is in progress. Wait for the number of seconds in the Retry-After header, then retry.

Questions? Email help@podby.com.