Skip to content

Blueprint paas.toml

paas.toml is the single declarative blueprint for an app on PaaS Runtime — versionable, code-reviewable, and reproducible across environments. One file at the repo root captures language detection, processes, environment, add-ons, volumes, domains, scaling targets, resources, networking, healthchecks, sidecars, observability, and deploy strategy. The same blueprint that runs in dev runs in prod.

It plays the same role as Heroku's app.json or Render's render.yaml: instead of clicking through a dashboard, you declare intent in TOML and let the platform converge to that intent.

Schema versioning

Pin the spec version at the top of the file:

paas_spec_version = 1

A future major bump can refuse to load files that don't declare a compatible paas_spec_version, so blueprints don't silently get re-interpreted under new semantics. Today the field is optional and purely informative — files written before AC/22 keep parsing.

Strict validation

Two parsers cohabit:

Function Behaviour
parse_paas_toml(&Path) Returns None on missing file, malformed TOML, or unknown field. Used by the build planner — a typo shouldn't brick a deploy, the platform falls through to language sniffing.
validate_paas_toml_strict(&str) Returns Err(toml-error) with the offending key and approximate location. Used by paas check (CLI cycle 2) and any human-facing tool where "unknown field at line 12" is more actionable than silent fallback.

#[serde(deny_unknown_fields)] is enforced on the top-level PaasToml struct, so a mistyped section name (e.g. [scalign] instead of [scaling]) is caught — silently in the lenient path, loudly in the strict one.

Sections

[app] — identity

[app]
name   = "myapp"
region = "eu-west"
stack  = "ubuntu-22.04"

[build] — language + buildpack overrides

[build]
buildpack  = "paketobuildpacks/python"
build_args = { NODE_ENV = "production" }

build_args is forwarded to kaniko as --build-arg KEY=VAL (AA/09 Dockerfile path); buildpack overrides the auto-detected language (AA/08 detection).

[[processes]] — workloads

[[processes]]
type     = "web"
command  = "node server.js"
replicas = 2

[[processes]]
type    = "worker"
command = "node worker.js"

type = "web" gets the public ingress; worker/cron don't.

[env] — non-secret environment

[env]
NODE_ENV = "production"
PORT     = "8080"

[secrets] — secret backend pointer

[secrets]
provider = "vault"
path     = "secret/data/myapp"

[[addons]] — managed services

[[addons]]
type = "postgres"
plan = "standard-0"
name = "mydb"

[[addons]]
type = "redis"
plan = "shared-0"

[[volumes]] — persistent storage

[[volumes]]
name       = "data"
mount_path = "/data"
size       = "10Gi"

[[domains]] — public hostnames

[[domains]]
hostname = "myapp.example.com"
tls      = true

[scaling] — autoscaling targets

[scaling]
min_replicas       = 1
max_replicas       = 10
target_cpu_percent = 70

[resources] — pod requests / limits

[resources]
cpu          = "250m"
memory       = "256Mi"
cpu_limit    = "500m"
memory_limit = "512Mi"

[networking] — port + protocol

[networking]
port     = 8080
protocol = "http"
ingress  = true

[deploy] — rollout strategy

[deploy]
surge                 = 1
max_unavailable       = 0
timeout               = 600
readiness_probe_path  = "/healthz"

Defaults match the platform's hard-coded RollingUpdate strategy (AB/18). See Rolling Deploy.

[healthcheck] — readiness/liveness

[healthcheck]
path                   = "/healthz"
port                   = 8080
initial_delay_seconds  = 10
period_seconds         = 5

[[sidecars]] — co-located helpers

[[sidecars]]
name    = "envoy"
image   = "envoyproxy/envoy:v1.29"
command = "envoy -c /etc/envoy/envoy.yaml"

[security] — deploy gating on CVEs

[security]
block_deploy_on = "high"

"none" (default), "critical", or "high" — see CVE Scanning (AB/12).

[git_source] — git-push-deploy hint

[git_source]
repo         = "github.com/octave/myapp"
branch       = "main"
token_secret = "github-pat"

[observability] — metrics + logs

[observability]
metrics_path = "/metrics"
metrics_port = 9090
log_level    = "info"

log_level: debug / info / warn / error.

Complete example

paas_spec_version = 1

[app]
name   = "hello-paas"
region = "eu-west"

[build]
buildpack  = "paketobuildpacks/nodejs"
build_args = { NODE_ENV = "production" }

[[processes]]
type     = "web"
command  = "node server.js"
replicas = 2

[[processes]]
type    = "worker"
command = "node worker.js"

[env]
NODE_ENV = "production"
LOG_LEVEL = "info"

[secrets]
provider = "vault"
path     = "secret/data/hello-paas"

[[addons]]
type = "postgres"
plan = "standard-0"
name = "maindb"

[[volumes]]
name       = "uploads"
mount_path = "/data/uploads"
size       = "5Gi"

[[domains]]
hostname = "hello.example.com"
tls      = true

[scaling]
min_replicas       = 1
max_replicas       = 10
target_cpu_percent = 70

[resources]
cpu          = "250m"
memory       = "256Mi"
cpu_limit    = "500m"
memory_limit = "512Mi"

[networking]
port     = 8080
protocol = "http"
ingress  = true

[deploy]
surge                 = 1
max_unavailable       = 0
timeout               = 600
readiness_probe_path  = "/healthz"

[healthcheck]
path                   = "/healthz"
port                   = 8080
initial_delay_seconds  = 10
period_seconds         = 5

[security]
block_deploy_on = "high"

[observability]
metrics_path = "/metrics"
metrics_port = 9090
log_level    = "info"