On this page
Config as code
A project can live in a file. anyport.yaml names the apps, managed services, secrets (by name
and key), scheduled tasks, variables and domains a project is made of, and anyport apply makes
a project match it. The same file serves development, staging and production: where it runs is
an argument to apply, not a line in the file.
apiVersion: anyport.dev/v1alpha1
kind: Project
name: demo
environment: staging
variables:
REGION: eu
CLERK_SECRET_KEY: { secret: true, description: "Clerk backend key" }
SESSION_SECRET: { secret: true, generate: true }
secrets:
- name: creds
keys: [API_KEY]
- name: session
generate: [APP_SECRET]
services:
- name: db
type: postgres
config:
version: "17"
apps:
- name: api
image: ghcr.io/acme/api:1.4.0
replicas: 2
ports:
- { name: http, port: 8080 }
env:
- { name: REGION, value: "${{ REGION }}" }
- { name: CLERK_SECRET_KEY, value: "${{ CLERK_SECRET_KEY }}" }
- { name: DATABASE_URL, fromService: { service: db, key: DATABASE_URL } }
- { name: API_KEY, fromSecret: { secret: creds, key: API_KEY } }
probes:
readiness: { path: /healthz, port: 8080 }
domains:
- host: api.acme.dev
publicPorts:
- { port: 20017, target: 50051 }
- name: web
source:
repo: acme/web
branch: main
ports: [{ port: 3000 }]
tasks:
- name: nightly
app: api
command: ["python", "manage.py", "cleanup"]
schedule: "0 3 * * *"The full format is published as a JSON Schema at /schema/anyport.json.
Point your editor at it with a # yaml-language-server: $schema=https://anyport.dev/schema/anyport.json
line at the top of the file and it completes and checks every key. Unknown keys are refused, not
ignored: a misspelled setting that silently did nothing is the failure this exists to prevent.
A variable is a plain string, or an object when it is secret: { secret: true } declares the
name and nothing else. The value is set with anyport project variable set NAME=… --secret or in
the console, and never written to the file; a file that carries one is refused before anything is
read. generate: true is for a value that only needs to exist, a session key or a signing
secret: apply mints it the first time and never overwrites it. A plain variable is written in
clear, so the export warns when a plain one is named like a credential (STRIPE_KEY,
DB_PASSWORD), and anyport project export --strict fails instead, for a job that commits the
file.
publicPorts opens raw TCP or UDP ports beside the hostnames (see Domains).
via is left out on purpose: the console picks what the cluster serves for free, and on a
managed cloud where a load balancer costs money it refuses to guess until you write
via: loadBalancer or via: nodePort.
Scripts#
The file can also carry scripts: commands a developer runs on their own machine with an app's
environment, as anyport run <name>, the way package.json holds npm scripts. They are not part
of the project, so plan, apply and the sync from your repository ignore them, the console's
copy of the file leaves them out, and anyport project export -o anyport.yaml keeps the section
of the file it overwrites. See Save commands as scripts for
the format and which value wins when a script overrides the app's.
Start from a project you have#
anyport project export demo -o anyport.yamlwrites a running project as a file. That is the fastest way to adopt this, and it is also how the format is kept honest: everything the console can build has to come back out of an export, and a test in the codebase fails when a setting exists that the file cannot say.
See what would change#
anyport plan -f anyport.yamlProject demo on prod (update)
ACTION RESOURCE DETAIL
blocked secret/creds secret "creds" lacks API_KEY; set it first: anyport secret set creds API_KEY=… -p demo
blocked variable/CLERK_SECRET_KEY variable "CLERK_SECRET_KEY" has no value; set it first: anyport project variable set CLERK_SECRET_KEY=… -p demo
create variable/SESSION_SECRET secret, generated on first apply
update app/api env, replicas
create task/nightly
delete variable/OLD_TOKEN secret; the value cannot be recovered
unmanaged app/legacy in the project but not in the file; left alone (pass prune to remove)
noop service/db
2 to create, 1 to update, 1 to remove, 1 unchanged, 1 in the project but not in the file.
Has to happen first:
- secret "creds" lacks API_KEY; set it first: anyport secret set creds API_KEY=… -p demo
- variable "CLERK_SECRET_KEY" has no value; set it first: anyport project variable set CLERK_SECRET_KEY=… -p demo
Nothing is changed by plan. The rows are what apply would do, in the file's own vocabulary.
Apply#
anyport apply -f anyport.yaml
anyport apply -f anyport.yaml --cluster staging # a new project lands hereapply shows the plan, asks, and then works in dependency order: the project, then its managed
services, then apps, tasks and domains. Three rules decide what it will and will not do:
- Secret values never enter the file, so apply never writes a secret. A file declares which
secrets, keys and secret variables the project needs; apply checks each has a value and stops
if one does not, naming the
anyport secret setoranyport project variable setcommand that fixes it. Everything before that point is applied, and runningapplyagain finishes the job. The one exception isgenerate: a signing key or a session secret needs a value, not a particular one, so apply mints a random one the first time and never overwrites it. - Apply never deletes by omission. A resource in the project that the file does not mention
is reported as unmanaged and left alone.
--pruneremoves unmanaged apps, tasks and domains. Managed services are never removed by apply, prune or not: an uninstall destroys data and has its own confirmation in the console and inanyport service uninstall. - What the file owns, it owns completely. A key removed from an app in the file is removed
from the app, and a variable removed from
variablesis removed from the project — a secret one too, and the plan says so, because its value cannot be recovered. Settings the file does not carry (whether a project is paused, for one) are not touched, and a file with novariablessection at all says nothing about them.
In the console#
Project → Settings → Config as code shows the file for a project, to copy or download, and takes a pasted file: the same plan, then apply. Projects → New project → Create from file starts a project from one, with the cluster chosen in the dialog.
Kept in sync from the repository#
Once an anyport.yaml is on the branch an app deploys from, the console applies it on every
push that changes it, before the build that push triggers. So a pull request that changes the
file and the code together deploys both, in that order, and the repository is the place the
project is described. The rules are the ones above: nothing is deleted by omission (a push is
never a prune), and a missing secret stops the apply where it is, with the reason in the
repository's deployment log next to the build.
The file has to name the project the app deploys into; a file that names another project is left alone, so two apps in one repository deploying into two projects cannot rewrite each other's. In a monorepo the file is looked for in the app's build directory first, then at the root.
A pull request's preview gets the pull request's own file: applied into the preview when it is created, and on every push after. Hostnames and public ports in the file stay with the base project, since a preview is reached on generated hostnames of its own, and the preview's name and variables are the preview engine's.
What goes in the file, and what does not#
| In the file | Not in the file |
|---|---|
| Apps: image or source, ports, env, resources, autoscaling, probes, volumes, sidecars, pull secret, which projects may reach it | The cluster: pass --cluster, or let a new project land on the org default |
| Managed services with their configuration | Secret values: set with anyport secret set or anyport project variable set, or in the console |
| Secrets by name and the keys the project reads, and the keys apply may generate | Whether a project or app is paused |
| Tasks: schedule, command, the app whose image and env they borrow | Preview settings, which live on the base project in the console |
Variables and their use as ${{ NAME }}; a secret variable by name, with generate when apply may mint it | Anything a build produced: a source-built app is described by its repository, never by the image its last build pushed |
| Domains, on the app that serves them | How a database was created: a restore's source and point in time |
A database restored from a backup records where it came from (restoreFrom,
restoreServerName, restoreTargetTime, restoreBasePath). Those markers are managed by the
console's restore flow and are not part of the manifest: an export leaves them out, and a file
that still carries them (an older export, or written by hand) has them ignored, and the plan
row names them. Otherwise applying the file to a new project would create a database that
recovers from the old one's backups instead of an empty one. To bring data across, restore from
a backup in the console.
A source-built app carries source instead of image. Applying it creates the app and its build
from the repository the same way "deploy from GitHub" does, and a push to the branch keeps it
current from then on.
Next steps#
- CLI — Export, plan and apply from a terminal or a CI job.
- Deploy from GitHub — The build a
sourceapp gets, and the push that keeps it current. - Templates & duplication — Copy a live project from the console instead of a file.