Skip to main content

App Hosting Release Notes

Newest first. This records behaviour changes as well as additions: a fix that changes what you should expect is often more useful to know about than a new field.

Build from source

Deploy a git repository or an uploaded source tarball and the platform builds the image for you, on your app's own VM, and pushes it to the platform registry. You supply source; you no longer need a registry, a CI pipeline, or an image build of your own.

  • Automatic builder detection. A repository with a Dockerfile builds with it; one without builds with Cloud Native Buildpacks. source.builder forces either.
  • The image is the build output. A source-built app takes no image_ref: each successful build writes its digest reference into the app's configuration and the normal blue/green deploy consumes it. A failed build never disturbs the version currently serving.
  • Deploy keys for private repositories. Write-only, never returned by a read, preserved when omitted on update, and cleared if the repository changes so a key is never carried onto a repository it cannot authenticate. Credentials embedded in a repository URL are rejected, because the URL is recorded on every build and returned by the builds API.

See Build from Source and the Deploy from Git tutorial.

Deploy from a local directory

POST /app-source-uploads takes a gzipped source tarball and returns an upload_ref that an app create names with source.type upload. This covers code the platform cannot clone: a private repository you would rather not issue a key for, a monorepo subtree, or a directory not under version control at all.

Send the tarball as the raw request body with a binary content type (application/octet-stream or application/gzip). Compare the returned checksum_sha256 against your own: the builder verifies the same value after fetching, so a corrupted transfer fails with that cause instead of surfacing later as an unexplained tar error.

Uploads expire 24 hours after creation. They are a hand-off to a build, not storage.

Push to deploy

Mint a webhook secret, configure the webhook on your git host, and set auto_deploy. A verified push on the tracked ref rebuilds and redeploys.

Every request is verified against your secret before anything happens: a git host presents no credentials, so the signature is the authentication. Pushes to other branches are acknowledged and ignored, as is a branch deletion, since building the previous head would deploy code the author just removed.

The secret is shown once, at mint time. Minting a new one replaces the old, which is also how a leaked secret is revoked.

Behaviour change worth knowing. A rebuild requested while a build is already running used to be refused, and the pushed commit was then never built: the app kept serving the previous image until somebody noticed. It is now recorded and run when the current cycle finishes. It coalesces rather than queues, so many pushes during one build produce one rebuild of the newest source, not one build per superseded commit.

Build history, logs and statistics

  • Every attempt is recorded, succeeded or failed, with its trigger (manual, push, config, api), source type, resolved commit and image digest.
  • Live build logs. Poll a build's log with the cursor the previous response returned and receive only what has arrived since, so a long build can be followed as it runs. The response also carries an ordered step log; a failed step names the phase and carries the tail of the builder's own output.
  • Statistics. Success rate over finished builds, and duration percentiles over succeeded builds only, since a failed build's duration measures how long it took to give up and averaging the two together would make a run of fast failures look like an improvement.
  • Image size and cache use are recorded per build. Both are reported only once a build has actually measured them: builds predating the measurement show nothing rather than a confident zero.
  • Retention trims build log payloads rather than deleting build rows. A build row records what was deployed and is a rollback target, so it is kept at any age; a build whose logs were reclaimed says so, which is distinct from a build that never logged.

Faster rebuilds

Dockerfile builds now reuse cached layers between builds, so a rebuild of unchanged source is substantially faster than the first build. The cache is kept for a week and reclaimed automatically when the VM's disk gets tight, since a cold rebuild costs a little time while a full data disk would take your running container down.

Builds are bounded

Because a build runs on your app's own VM, beside your serving container, an unbounded build is not merely a slow build. Dockerfile builds now run under an explicit memory cap (a share of the VM, leaving the remainder for your container) and a process cap of 4096.

A build that exceeds a limit fails with that cause, which is recoverable, instead of starving or evicting the app it was updating. If you hit these, the answer is usually a larger plan or a more restrained build (a bounded make -j) rather than a retry.

Node listing

GET /app-services/{id}/nodes returns the VMs behind an app, including the agent build running on each. Agent behaviour ships in a binary on the VM, so this is what tells you which build is actually serving your app.

Only the floating IP is reported. The VM's baseline public address and its utility address are infrastructure detail, not properties of your service.

App-to-app private networking

An app can attach to another app over private networking (east-west). The attachment injects {PREFIX}_HOST, {PREFIX}_PORT and {PREFIX}_URL only: there are no credentials and no DATABASE_URL, because an app target is a network address rather than a datastore.

An app that listens on port 80 cannot be an east-west target: 80 is reserved for the public HTTPS ingress, so such a target needs a dedicated container port.

Fixed. The console listed an app attachment as a bare identifier with no icon or link, and showed the database credential variables that an app target never injects, telling you to expect environment variables your container would not receive. The attach picker's app group was empty for the same reason. Both resolved app targets through the databases list, which does not contain apps.