DinD HostingManual
Overview/Manual/Build & deploy
CHAPTER 05

platform.yaml reference

The manifest is optional — a bare source tree is detected by its files. Add platform.yaml when you need to choose a runtime, pin a version, attach a database or change how the app starts.

The file at a glance#

Put platform.yaml at the root of your source. Every key is optional; unknown keys are rejected so a typo fails the deployment instead of being silently ignored.

platform.yaml — all keys
runtime: auto            # auto | static | node | python | php
version: "22"            # runtime release: "22" | "22.12" | "22.12.1"  — always quote it
port: 3000               # pin the listen port (default: allocated for you, exposed as $PORT)
site_dir: .              # static only — folder to serve
spa: true                # static only — fall back to /index.html for unknown paths
docroot: public          # php only — web root inside the source
install_cmd: null        # override the install step
build_cmd: null          # override the build step (node)
start_cmd: null          # override the start command
env: {KEY: value}        # static environment, committed with the source
s3: true                 # inject S3_* with your personal bucket's credentials
databases: [main]        # attach project databases → DB_MAIN_HOST / PORT / USER / PASSWORD / NAME
resources: {cpu: "1.0", memory: 512m}
volumes: [{name: data, path: /app/data}]

Key reference#

Key Type · limits Default Applies to
runtime auto, static, node, python, php auto — detect from files all
version string of 1–3 dot-separated numbers, ≤ 24 chars per runtime, see versions all
port integer 1–65535 allocated from 10000–19999 per daemon all
site_dir relative path, no .. . static
spa boolean false static
docroot relative path, no .. public php
install_cmd string ≤ 2000 chars runtime default all
build_cmd string ≤ 2000 chars npm run build if the script exists node
start_cmd string ≤ 2000 chars runtime default node, python
env map; keys [A-Za-z_][A-Za-z0-9_]*, values become strings {} all
s3 boolean false all
databases list of ≤ 16 names [a-z0-9][a-z0-9_-]{0,39} [] all
resources.cpu number 0.01–64, as a string platform default all
resources.memory digits + b, k, m or g (512m) platform default all
volumes ≤ 8 × {name, path}; name [a-z0-9][a-z0-9._-]*, path absolute, no .. none all

port and $PORT#

The platform picks a free port in your daemon and gives it to your app as the PORT environment variable. Your app must listen on 0.0.0.0:$PORT. Set port: only when something needs a fixed number; if that port is taken in your daemon the deployment is refused.

resources#

resources limits this app's container. If you leave it out, the platform's default limits (set by an admin) apply. Use it to give a heavy build more memory — or to keep a small site small.

volumes#

yaml
volumes:
  - name: uploads
    path: /app/uploads

A volume is a named Docker volume in your daemon, mounted at path. It persists across deploys and recreates and is included in project backups. Everything outside a volume is thrown away with the container.

env, s3 and databases#

env is the lowest-effort place for non-secret constants. Real secrets belong in project variables. s3: true and databases: [name] make the platform inject credentials as S3_* and DB_<NAME>_* variables — see Databases and File storage.

Create the database first

databases: [main] only injects variables for a database that already exists in the project. If it does not, deployment still succeeds but the DB_MAIN_* variables are missing. Create the database, then deploy or recreate.

The four runtimes#

static

base
nginx:<v>-alpine (1.27)
detected by
index.html
serves
site_dir via nginx
extras
spa: true → fallback to /index.html; gzip on

node

base
node:<v>-alpine (24 LTS)
detected by
package.json
install
npm ci, falling back to npm install
start
npm run start, else server.js / index.js / dist/main.js

python

base
python:<v>-slim (3.12)
detected by
requirements.txt, pyproject.toml
install
pip install -r requirements.txt or pip install .
start
start_cmd, else uvicorn main:app / app.main:app

php

base
php:<v>-fpm-alpine (8.5) + nginx + supervisor
detected by
composer.json, public/index.php
install
composer install --no-dev when there is a composer.json and no vendor/
serves
docroot via fpm on a unix socket

Examples#

platform.yaml
runtime: static
site_dir: dist          # the folder your build produced and you committed or archived
spa: true               # client-side routing: unknown paths return index.html
platform.yaml
runtime: node
version: "24"
install_cmd: npm ci --include=dev   # the build below needs devDependencies (tsc)
databases: [main]
s3: true
resources: {memory: 512m}
package.json (excerpt)
{ "scripts": { "build": "tsc -p .", "start": "node dist/main.js" } }
platform.yaml
runtime: python
version: "3.12"
start_cmd: uvicorn app.main:app --host 0.0.0.0 --port $PORT
databases: [main]
requirements.txt
fastapi
uvicorn[standard]
psycopg[binary]
platform.yaml
runtime: php
version: "8.5"
docroot: public
volumes:
  - {name: storage, path: /var/www/app/storage}

NODE_ENV=production while installing

The generated Dockerfile sets NODE_ENV=production before npm ci, so npm skips devDependencies. If your build step needs them (TypeScript, Vite, a CLI), either move those packages into dependencies or set install_cmd: npm ci --include=dev.

Runtime versions#

A project chooses its runtime version, not only its runtime. The platform builds the Docker tag from a validated string, so node + "22" becomes node:22-alpine.

How a version is chosen#

  1. platform.yamlversion: "22" — always wins
  2. Source hintan exact or major version in a file you already have
  3. Platform defaultnode 24 · python 3.12 · php 8.5 · nginx 1.27

Recognised hint files (only a plain exact or major version — optionally with a leading v — counts; ranges like >=18 are ignored and mentioned in the build log):

Runtime Hint files
node .nvmrc, .node-version, package.json → engines.node
python .python-version
php composer.json → require.php

Accepted values#

22, 22.12 or 22.12.1 — one to three numbers separated by dots, at most 24 characters, digits only. Quote it.

YAML turns 3.10 into 3.1

Unquoted version: 3.10 is read by YAML as the number 3.1, which silently drops the zero. The platform therefore rejects floats and tells you to write version: "3.10". An unquoted whole number such as version: 22 is fine. Booleans are rejected too.

Fresh or pinned?#

Official Docker tags move — node:22-alpine is whatever 22.x is newest today. The platform handles that explicitly:

You wrote Behaviour
"22" or "22.12" (short) Floating. The base image is pulled again on every deploy, so a rebuild picks up security fixes. If the registry cannot be reached the build continues from the local copy and writes a warning to the log.
"22.12.1" (three parts) Pinned. Pulled only if it is not on the daemon yet — reproducible rebuilds.

Every build logs # base image: <ref> (<digest>), and the App tab shows the base image in use.

Defaults, and changing them#

Runtime Default Note
node 24 the newest Node.js LTS when the default was set
python 3.12
php 8.5
static (nginx) 1.27

Administrators can override a default for the whole platform with PLATFORM_DEFAULT_NODE_VERSION, …_PYTHON_VERSION, …_PHP_VERSION and …_STATIC_VERSION.

A new default moves existing apps

Apps that do not set version follow the default. When it changes, they move to the new version on their next rebuild — already-built images keep running until then. Pin version in anything that must not move.