Project configuration manifest
volcano-config.yaml is the declarative manifest for a project's user-facing configuration.
volcano-config.yaml is the declarative manifest for a project's user-facing
configuration. The CLI uploads it with volcano cloud config deploy, downloads
the current state with volcano cloud config pull, and the dashboard offers the
same download on the Projects page. Without cloud, both commands target local
development. All reconciliation happens server-side through
two endpoints:
GET /projects/{id}/config— export current configuration (JSON, or canonical YAML withAccept: application/yaml/?format=yaml)PUT /projects/{id}/config— validate and apply a manifest (?dry_run=truereports projected actions without changing anything)
The manifest applies to the currently selected project (volcano use <name> /
VOLCANO_PROJECT_ID). Creating or selecting a project is not part of the
manifest.
GitHub auto-deploy reads the same file from your repository,
but applies only the functions[] settings that belong to the code a push
deploys: visibility, invocation_mode, http_auth_mode, openapi_spec,
variable_scope, and variables. Every other section still needs volcano cloud config deploy.
Full schema (version 1)
version: 1
project:
name: my-app # optional rename
all_regions: false
selected_regions: [us-east-1, us-west-2] # bare region names; subset requires SUPERAGENT
databases: # must already exist; assertion-only
- name: appdb # required, matches deployed database
region: us-east-1 # required; mismatch => error
pg_version: '18' # required; mismatch => error
database_type: volcano-db-xs # asserted; tier changes are NOT allowed via config
frontend_shared_variables: # complete list for shared frontends
- NEXT_PUBLIC_API_URL
variables: # ALWAYS fully synced when declared
- name: STRIPE_SECRET_KEY
value: ${STRIPE_SECRET_KEY} # ${ENV} interpolated by the CLI
- name: NEXT_PUBLIC_API_URL
value: https://api.myapp.com
buckets: # must already exist (never created here)
- name: avatars
file_size_limit: 5242880 # bytes, optional
allowed_mime_types: [image/png, image/jpeg]
policies: # fully synced when declared; omit the key => policies untouched
- name: public-read
operation: SELECT # SELECT|INSERT|UPDATE|DELETE
definition: 'true'
realtime:
enabled: true
broadcast_enabled: true
presence_enabled: true
postgres_changes_enabled: true
notifications: # omitted fields keep their value
email:
enabled: true
recipients: [] # the project owner; no other address yet
webhook:
url: https://ops.myapp.com/hooks/volcano # "" removes it; the secret is never in the manifest
scheduler_failures:
notify_on: consecutive_failures # first_failure | consecutive_failures
threshold: 3 # 1-100
notify_on_recovery: true
auth:
tokens:
access_token_lifetime: 3600
refresh_token_lifetime: 2592000
refresh_token_reuse_interval: 10
platform_token_ttl: 2592000
sessions:
inactivity_timeout: 0
max_session_duration: 0
signup:
enable_signup: true
enable_anonymous_signins: false
allowed_email_domains: [] # empty = any domain may sign up
allowed_email_domains_mode: signup # signup | signup_and_signin | disabled
rate_limits: # per hour
signup: 100
signin: 100
token_refresh: 1000
password_reset: 10
password:
min_length: 15 # 15-128
require_uppercase: true
require_lowercase: true
require_numbers: true
require_special_chars: false
password_reset:
allow: true
timeout: 3600
max_history: 0
email_verification:
require_confirmation: true
confirmation_timeout: 86400
cors:
enabled: true
allowed_origins: [https://myapp.com, http://localhost:3000]
allow_credentials: true
max_age: 86400
allow_frontend_previews: true # allow this project's live preview origins
providers:
email_password:
enabled: true
oauth: # fully synced when declared
- provider: google # google|github|microsoft|apple|device
enabled: true
client_id: ${GOOGLE_CLIENT_ID}
client_secret: ${GOOGLE_CLIENT_SECRET} # write-only
redirect_url: https://api.myapp.com/auth/oauth/google/callback
scopes: [openid, email, profile]
- provider: device # client_id/secret server-generated
enabled: true
email:
enabled: true
from:
address: no-reply@myapp.com
name: My App
smtp:
host: smtp.sendgrid.net
port: 587
username: ${SMTP_USERNAME}
password: ${SMTP_PASSWORD} # write-only
use_tls: true
templates: # fully synced when declared
confirmation:
subject: 'Confirm your email'
html_body: '<p>Confirm: {{.Token}}</p>' # bodies require SUPERAGENT
text_body: 'Confirm: {{.Token}}'
password_reset:
subject: 'Reset your password' # subject-only works on HOBBY
password_changed:
subject: 'Your password was changed'
welcome:
subject: 'Welcome'
html_body: '<p>Welcome {{.Email}}</p>'
text_body: 'Welcome'
managed_pages:
enabled: true
redirects:
allowed: [https://myapp.com/welcome, https://myapp.com/login]
post_auth: https://myapp.com/welcome
post_logout: https://myapp.com/goodbye
device_verification: https://myapp.com/device
pages: # SUPERAGENT; upsert-only (omission => untouched)
login:
html: '<html>...</html>' # required, <=256 KiB
css: 'body{}' # optional, <=256 KiB
reset_password: # maps to page type "reset-password"
html: '<html>...</html>'
signup: # also: forgot_password, device, verify_email
html: '<html>...</html>'
appearance: # SUPERAGENT; built-in renderer configuration
theme: # shared by every managed auth page
version: 1
colors:
background: '#f7f5f2'
surface: '#ffffff'
text: '#1a1a1a'
accent: '#1d4ed8'
accent_text: '#ffffff'
font: humanist # system|humanist|geometric|slab|mono
scale: large # small|default|large
density: spacious # compact|comfortable|spacious
radius: large # none|small|medium|large
layouts: # centered|split-left|split-right
login: split-left
signup: split-right
forgot_password: centered
device: split-left
verify_email: centered
reset_password: split-right
functions: # must already be deployed
- name: hello
kind: standard # standard (default) or durable; fixed at creation
visibility: public # private, authenticated, or public; omit to keep the current level
invocation_mode: http # rpc (default) or http
http_auth_mode: none # volcano (default) or none; none requires public
openapi_spec: # optional OpenAPI 3.0/3.1 metadata; HTTP mode only
openapi: 3.1.0
info: { title: Hello webhook, version: 1.0.0 }
paths: {}
variable_scope: scoped # all (default) or scoped
variables: # replaces the declared list when present
- STRIPE_SECRET_KEY
schedulers: # fully synced when declared
- name: nightly
cron: '0 3 * * *' # 5-field UTC cron
enabled: true
payload: { source: cron }
frontends: # must already be deployed
- name: web
variable_scope: shared # all (default), shared, or scoped
function_routes: # fully synced when declared
- function: hello # public standard HTTP-mode function
path_prefix: /api # exact segment prefix; /api or /api/...
strip_prefix: true # function receives / for /api
frontend_routes: # fully synced when declared
- frontend: marketing # another frontend of the project
path_prefix: /blog # forwarded unchanged; /blog or /blog/...
custom_domain: # omitted: kept; null or bare key: deleted
domain: app.myapp.com
tls: # optional for an existing domain
mode: managed
- name: marketing
custom_domains: # instead of custom_domain; fully synced when declared
- domain: www.myapp.com
tls: { mode: managed }
- domain: myapp.com # a second domain requires SUPERAGENT
tls: { mode: managed }
redirect: # answer with a redirect instead of serving
to: www.myapp.com # a serving domain listed here
status: 308 # 301, 302, 307, or 308 (default)Not in the manifest: project logo, anon/service keys, project access tokens, auth users (no end-user creation, bans, or sessions), scheduler regions, code artifacts (function/frontend deploys), and one-shot actions (password reset, key regenerate, test email, redeploy).
Credentials stay out because they are state, not configuration: they are created and revoked imperatively, and a project access token's secret exists only in the response that created it. Manage them through their endpoints.
Reconciliation semantics
- Omitted sections are untouched. Only what you declare is reconciled.
- Patch semantics within entries. An omitted optional field keeps its
current server value (for example a bucket entry with only
file_size_limitleavesallowed_mime_typesalone). - Function visibility.
visibilityisprivate,authenticated, orpublic; see who can invoke a function. Omitting it keeps the current level.publicis a deprecated alias that still applies:public: truemeanspublicandpublic: falsemeansauthenticated, notprivate. A manifest that sets both must make them agree. Export writesvisibilityand neverpublic. Exports made beforevisibilityexisted wrotepublic: false, and an entry that still says so appliesauthenticatedon every deploy and every push. To keep the functionprivate, replace it withvisibility: private;volcano cloud config pullwritesvisibilityfor every function. - Function invocation metadata.
invocation_mode,http_auth_mode, andopenapi_specuse the same validation asPATCH /projects/{id}/functions/{functionId}. Unauthenticated HTTP ingress (http_auth_mode: none) is allowed only when the function ispublic. Moving such a function offpublicimplicitly restoreshttp_auth_mode: volcanowhen the auth mode is omitted. - Frontend Function routes. Declaring
frontends[].function_routesfully syncs the same-origin path mappings for that frontend; routes absent from the list are deleted. Omitting the key preserves existing routes, whilefunction_routes: []deletes them all. Targets must be deployed standard Functions withvisibility: publicandinvocation_mode: http; one apply can set both on the function and add the route, and a route to a function that stays non-public is a validation error. A function that keeps a route cannot be made non-public or moved torpcunless the same apply removes that route through the frontend'sfunction_routes. A route works on both the generated frontend hostname (including preview deployments) and a custom domain because routing follows the resolved frontend rather than the domain. An apply may remove a function's routes and change its visibility or mode together; Volcano removes the routes before changing the function. See Frontend Function routes. - Frontend routes. Declaring
frontends[].frontend_routesfully syncs the paths that frontend serves from another frontend of the project; routes absent from the list are deleted. Omitting the key preserves existing routes, whilefrontend_routes: []deletes them all. Eachfrontendmust already exist and can't be the frontend itself. Function and Frontend routes on one frontend share one set of paths, so a path listed in both is a validation error, as is a path the other kind already routes when the manifest leaves that kind alone. An apply removes routes before it adds any, so it can move a path from one kind to the other. An export includesfrontend_routesonly on frontends that have routes. WhereGET /capabilitieslistsfrontend_routesas unavailable, a manifest that would create or change a route answers400with codefeature_unavailable; keeping or removing routes still applies. See Frontend routes. - Function kind is asserted, never written.
kindis fixed when a function is created, so the manifest compares it and reports an error when it disagrees with the deployed function. Omitting it meansstandard, so namekind: durableto describe a durable function. An export writeskind: durablefor durable functions and leaves it off standard ones, so re-applying an export is a no-op either way. - Durable functions take no invocation settings.
invocation_mode,http_auth_mode, andopenapi_specdescribe synchronous HTTP invocation, which a durable function does not have — it is started through its executions collection. Declaring one on a durable function is a validation error, and an export never writes them.visibilitystill applies: it decides who may start executions. See Durable functions. - Shared variables.
shared_variables: [LOG_LEVEL]replaces the complete shared list using existing names only. Omission keeps membership;[]clears it. Apply changes no values and deletes no variables through this field. Re-applying the same list retries function environment reconciliation, including after a previous apply failed to start it. Export emits the shared names and omits variable values. Unknown names and oversized final function environments are rejected before mutations. Scoped functions select variables independently of this list. The variable API's optionalsharedwrite field preserves existing membership when omitted and defaults totruefor new variables for legacy clients. In a manifest that declaresshared_variables, that list is the complete membership and is authoritative for every variable in the apply. Each create and update is written with the membership the list gives it, never as shared first and demoted after, so no reader sees a name under a membership the manifest did not ask for. A conflicting per-variableshareddoes not override the list — declaringshared_variables: []alongsideshared: trueon an entry writes that variable private. Use the per-variable field only when the manifest omitsshared_variables. - Function variable scope.
variable_scope: all(the default) gives a function only variables markedshared: true.variable_scope: scopedgives it only the variables it selects: every name invariables, plus the names Volcano detects in its source that the project defines. Omitting either key keeps the function's current value, so a function scoped through an earlier apply stays scoped. See Function variables below. - Frontend shared variables.
frontend_shared_variablesreplaces the complete project list for frontends withvariable_scope: shared. Omission keeps the list;[]clears it. Unknown names and environments over 4096 bytes are rejected before mutation.allkeeps the legacy access to every project variable.scopeduses only the frontend entry'svariableslist. - Fully synced when declared (destructive by design):
variables,buckets[].policies,auth.providers.oauth,auth.email.templates,functions[].variables,functions[].schedulers, andfrontends[].custom_domains. The declared list is the source of truth: entries absent from the manifest are deleted; an explicit empty list deletes everything. Runvolcano cloud config pullbefore your first deploy and check the--dry-runreport to see what a partial list would remove. - Email templates and built-in defaults. Template content that still
matches the built-in defaults is not a customization: exports omit the
default bodies, and a declared
templatesmap treats them as absent, so a subject-only entry works on HOBBY without touching the default content. A template type absent from a declared map reverts to the built-in default. - Never created, never deleted: functions, frontends, databases, and
buckets. The manifest only updates their configuration. Databases are
assertion-only —
region,pg_version, anddatabase_typeare compared, never written; adatabase_typemismatch is an explicit error because tier changes must go throughvolcano cloud databasesor the dashboard. - Custom domains belong to their declared frontend entry. Set
tls.mode: managedto have Volcano issue and renew the certificate. Your account must own the hostname's domain in either TLS mode; see Verify domain ownership. Both an apply and a dry run refuse a hostname another account owns, and a BYOC hostname your account has not verified, naming the_volcanoTXT record to publish. When DNS does not answer an ownership check, they refuse the hostname with a message to retry later. A managed hostname nobody owns is reserved for your account until you publish the record thatvolcano cloud frontends domain getreports; then keep the certificate validation CNAME in DNS for renewal. Domains already attached keep applying unchanged. If another account has only reserved the hostname, an apply that adds a domain to a frontend without one releases the reservation once your account owns the domain; for a managed domain it otherwise reports your account's_volcanoTXT record, so publish it and apply again. A reservation is not ownership, so a dry run reports that domain as created and only the apply names the record. Replacing an existing domain never takes over a reservation: delete the existing domain in one apply, wait for its removal to finish, then declare the hostname. Add the separate routing record to direct traffic to the frontend. Use CNAME unless the hostname is the apex of your DNS zone, including a separately delegated subdomain. At an apex, use your provider's ALIAS, ANAME, or CNAME-flattening feature.custom_domaindescribes a frontend with at most one domain and no redirect; for any other frontend an apply that sets it fails validation and namescustom_domains. Omittingcustom_domainon a declared frontend leaves its domain alone, so a domain attached withvolcano cloud frontends domain createsurvives a manifest that does not mention it. The report lists it as unchanged with a notice naming the hostname. To delete the domain, setcustom_domain: null; the report names the hostname it deletes. Manifests that relied on omission to delete a domain need the explicitnull. Moving a hostname to another of the project's frontends takes two applies: setcustom_domain: nullon its current frontend, wait for that removal to finish, then declare it on the new one. After a served domain is removed, proxies can keep serving its hostname for about a minute. An apply that declares that hostname within the minute fails validation; apply again once it has passed. Changing the hostname of a managed domain requires one apply to delete it and a later apply to create the replacement. Replacing a BYOC domain with a managed domain for a different hostname takes one apply; if Volcano cannot admit the new hostname, the BYOC domain keeps serving. Changing the TLS mode of an active domain for the same hostname takes one apply and keeps HTTPS up: the current certificate serves until its replacement does. See Switch between BYOC and managed TLS. A domain that is not yet active cannot switch in place; delete it first. Managed domains retain their certificate quota until deletion finishes. At the account limit, apply the deletion, wait for cleanup, then apply the replacement; planning a deletion does not grant capacity early. - Custom domain sets.
frontends[].custom_domainslists every custom domain of the frontend, each withdomain,tls, and an optionalredirect(to,status). Declaring it fully syncs the set: listed domains are created or updated, unlisted ones are deleted and the report names each hostname, andcustom_domains: []deletes them all. Omitting the key leaves the domains alone. An entry setscustom_domainorcustom_domains, not both. Each entry follows thecustom_domainrules above for ownership, TLS, and rotation; where they say to setcustom_domain: null, drop the entry from the list instead. Aredirect.tomust name another listed domain that isactiveand has noredirect, andstatusdefaults to308. A domain the apply adds is not active yet, so add redirects to it in a later apply. One apply can add, remove, and retarget redirects among active domains, including swapping which domain serves: Volcano orders the changes so no redirect points at a domain that isn't serving. Replacing a hostname in one apply stops the old one serving as soon as its removal starts, before the new one is active. To swap without downtime, apply the list with both, wait for the new domain to beactive, then apply the list without the old one. When the plan's allowance holds the old and new domains together, the apply creates the new domain first and keeps the old one if the create fails; the report marks the old entry as an error, and the next apply retries both. If the create stopped before Volcano stored the certificate, the retry needscertificate_pemandprivate_key_pemin the entry. A list longer than the plan's custom domains per frontend fails validation when it adds a hostname, so a frontend over the limit after a downgrade still re-applies its export and can shrink. Export writescustom_domainsfor a frontend with more than one domain or any redirect, andcustom_domainotherwise. See Redirect one domain to another. - BYOC certificates use
tls.mode: byocwithcertificate_pemandprivate_key_pemtogether, plus optionalcertificate_chain_pem. Atlsblock withoutmodeis BYOC. New material for the same domain rotates the certificate in place with zero downtime: the frontend proxies pick up the new certificate within seconds while the old one keeps serving handshakes. Omittingtls, or sending onlytls.mode: byocas an export does, keeps the stored certificate. A different BYOC domain name is a detach-and-recreate; the new domain serves after provisioning. - Hosted pages are upsert-only. There is no delete API for hosted pages,
so pages omitted from
managed_pages.pagesare left untouched. - Managed-page appearance is also upsert-only in the manifest. Omitted
theme or layout fields stay untouched. Use the appearance API's
DELETEoperations to return a theme or page layout to default tracking. auth.signup.allowed_email_domainsreplaces the stored list when declared.[]removes the restriction; omitting the key keeps the current allowlist. Entries are normalized to bare lowercase domains, so["@Acme.com"]is stored as["acme.com"].allowed_email_domains_modefollows the same rule — omit it to keep the stored mode. Declaringsignup_and_signin, or narrowing the list while it is already set, signs out every session the list no longer admits. See Email domain allowlist.- New or changed
auth.email.from.addressandnamevalues cannot contain line breaks. Apply reports invalid values as422validation errors. Exports remove leading and trailing whitespace from older sender settings; reapplying them is a no-op and leaves the stored settings unchanged. Unchanged exports of legacy values with interior line breaks also preserve those values; delivery refuses an unsafe stored sender until it is repaired. - New or changed email template subjects cannot contain literal interior line breaks after
Go template whitespace trimming. Apply reports one
422validation error under the affected template entry. Multiline control flow with{{-and-}}trim markers, outer whitespace, and trailing newlines from YAML block scalars are allowed; message bodies can span lines. Exports preserve older subjects. Reapplying the exact stored value is a no-op, and unrelated settings or template bodies remain editable. A changed subject must pass validation. Delivery uses the default subject when a rendered legacy value is unsafe. See Subject headers.
Leave out lavaflow
The manifest doesn't configure LavaFlow. A manifest that still has a lavaflow
section applies as if the section were absent: the dry run and the apply list
it as unchanged with a notice to remove it, and export never writes it. That
includes autonomy: merge and merge_policy: letting LavaFlow merge is the
project owner's decision, made with
PUT /lavaflow/settings.
Notification settings
The notifications section reconciles where the project's
alerts go. An omitted field keeps its current value. Alert emails
go only to the project owner's verified account address, so leave recipients
empty. Naming that address also works, but the file then fails validation once
the address changes. Any other address is a validation error, and a dry run
reports it. The webhook signing secret is
never part of the manifest. Declaring a webhook.url on a project without a
webhook generates a secret the apply does not return. To get a secret you can
use, rotate it with POST /projects/{id}/notifications/webhook-secret.
url: "" removes the webhook and its secret.
Export writes the section once the settings have been saved. It never writes
webhook, because the URL can carry your receiver's token. An omitted webhook
keeps its URL and secret, so re-applying an export leaves it in place; declare
url: "" to remove one.
Coverage warnings: skipped and missing
Because the manifest can never create functions, frontends, databases, or buckets, the apply report tells you when your file and your deployed resources disagree:
skipped— the manifest configures a resource that does not exist. Deploy or create it first, then re-runvolcano cloud config deploy.missing— a deployed resource has no entry in its declared manifest section.
Both are warnings: the rest of the manifest still applies and the CLI exits 0.
For a new frontend, deploy it with the variables its entry selects, then apply
the manifest (see
First deploy with volcano-config.yaml):
volcano cloud frontends deploy --variable-scope scoped --variable NEXT_PUBLIC_API_URL
volcano cloud config deployvolcano cloud deploy does both for every frontend in
.volcano/source-layout.json, after the functions, and applies the manifest
last. Pass -c to apply another manifest, and repeat it to apply several in
order. It refuses a manifest with a variables section when an env file is
also present, since applying the section would delete the variables only the
env file sets. See the CLI deploy reference.
Validation, plan limits, and partial failures
- Validation failures (bad cron expressions, invalid policy definitions,
database assertion mismatches, plan-gate violations) return
422with the full error list and nothing is applied. - Plan gates are change-aware: they only fire when the manifest would change a gated value. Re-applying an export of a project downgraded from SUPERAGENT stays a no-op.
- SUPERAGENT-gated surfaces: region subsets, email template bodies, hosted pages,
and the
auth.signup.allowed_email_domainsallowlist. A HOBBY project can still declare the allowlist it already has, or clear it to remove the restriction. Scheduler counts, storage policy counts, custom domains per frontend, and managed TLS certificates respect plan caps. - Apply-phase failures (a provider call failing mid-apply) return
200with per-entryaction: error; already-applied changes are not rolled back. Re-running the deploy is safe — unchanged entries are no-ops. - Applies are serialized per project; a concurrent apply returns
409.
Function variables
By default a function receives the project variables marked shared: true.
Keep this list small: the platform caps a function's environment at 4096 bytes,
measured as its variables encoded as JSON, and a project can hold more than that.
See environment variables for how the
size is counted.
Scoping a function narrows it to the variables it actually reads:
functions:
- name: checkout
variable_scope: scoped
variables:
- STRIPE_WEBHOOK_SECRETThe function's variables come from two sets, which differ in whether the name has to exist:
-
Declared — the names in
variables. Required: a declared name the project does not define fails the apply. These round-trip throughvolcano cloud config pull, so the manifest stays the source of truth for them. -
Detected — the names Volcano finds in the source you deploy. Optional: a detected name is included when the project defines it and ignored when it does not, because a direct reference is often to something optional (
process.env.DEBUG,process.env.NODE_ENV) and code written to run without it should still deploy. Volcano reads direct references only:Runtime Detected forms Node.js process.env.NAME,process.env["NAME"]Python os.environ["NAME"],os.getenv("NAME")Ruby ENV["NAME"],ENV.fetch("NAME")Detection runs on every deploy and reads the whole source you uploaded, so it follows your code without a manifest change. It reads code only: a name that appears just in a comment or inside an unrelated string is not a reference and does not select a variable. Code interpolated into a string still counts, so
${process.env.KEY},#{ENV['KEY']}andf"{os.getenv('KEY')}"are detected. It skips dependency trees (node_modules,vendor,.venv), so declare a variable your function only reads from inside a dependency.
Detected names are remembered even when the project does not define them yet, so
creating the variable later redeploys the function with it. Declare a name in
variables when the function must not deploy without it — that is the
difference between the two sets.
Anything computed — process.env[key], a name built at runtime — cannot be
detected. Declare those in variables. A scoped function reading a variable
that is neither declared nor detected gets undefined at runtime, even when the
project defines it; that is the cost of not failing a deploy over an optional
reference.
Apply checks every function against the variables the manifest will produce, not the ones the project has now, so one file can create a variable and declare it on the same pass. It rejects the apply before any change lands when:
- A scoped function declares a variable the resulting project does not define — including one this file deletes, even if that function's own entry is unchanged. Create the variable, or drop the name.
- A function's resulting environment exceeds 4096 bytes. Scope the function to fewer variables, or shorten the values.
volcano cloud config deploy and a Git push refuse a variables key with no
value, such as a bare variables: or variables: null, rather than reading it
as omitted. Write variables: [] to declare no names, or remove the key to keep
the current list. volcano cloud config deploy answers 400, and a Git push
fails its deploy with nothing applied.
Changing variable_scope or variables redeploys that function's environment,
so switching from all to scoped removes the variables it no longer selects
from the running function rather than only from its configuration.
Creating, updating, or deleting a project variable only redeploys the functions that select it. A function scoped away from a variable is left alone, so a write to one secret no longer restarts every function in the project.
Secrets
Write-only secrets — auth.email.smtp.password, OAuth client_secret, and
BYOC certificate material — are omitted from exports and stay unchanged unless
you set them explicitly. The notification webhook signing secret is never part of the manifest either; see
Notification settings. Custom-domain exports keep only tls.mode, for both
managed and BYOC domains. The CLI interpolates ${ENV_VAR} references before
upload, so keep secrets in your environment, not in the file. Variable values
are included in exports (they are readable through the API).
Asynchronous side effects
Apply starts long-running work and returns immediately: variable changes
propagate to functions and frontends through one batched workflow, region
changes trigger function redeploys, and new custom domains provision
asynchronously. Check convergence with the usual commands (volcano cloud variables list, volcano cloud frontends domain get, ...).
Frontend variable selection
Limit the project variables available to an existing frontend during both build and runtime:
version: 1
frontends:
- name: web
variable_scope: scoped
variables:
- NEXT_PUBLIC_API_URL
- SESSION_SECRETNew frontends default to scoped with no selected project variables. Existing
frontends created under the legacy all default keep that selection until it
is changed explicitly; this legacy mode selects every project variable and does
not use the shared function list. scoped selects only the declared names;
frontend source is not scanned for
variable references. Missing declared variables reject deployment or apply.
NEXT_PUBLIC_* names remain build-only and do not enter the runtime environment.
Omitting either field preserves its stored value; variables: [] selects no
project variables in scoped mode. Config export includes both fields.
A scope change synchronizes the running cloud frontend's environment. Rebuild and redeploy to apply changes to values embedded in browser assets. Local mode uses the stored selection on deployment and restart. Variable updates that the frontend does not select do not trigger its runtime synchronization.
Selected runtime values, platform metadata, and reserved space for the largest cache metadata and proxy-token rotation must fit the 4,096-byte environment limit before a variable or scope change is saved. The platform's share is about 900 bytes, reserved even when no rotation is staged, so roughly 3,100 bytes remain for selected variables. The reservation keeps updates safe without reading deployed runtimes. Deployment also checks the final environment including platform-managed values; an oversized environment fails before the runtime configuration is updated. Error messages report byte counts without exposing variable values.
Sandbox template settings
Declare existing sandbox templates by name:
version: 1
sandboxes:
- name: browser-worker
memory_mb: 1024
ports: [8080]
ttl_seconds: 3600config deploy --dry-run previews changes. Applying updates the default lifetime
for newly created sessions; existing sessions keep their original lifetime.
An explicit session max_duration_seconds overrides the template default.
Cloud sessions allow 30–3,600 seconds on HOBBY and 30–28,800 seconds on
SUPERAGENT, subject to regional capacity. Template defaults above the plan limit
are clamped when starting a session; an explicit duration above it is rejected.
Local lifetime defaults to zero (unlimited); positive values impose a limit
without the cloud eight-hour limit. Local commands also default to unlimited.
memory_mb (1024 or 2048) and ports describe the deployed image. Running a
2048 MiB image requires SUPERAGENT; HOBBY supports a 1024 MiB baseline. Image
deployment and validation can succeed on HOBBY at either size, but starting a
2048 MiB session or one-shot execution returns sandbox_plan_limit_exceeded. Config apply
asserts these values and reports a validation error if they differ; deploy the
sandbox source to rebuild an image with different values. Omitted fields preserve
current settings. Missing templates are reported as skipped; templates absent from
the manifest are reported as missing and are never deleted. Export includes the
current settings and can be applied unchanged. A project without sandbox
templates exports no sandboxes section.
Git deployments discover each volcano/sandboxes/<name>/Dockerfile and its isolated
build context. Matching sandboxes entries supply memory, readiness ports, and
session defaults. Each push records a durable deployment for every discovered
sandbox and waits for image validation before reporting success. An entry without
a source directory does not create a template. Undeclared source directories use
the existing template's settings, or 1024 MB with no service ports for a new template.