Deploy a backend API with secrets and autoscaling
A Node.js API from zero to production: explicit start command, secrets from AWS Secrets Manager, autoscaling, and canary rollouts. The same flow works for Python and Go — variants at the end.
1. The app
Any Node server works; the only requirement is that you know how it
starts — Pagifier never reads package.json:
const express = require("express");
const app = express();
app.get("/health", (_, res) => res.send("ok"));
app.get("/", (_, res) => res.json({ hello: "pagifier", db: !!process.env.DATABASE_URL }));
app.listen(3000);
2. The file, explained line by line
#:schema https://pagifier.company.com/v1/schema/pagifier.json
version = 1
name = "orders-api"
environment = "prod"
runtime = "node"
build = ["npm ci --omit=dev"] # runs verbatim in the toolchain image
[runtimeConfig]
command = "node src/server.js" # mandatory — never guessed
port = 3000 # mandatory — never guessed
[ingress]
host = "orders.company.com"
healthCheck = "/health" # readiness + liveness probes
[env]
NODE_ENV = "production"
[secrets] # referenced, never stored
provider = "aws"
[secrets.keys] # ENV_VAR = "remote key"
DATABASE_URL = "prod/orders/database-url"
[autoscaling]
enabled = true
minReplicas = 3
maxReplicas = 30
cpu = 70
What each block buys you:
runtime = "node"+runtimeConfig— your app runs with exactly this command on exactly this port. A missing command is a400at upload, not a broken pod at 2 a.m.secrets— Pagifier renders an ExternalSecret that pullsprod/orders/database-urlfrom AWS Secrets Manager intoDATABASE_URL, refreshed hourly. The value never touches Pagifier's API or your repo. (details)autoscaling— an HPA between 3 and 30 replicas at 70% CPU.healthCheck— no traffic reaches a pod until/healthreturns 200.
If a template exists (template = "express"), the build and
runtimeConfig blocks usually come from it and your file shrinks to
five lines.
3. Deploy
zip -r app.zip . -x '.git/*' -x 'node_modules/*'
curl -H "Authorization: Bearer $PAGIFIER_TOKEN" \
-F config=@pagifier.toml -F archive=@app.zip \
"https://pagifier.company.com/v1/applications/orders-api/environments/prod/deployments?wait=true"
curl https://orders.company.com/
{"hello":"pagifier","db":true}
"db": true — the secret arrived without ever being written down
anywhere.
4. Canary rollouts (probably already on)
If your platform's prod profile enables canaries (most do), your second deploy behaves differently — watch the status while it runs:
curl -H "Authorization: Bearer $PAGIFIER_TOKEN" \
"https://pagifier.company.com/v1/applications/orders-api/environments/prod" | jq .application.canary
{"release": "orders-api-r20260714-…", "weight": 10}
The new version takes 10% of traffic, then 50%, advancing only while error-rate and latency gates pass; a bad release aborts itself and the old version never stopped serving. You did nothing to get this. (how it works)
Python and Go variants
Same file, different runtime — the rules never change (explicit command, explicit port, no manifest inspection):
version = 1
name = "ml-api"
template = "fastapi" # brings pip install + uvicorn + /health probe
environment = "prod"
ingress = "ml.company.com"
version = 1
name = "payments"
template = "go-api" # compiles a static binary, ships ONLY bin/
environment = "prod" # into a distroless image
ingress = "payments.company.com"
Without templates, spell it out — e.g. Go by hand:
runtime = "go"
build = ["mkdir -p bin", "CGO_ENABLED=0 go build -o bin/server ."]
artifact = "bin" # sources never enter the image
[runtimeConfig]
command = "/app/server"
port = 8080