Aramb TOML Generation
Analyze the project codebase and produce a valid aramb.toml.
Prerequisites
APPLICATION_ID must be set:
[ -n "$APPLICATION_ID" ] || { echo "ERROR: APPLICATION_ID not set"; exit 1; }
Every [[services]] block sets applicationId = "$APPLICATION_ID". The application already exists on the platform — no [[application]] block goes in the TOML.
Caller-Provided Context
The deployment skill passes:
| Parameter | Values | Meaning |
|---|---|---|
mode |
git / no-git |
git creates type="build" services for own backend code; no-git uses local-build placeholders. |
repoUrl |
https://github.com/… |
Set as repoUrl in every build service (git mode only). |
Step 1: Codebase Analysis
Scan for:
- Docker Compose: services, ports, env vars, volumes, database images
- Env files (
.env,.env.example): KEY=VALUE pairs → vars or secrets - Package files (
package.json,go.mod,requirements.txt,pom.xml,Gemfile): frameworks - Framework markers:
next.config.*,vite.config.*,angular.json,nuxt.config.* - Dockerfiles: build context paths, base images, CMD bind ports
Step 2: Service Type Mapping
| Detected Pattern | Service(s) | Build service targetType |
|---|---|---|
| Backend framework (Express, FastAPI, Gin, Django, Rails, …) | git: build + backend · no-git: backend only |
"backend" |
| Server-side frontend (Next.js, Nuxt, SvelteKit) | same as backend | "backend" |
| Static frontend (React/Vite, CRA, Angular, plain HTML) | git: build + frontend · no-git: frontend only |
"frontend" (+ staticOutDir) |
| Aramb agent code | build + aramb-agent |
"aramb-agent" |
| Database (postgres, redis, mongodb) | direct image |
n/a (no build service) |
| Pre-built / third-party container | direct image |
n/a (no build service) |
Supported service types: aramb-agent, backend, build, frontend, mongodb, onboarding, postgres, redis, template.
Step 3: TOML Structure Rules
uniqueIdentifier
- Start at 100, increment by 1. No gaps, no duplicates.
- Build service ID is less than its runtime service ID.
- Order services by dependency: lower IDs are dependencies of higher IDs.
Build services declare targetType
Every type="build" service sets targetType to the runtime type that consumes its outputs.IMAGE_URL ("backend", "frontend", "aramb-agent", or "template"). The build worker forwards this to aramb build --type …, taking the matching path deterministically (static OCI artifact for frontend; Docker/Railpack image for backend) regardless of any Dockerfile in the build path. The platform validates that the consuming runtime service's type matches the build's targetType.
When targetType="frontend", also set staticOutDir to the framework's build-output directory ("./dist" for Vite, "./build" for CRA, "./out" for Next static export, "./dist/<app>" for Angular).
Backend cmd is never set
The platform passes cmd as raw argv to Kubernetes with no shell wrapper, so && chaining and shell interpolation break. Backend services rely on the Dockerfile CMD (use shell form there for startup chains: CMD ["sh","-c","flask db upgrade && gunicorn -b 0.0.0.0:$PORT app:create_app()"]).
commandPort matches the in-container bind port
Read the Dockerfile CMD or app source — set commandPort to whatever port the process binds inside the container (gunicorn -b 0.0.0.0:5000 → 5000; app.listen(3000) → 3000). Mismatch = service starts but never receives traffic.
Variable references
| Syntax | Resolves To |
|---|---|
${N.vars.KEY} / ${N.secrets.KEY} |
Var / secret of service N |
${N.outputs.IMAGE_URL} |
Image URL from build service N (Docker image or static OCI artifact) |
${N.outputs.PRIVATE_URL} |
In-cluster URL of HTTP service N — http://<slug>.clode.internal:<port> |
${N.outputs.PUBLIC_URL} |
Public URL of HTTP service N — https://<slug>.proxy.clode.space |
${N.outputs.PRIVATE_HOST} |
Scheme-free in-cluster hostname of datasource service N — <slug>.clode.internal |
${N.outputs.PRIVATE_PORT} |
In-cluster port of datasource service N (e.g. 5432, 6379, 27017) |
Pick the right output by caller:
- HTTP service → datasource (backend → postgres / redis / mongodb): use
PRIVATE_HOSTandPRIVATE_PORTas separateDB_HOST/DB_PORTvars. Datasources don't speak HTTP, so wiringDB_HOST = ${100.outputs.PRIVATE_URL}won't work — every Postgres / Redis / Mongo client expects host and port as discrete inputs. - In-cluster HTTP → HTTP (backend → backend, SSR frontend → backend):
PRIVATE_URL. - Static frontend → backend (the browser fetches the API):
PUBLIC_URL. - Anything user-facing (OAuth callbacks, links, redirects):
PUBLIC_URL.
For HTTP services, PRIVATE_URL / PUBLIC_URL include the scheme and (for PRIVATE_URL) the port. Datasource services expose only the PRIVATE_HOST / PRIVATE_PORT atoms — public exposure for raw TCP wire protocols (5432, 6379, 27017) isn't wired today, so they have no PUBLIC_* outputs.
Vars vs secrets classification
Var (set actual value): hosts, ports, URLs, database names, usernames, env flags, feature toggles, timeouts.
Internal secret (app owns it — generate a placeholder so the service starts):
| Key pattern | Default value |
|---|---|
*PASSWORD, *PASSWD, *PWD |
"postgres" for DB, "change-me" otherwise |
JWT_*, *SIGNING_KEY |
"super-secret-jwt-key-change-in-production" |
SESSION_*, COOKIE_* |
"session-secret-change-in-production" |
APP_SECRET, SECRET_KEY |
"app-secret-key-change-in-production" |
External secret (third-party / OAuth — leave value = ""): STRIPE_SECRET_KEY, SENDGRID_API_KEY, GOOGLE_CLIENT_SECRET, SMTP_PASSWORD, webhook secrets, etc.
Keyword cues — secret if key contains: PASSWORD, PASSWD, PWD, SECRET, TOKEN, API_KEY, PRIVATE, CREDENTIAL, JWT, OAUTH. Var if it contains: HOST, PORT, URL, ENDPOINT, DATABASE, DB_NAME, DB_USER, ENVIRONMENT, DEBUG.
Any value referencing ${N.secrets.KEY} lives in [[secrets]], not [[vars]].
ALLOWED_ORIGINS
Every backend and every frontend service includes:
[[services.configuration.vars]]
key = "ALLOWED_ORIGINS"
value = "https://*.proxy.clode.space"
Comma-append more origins when needed; the proxy wildcard is always present. The recipient (CORS middleware, Vite host whitelist, etc.) reads it from env — see the dev-workflow skill.
Step 4: TOML Template — Git Mode
# === DATABASE — public image ===
[[services]]
uniqueIdentifier = 100
name = "postgres-db"
type = "postgres"
description = "PostgreSQL database for application data"
applicationId = "$APPLICATION_ID"
[services.configuration.settings]
image = "postgres:15"
commandPort = 5432
publicNet = false
[[services.configuration.vars]]
key = "POSTGRES_DB"
value = "myapp"
[[services.configuration.vars]]
key = "POSTGRES_USER"
value = "postgres"
[[services.configuration.secrets]]
key = "POSTGRES_PASSWORD"
value = ""
# === BACKEND — own codebase (build + runtime) ===
[[services]]
uniqueIdentifier = 101
name = "backend-build"
type = "build"
description = "Build service that produces the backend API image"
applicationId = "$APPLICATION_ID"
[services.configuration.settings]
repoUrl = "<repoUrl from caller>"
buildPath = "./backend"
targetBranches = ["main"]
installationId = "123456789"
targetType = "backend"
[[services]]
uniqueIdentifier = 102
name = "backend-api"
type = "backend"
description = "Backend API service handling business logic and data persistence"
applicationId = "$APPLICATION_ID"
[services.configuration.settings]
image = "${101.outputs.IMAGE_URL}"
commandPort = 8080
publicNet = true
[[services.configuration.vars]]
key = "PORT"
value = "8080"
[[services.configuration.vars]]
key = "ALLOWED_ORIGINS"
value = "https://*.proxy.clode.space"
[[services.configuration.vars]]
key = "POSTGRES_HOST"
value = "${100.outputs.PRIVATE_HOST}"
[[services.configuration.vars]]
key = "POSTGRES_PORT"
value = "${100.outputs.PRIVATE_PORT}"
[[services.configuration.vars]]
key = "POSTGRES_USER"
value = "${100.vars.POSTGRES_USER}"
[[services.configuration.vars]]
key = "POSTGRES_DB"
value = "${100.vars.POSTGRES_DB}"
[[services.configuration.secrets]]
key = "POSTGRES_PASSWORD"
value = "${100.secrets.POSTGRES_PASSWORD}"
[[services.configuration.secrets]]
key = "JWT_SECRET"
value = ""
# === FRONTEND — static assets (build + runtime) ===
# targetType="frontend" + staticOutDir tell the build worker to
# invoke `aramb build --type frontend --static-outdir <staticOutDir>`,
# producing a static.tgz OCI artifact regardless of any Dockerfile in the
# build path. Dockerfiles for local docker-compose previews stay intact.
[[services]]
uniqueIdentifier = 103
name = "frontend-build"
type = "build"
description = "Build service that produces the frontend static OCI artifact"
applicationId = "$APPLICATION_ID"
[services.configuration.settings]
repoUrl = "<repoUrl from caller>"
buildPath = "./frontend"
targetBranches = ["main"]
installationId = "123456789"
targetType = "frontend"
staticOutDir = "./frontend/dist"
[[services]]
uniqueIdentifier = 104
name = "frontend-web"
type = "frontend"
description = "Frontend web application serving the React/Vue/Angular UI"
applicationId = "$APPLICATION_ID"
[services.configuration.settings]
image = "${103.outputs.IMAGE_URL}"
staticPath = "./frontend/dist"
[[services.configuration.vars]]
key = "API_URL"
value = "${102.outputs.PUBLIC_URL}"
[[services.configuration.vars]]
key = "ALLOWED_ORIGINS"
value = "https://*.proxy.clode.space"
No-Git Mode — delta from above
Apply these changes to the git-mode template; everything else stays identical:
- Drop the
backend-buildandfrontend-buildblocks (alltype="build"services). - In the backend runtime block, replace
image = "${101.outputs.IMAGE_URL}"withimage = ""(filled locally byaramb build ./backend --type backend --service {slug} --push). - In the frontend runtime block, replace
image = "${103.outputs.IMAGE_URL}"withimage = ""(filled locally byaramb build ./frontend --type frontend --static-outdir ./frontend/dist --service {slug} --push). - Renumber
uniqueIdentifierto stay sequential without the build services (backend becomes 101, frontend becomes 102). - Update the frontend's
API_URLreference to the new backend ID (${101.outputs.PUBLIC_URL}).
Updating an Existing aramb.toml
- Read the file.
- Skip any service whose
namealready exists. - Preserve existing
uniqueIdentifier,id, andslugvalues exactly. - Don't modify any service that already has
idorslugset — it is already deployed. - Continue the
uniqueIdentifiersequence from the highest existing value + 1.
Validation Checklist
APPLICATION_IDis set; no[[application]]block in the file.- Every
[[services]]block hasapplicationId = "$APPLICATION_ID",name,type,description. - All
typevalues are from the supported list. uniqueIdentifieris sequential, no gaps, no duplicates; every build ID is less than its runtime ID.- All
${N.vars.KEY}/${N.secrets.KEY}/${N.outputs.KEY}references point to real services and keys. No circular dependencies. - Any value referencing
${N.secrets.KEY}lives in[[secrets]]; no hardcoded sensitive values (""or a${…}reference only). - Backend services omit
cmdand havecommandPortmatching the Dockerfile bind port. - Static frontends (
type="frontend") haveimage = ""andstaticPathset; image is filled by a local--static-outdirbuild. - Static frontends reference backends via
${backend-id.outputs.PUBLIC_URL}; SSR frontends (type="backend") and other backends reference in-cluster HTTP services via${N.outputs.PRIVATE_URL}. Datasource consumers (backend → postgres / redis / mongodb) wireDB_HOST = ${N.outputs.PRIVATE_HOST}andDB_PORT = ${N.outputs.PRIVATE_PORT}— neverPRIVATE_URLfor a database. - Every backend and every frontend service has an
ALLOWED_ORIGINSvar that includeshttps://*.proxy.clode.space. - Every
type="build"service declarestargetTypematching its consuming runtime ("backend","frontend","aramb-agent", or"template"). WhentargetType="frontend",staticOutDiris also set. - Git mode: every build service has
repoUrl,buildPath,targetBranches,installationId. No-git mode: all own-codebase services haveimage = "".
Error fallbacks: no services detected → minimal template (one postgres + one backend); unknown framework → type = "template"; circular dependency → break the cycle; Docker Compose parse failure → fall back to codebase-only analysis.