Back to blog
Abhishek·May 14, 2026·9 min read

Deploy anything on InstaVM with `instavm.yaml`

InstaVMDeployYAMLManifestVaultEgressCookbooksReference

instavm.yaml is the deploy manifest for an InstaVM project. Drop it at the root of your app and run:

instavm deploy .

Use it when you want the deploy path committed with the code: reproducible across machines, reviewable in PRs, and not inferred from whatever the local directory looks like.

For a worked example that uses these fields to deploy a real research agent, see Deploy an Exa deep research agent on InstaVM with vault-backed API keys.

Minimal service manifest

Smallest valid manifest for a web service:

schema_version: 2
kind: service
slug: hello-fastapi
title: Hello FastAPI
version: "0.1.0"
summary: Simple FastAPI app with a health endpoint.
category: web
runtime: python-fastapi
deploy:
  kind: upload_and_run
source:
  include:
    - app.py
    - requirements.txt
    - instavm.yaml
  exclude: []
  setup_command: python -m pip install --no-cache-dir -r requirements.txt
vm:
  memory_mb: 1024
  vcpu_count: 1
app:
  port: 8000
  healthcheck_path: /health
  readiness_timeout_seconds: 90
  share_public_default: true
run:
  workdir: .
  start_command: python -m uvicorn app:app --host 0.0.0.0 --port 8000
secrets: []
post_deploy_notes: []

Validate the resolved plan before deploying:

instavm deploy . --plan
instavm deploy . --plan --json

Deploy:

instavm deploy .

Use a non-default manifest filename:

instavm deploy -f instavm.web.yaml

Metadata fields

Every manifest opens with a metadata header. These fields identify the workload and don't affect runtime behavior on their own.

schema_version: 2
kind: service
slug: hello-fastapi
title: Hello FastAPI
version: "0.1.0"
summary: Simple FastAPI app with a health endpoint.
category: web
runtime: python-fastapi
Field Meaning
schema_version Manifest schema number required by the parser.
kind Workload type: service, cron, or job. Use service for web deploys.
slug Stable app id. Use lowercase letters, numbers, and dashes.
title Display name.
version App or cookbook version string.
summary One-line description shown in deploy and cookbook output.
category Group label such as web, agents, automation, or dev-environment.
runtime Runtime label such as python-fastapi, node, or code-server.

deploy

The deploy block selects the deployment strategy.

deploy:
  kind: upload_and_run

upload_and_run is the strategy for source-based deploys: upload files from source.include, run source.setup_command, start run.start_command, poll app.healthcheck_path, and return a share URL.

For prebuilt cookbooks, point at a snapshot instead:

deploy:
  kind: published_snapshot
  snapshot_name: cookbook/hello-fastapi:0.1.0

Snapshot deploys use artifact and optionally build instead of source.

source

The source block lists what to upload and how to install dependencies.

source:
  include:
    - app.py
    - requirements.txt
    - instavm.yaml
  exclude:
    - .venv
    - node_modules
    - dist
  setup_command: python -m pip install --no-cache-dir -r requirements.txt
Field Meaning
include Files and directories to upload. At least one item is required.
exclude Files to skip during upload.
setup_command Command run after upload and before run.start_command.

Keep uploads small. Skip local virtualenvs, dependency folders, build output, caches, and any file that contains a key.

Same shape for a Node app, with NVM-driven setup:

source:
  include:
    - package.json
    - package-lock.json
    - src
    - public
    - server.mjs
    - instavm.yaml
  exclude:
    - node_modules
    - dist
  setup_command: >-
    export NVM_DIR="$HOME/.nvm" &&
    if [ ! -s "$NVM_DIR/nvm.sh" ]; then
      curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash;
    fi &&
    . "$NVM_DIR/nvm.sh" &&
    nvm install 22 &&
    nvm use 22 &&
    npm ci &&
    npm run build

vm

The vm block sizes the microVM and tags it with metadata.

vm:
  memory_mb: 2048
  vcpu_count: 2
  timeout_seconds: 7200
  profile: microvm
  metadata:
    team: agents
    env: demo
Field Meaning
memory_mb Memory requested for the VM.
vcpu_count Number of vCPUs requested.
timeout_seconds Optional maximum VM lifetime.
image_variant Optional base image variant.
profile Optional VM profile: microvm or computer_use.
metadata Key-value metadata attached to the VM.

Start small. Reach for more memory and CPU when the app runs a browser, builds during setup, hosts a local database, or executes user-generated code.

app

The app block is required for kind: service. It tells InstaVM how to verify the service is healthy.

