Skip to content

Deploying

New preview takes a folder, files or a .zip, a runtime’s example to start from, and throwaway databases to run beside it.

The New preview page: runtime starters, a drop zone for a folder, files or a .zip, and Postgres, MySQL and Redis add-ons.The New preview page: runtime starters, a drop zone for a folder, files or a .zip, and Postgres, MySQL and Redis add-ons.

Once it is up, the preview’s page keeps its files. Edit one, add one or drop in a new set, then Save & rebuild updates the preview at the same URL. The old version serves until the new one is built.

A preview's Source panel: the file list, the Dockerfile open in the editor, a Rebuild as choice and a Save & rebuild button.A preview's Source panel: the file list, the Dockerfile open in the editor, a Rebuild as choice and a Save & rebuild button.

Make a token under Account → API tokens. deploy is the scope CI needs:

API tokens on the Account page: a name, an expiry, the scopes from read to admin, and a token in the list with its scopes and a Revoke button.API tokens on the Account page: a name, an expiry, the scopes from read to admin, and a token in the list with its scopes and a Revoke button.

Send a tarball to the REST API with it:

Terminal window
tar -czf - . | curl --fail -X POST \
-H "Authorization: Bearer $GANGWAY_TOKEN" -H "Content-Type: application/gzip" \
--data-binary @- "https://api.preview.example.com/v1/previews?name=hello&runtime=auto&wait=true"

With wait=true the answer comes once the preview serves, and includes its URL and id. Each POST makes a new preview. To update one in place, at the same URL, send the new tarball to its source:

Terminal window
tar -czf - . | curl --fail -X PUT \
-H "Authorization: Bearer $GANGWAY_TOKEN" -H "Content-Type: application/gzip" \
--data-binary @- "https://api.preview.example.com/v1/previews/$ID/source"

With runtime=auto, gangway looks at the files at the root, in this order, and the first match decides:

Files Runs as
compose.yaml (or .yml, docker-compose.*), or a Dockerfile your own stack
composer.json PHP
wrangler.toml (or .json, .jsonc) workerd
deno.json (or .jsonc) Deno
bun.lock, bun.lockb or bunfig.toml Bun
package.json Node
requirements.txt, main.py or app.py Python
index.php PHP
index.ts, main.ts, worker.ts or src/index.ts Bun
anything else a static site, served by gangway itself

An artifact.md with no index.html renders as a document, deck, dashboard or prototype.

A server listens on $PORT. If gangway cannot tell how to run something, it refuses the deploy and says which file would settle it. New preview shows this plan as soon as files are dropped, before anything is built:

The plan for a dropped Node.js app, one line per decision and the file that made it, with a Build it as choice and Deploy and Clear buttons.The plan for a dropped Node.js app, one line per decision and the file that made it, with a Build it as choice and Deploy and Clear buttons.

A gangway.yml at the root says what detection cannot:

runtime: node # static, node, bun, deno, workerd, python or php
version: 22
root: app # a subfolder to run from
install: npm ci # or false
build: npm run build # or false
start: node server.js
release: npm run migrate # before each version goes live
port: 3000
healthcheck: /healthz
env:
FEATURE_FLAG: "on"
addons: [postgres, redis] # or mysql
ttl: 7d # how long the preview lives
idle: 30m # or never
visibility: unlisted # public, unlisted or private

Every key, its default and the Compose equivalent, x-gangway:, are in the gangway.yml reference.

Add-ons run beside the app for as long as the preview lives:

Add-on The app gets
postgres DATABASE_URL, POSTGRES_URL and the PG* variables
mysql MYSQL_URL (8.4; about 20s to start the first time)
redis REDIS_URL (append-only, so it survives a sleep)

seed.sql or db/seed.sql loads into Postgres or MySQL on its first start. The preview page has a data browser.

  • Time to live. Every preview expires. Extend it from its page, or ask your agent to.
  • Sleep. An idle preview sleeps and the first request wakes it.
  • Visibility. Public, unlisted (an unguessable hostname), private (people signed in to gangway), and optionally a password.

A preview’s page shows its URLs, its state and visibility, and when it expires:

A preview's page: its title, awake and public, a Destroy button, its URL, and particulars such as source, policy, expiry and host.A preview's page: its title, awake and public, a Destroy button, its URL, and particulars such as source, policy, expiry and host.

What a preview gets when nothing more specific is said comes from a preview policy in Admin → Previews. There is one policy each for pull requests, API and CI deploys, and the deploy screen, and a repository can pick its own:

Preview policies in Admin: the default policy for pull requests, API and CI, and the deploy screen, and the Default policy's visibility, TTL, sleep-after, secrets and host.Preview policies in Admin: the default policy for pull requests, API and CI, and the deploy screen, and the Default policy's visibility, TTL, sleep-after, secrets and host.