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.
Command line
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.
API
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 onhttps://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=appendto 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),imagewhen it runs one, andappPath. - POST /pods/{name}/deploys
- Body is a gzipped tar of a folder with a
Dockerfileat its top level, sent asapplication/gzip. Or, to run an image,{"image"}sent asapplication/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
nullbefore 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_requiredorunknown_api_versionfor the version header.invalid_name,invalid_archive,invalid_mode,missing_dockerfile,invalid_image,invalid_body, ordisk_limitwhen deploying.invalid_bodyorinvalid_emailwhen logging in.- 401
unauthorizedwithout a valid token. When logging in,invalid_codefor a wrong or expired code, ormissingwhen no account uses the email.- 402
billing_requiredorpayment_required. The response has apayPath: open it onhttps://app.podby.comto pay, then deploy again.- 404
not_foundfor a site, pod, or deploy you do not have, or a path the API does not know.- 405
method_not_allowedfor a method the path does not take.- 409
account_closingwhen the account is closing,name_in_usewhen a site name is already reserved on its server, orappend_base_unavailablewhen 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 theRetry-Afterheader, then retry.
Questions? Email help@podby.com.