app:
  port: 8000
  healthcheck_path: /health
  readiness_timeout_seconds: 120
  share_public_default: true
Field Meaning
port Port your service listens on inside the VM.
healthcheck_path Path polled before deploy returns success. Must start with /.
readiness_timeout_seconds Health-check timeout.
share_public_default Whether deploy creates a public share URL by default.

The port must match your start command. Bind the service to 0.0.0.0, never localhost, or the readiness check will fail from outside the VM.

run

The run block is the long-running command for the service or task.

run:
  workdir: .
  start_command: python -m uvicorn app:app --host 0.0.0.0 --port 8000
  logs_hint: uvicorn
Field Meaning
workdir Directory where commands run after upload.
start_command Long-running command for the service or task.
logs_hint Optional note for log lookup.
pre_start_command Optional hook before the start command.
shutdown_command Optional hook for shutdown.

env

Use env for non-secret configuration the app reads from environment variables.

env:
  OPENAI_MODEL: gpt-5.5
  RESEARCH_MAX_TURNS: "12"

Values are parsed as scalars and passed as strings. Never put API keys here. See vault below.

secrets

Use secrets for values the app must read as environment variables inside the VM, but that you don't want committed to the manifest.

secrets:
  - name: editor_password
    required: true
    prompt: Password for the browser editor
    env_name: PASSWORD
Field Meaning
name Manifest-level secret name.
required Whether deploy must collect the value.
prompt Prompt shown by the CLI.
env_name Environment variable name written for the service.

For API keys that should only ever leave the VM as outbound request headers, and never sit in the process environment, use vault instead.

vault

The vault block declares which third-party hosts get credentials injected at egress. Use it for API keys that should never enter the process environment. See the deep-research cookbook for a worked walkthrough.

vault:
  required: true
  hosts:
    - api.openai.com
    - api.exa.ai

The app sends a placeholder string (e.g. OPENAI_KEY, EXA_KEY) on every outbound request to a bound host. The egress proxy substitutes the real credential, but only for that host. A prompt injection that tells the agent to send the same header to a different URL gets a placeholder.

Set up the vault once:

instavm vault create agent-apis
instavm vault secret set <vault-id> OPENAI_KEY --value-file ./openai.key
instavm vault secret set <vault-id> EXA_KEY --value-file ./exa.key
instavm vault service add <vault-id> --template openai --credential OPENAI_KEY
instavm vault service add <vault-id> --host api.exa.ai --auth-type api-key --header x-api-key --credential EXA_KEY

When vault.required: true and no matching vault is bound, instavm deploy launches the setup flow instead of starting an app that can't reach its upstream API.

egress

The egress block controls which outbound hosts the VM can reach.

egress:
  mode: allowlist
  include_vault_hosts: true
  allowed_domains:
    - api.openai.com
    - api.exa.ai
  allowed_cidrs: []
  allow_package_managers: true
Field Meaning
mode open, deny, or allowlist.
include_vault_hosts Include vault.hosts in the effective allowlist.
allowed_domains Domain allowlist.
allowed_cidrs CIDR allowlist.
allow_package_managers Allow package-manager traffic needed during setup.

For agents, use mode: allowlist plus the exact API hosts the app calls. open and deny exist but are rarely the right answer for production.

volumes

Use volumes for persistent storage that survives across runs.

volumes:
  - name: research-cache
    mount: /data
    mode: rw
    size_gb: 10
    create_if_missing: true
    auto_checkpoint:
      on_success: true
      retention: 5
Field Meaning
name Volume name.
mount Absolute mount path inside the VM. Cannot be /.
mode rw or ro.
size_gb Optional size for created volumes.
create_if_missing Create the volume when it does not exist.
checkpoint_id Mount a checkpoint. Valid only with mode: ro.
auto_checkpoint Create checkpoints after successful runs.

schedules

Use kind: cron for scheduled work that runs without a request.

schema_version: 2
kind: cron
slug: nightly-sync
title: Nightly Sync
version: "0.1.0"
summary: Nightly cleanup job.
category: automation
runtime: python
deploy:
  kind: published_snapshot
artifact:
  oci_image: docker.io/acme/nightly-sync:0.1.0
  snapshot_name: nightly-sync:0.1.0
  snapshot_visibility: private
vm:
  memory_mb: 1024
  vcpu_count: 1
schedule:
  cron: "0 2 * * *"
  timezone: UTC
  concurrency: skip
  catchup: run-once
  timeout_seconds: 1800
  retries:
    max: 2
    backoff_seconds: 60
run:
  workdir: .
  start_command: python jobs/nightly_sync.py
secrets: []
post_deploy_notes: []

