Deploying apps

Paas deploys any application that builds into a Docker image from a Dockerfile at the repo root, listens on a single TCP port, and serves HTTP.

The contract your app must satisfy

  1. Dockerfile at the repo root. Buildpacks are not in v1.
  2. Listen on the port the platform tells you. Default is 8080, set when you create the app and changeable later. The platform sets PORT in the environment, so prefer that over hardcoding.
  3. Bind to 0.0.0.0, not 127.0.0.1. The container's IP is reached from outside the container by nginx.
  4. Respond < 500 to GET / within 30 seconds of starting. That's the default health probe. (To use a different path, configure Orchestrator__HealthPath in the orchestrator service.)

Triggering a deploy

Three ways:

How When
git push paas main Normal flow. New commit → new deployment.
Dashboard → app → Redeploy current Same commit, new container (e.g. to pick up env-var changes).
POST /api/orgs/{org}/apps/{app}/deployments/redeploy API equivalent.

Only main (well, any branch, but the default-branch convention) triggers a deploy. Tag pushes are ignored.

Setting environment variables

Dashboard → app → Environment variables, or:

TOKEN=<your jwt>
curl -X PUT https://paas.example.com/api/orgs/acme/apps/web/env/DATABASE_DEBUG \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"value":"true"}'

Env-var changes do not auto-deploy — they apply on the next deploy or redeploy. This is intentional: env changes shouldn't silently alter behavior for users who didn't ship a release.

Reserved env vars Paas always injects:

Var Value
PORT The port your app must listen on (default 8080).
PAAS_APP App slug.
PAAS_RELEASE Release UUID, useful for log tagging.
DATABASE_URL Set automatically if a managed DB named main exists.

Build context

The Builder runs docker build against the entire repo at the pushed commit. Use a .dockerignore to keep your image lean — node_modules, .git, build outputs, etc.

The build container has a Docker socket (so it can talk to the engine) and runs in its own container. You don't get the host's Docker config — if your build needs private base images, set credentials via build-time secrets in your Dockerfile (BuildKit supports this) and store them as env vars on the app, then pass via --secret.

Build logs

Each deployment's build log is captured and shown in the dashboard:

acmeweb → click a Deployment ID → see the streamed docker build output. Logs are persisted in the build_log_lines table so you can scroll through old builds.

Common gotchas

  • 502 right after push. The new container is starting; the proxy hasn't swapped yet. Wait ~10s.
  • Container starts then immediately restarts. Likely your app is listening on 127.0.0.1. Bind to 0.0.0.0:$PORT.
  • Build is slow. First builds pull base images. Subsequent builds reuse layers as long as your Dockerfile order is layer-cache-friendly (deps before source, etc.).
  • DATABASE_URL not appearing. The managed DB has to be named exactly main and have status Ready before the next deploy.

Rolling back

A "rollback" is just redeploying an older deployment. From the API:

curl -X POST https://paas.example.com/api/orgs/acme/apps/web/deployments/redeploy \
  -H "Authorization: Bearer $TOKEN"

(Rollback to an arbitrary previous SHA is a v1.1 feature — for now, redeploy re-uses the most recent live one. As a workaround, push the old commit on top of main.)