instavm deploy --plan validates the manifest and prints the resolved plan. This is useful for catching cron typos before they fire at 2am.

jobs and triggers

Use kind: job for work that runs on demand: manual invocation, webhook trigger, or both.

schema_version: 2
kind: job
slug: import-customer-file
title: Import Customer File
version: "0.1.0"
summary: Imports a customer CSV into the database.
category: automation
runtime: python
deploy:
  kind: published_snapshot
artifact:
  oci_image: docker.io/acme/importer:0.1.0
  snapshot_name: importer:0.1.0
  snapshot_visibility: private
vm:
  memory_mb: 2048
  vcpu_count: 2
triggers:
  - type: manual
    timeout_seconds: 1800
  - type: webhook
    path: /hooks/import
    auth: hmac
    timeout_seconds: 1800
run:
  workdir: .
  start_command: python jobs/import_customer_file.py
secrets: []
post_deploy_notes: []

Webhook paths must start with /.

callbacks

Use callbacks to notify another service when a run finishes.

callbacks:
  on_success:
    - url: https://example.com/hooks/success
      retries: 2
      signing_secret: WEBHOOK_SIGNING_SECRET
  on_failure:
    - url: https://example.com/hooks/failure
      retries: 2
      signing_secret: WEBHOOK_SIGNING_SECRET
secrets:
  - name: webhook_signing_secret
    required: true
    prompt: Webhook signing secret
    env_name: WEBHOOK_SIGNING_SECRET

signing_secret must reference a declared secrets[].env_name.

share

Service deploys use app.port and app.share_public_default for the default share URL.

Use the explicit share block when you need to override the default share policy or pin auth requirements:

share:
  port: 8080
  public: true
  auth: none

auth accepts none, password, or api_key. Password auth requires password_secret to reference a declared secret env name.

desktop, sandbox, and domain

Three optional blocks for less-common runtime modes:

desktop:
  viewer:
    enabled: true
sandbox:
  ttl_seconds: 3600
domain:
  hostname: demo.example.com

If desktop is present and vm.profile is omitted, the deploy treats the VM as a computer-use VM. If vm.profile: computer_use and desktop.viewer.enabled is omitted, the viewer defaults to enabled.

Full web-agent example

A complete manifest combining most of the blocks above. This is the same shape used by the deep-research cookbook:

schema_version: 2
kind: service
slug: deep-research-exa
title: Deep Research Exa
version: "0.1.0"
summary: Research agent using Exa and the OpenAI Agents SDK.
category: agents
runtime: python-fastapi
deploy:
  kind: upload_and_run
source:
  include:
    - app.py
    - requirements.txt
    - instavm.yaml
    - README.md
  exclude:
    - .venv
    - __pycache__
  setup_command: python -m pip install --no-cache-dir -r requirements.txt
vm:
  memory_mb: 2048
  vcpu_count: 2
  timeout_seconds: 86400
  metadata:
    app: deep-research-exa
app:
  port: 8000
  healthcheck_path: /health
  readiness_timeout_seconds: 240
  share_public_default: true
run:
  workdir: .
  start_command: python -m uvicorn app:app --host 0.0.0.0 --port 8000
env:
  OPENAI_MODEL: gpt-5.5
  RESEARCH_MAX_TURNS: "12"
vault:
  required: true
  hosts:
    - api.openai.com
    - api.exa.ai
egress:
  mode: allowlist
  include_vault_hosts: true
  allowed_domains:
    - api.openai.com
    - api.exa.ai
  allowed_cidrs: []
  allow_package_managers: true
secrets: []
post_deploy_notes:
  - Open the share URL and ask a research question.
  - Check /health for vault_mode before sending traffic.

Deploy:

instavm deploy .

Override common values from the CLI without editing the manifest:

instavm deploy . --port 9090 --health-path /_health --private

JSON output for scripts:

instavm deploy . --json

Checklist

Run through this before every deploy:

  • kind: service for web apps
  • source.include contains every file needed at runtime
  • local caches, dependency folders, and key files are excluded
  • source.setup_command installs dependencies
  • run.start_command binds to 0.0.0.0
  • app.port matches the listening port
  • app.healthcheck_path starts with /
  • non-secret config goes in env
  • app-readable secrets go in secrets
  • API keys that should only touch outbound requests go in vault
  • egress.mode: allowlist lists only the hosts the app needs
  • instavm deploy . --plan shows the expected command, port, and secrets

Get free execution credits

Run your AI agents in secure, isolated microVMs. $50 in free credits to start.

Get started free
We use cookies to improve your experience. See our cookie policy.