Build Vercel Sandbox snapshots from declarative .snap files
Project description
vsnap
Build Vercel Sandbox snapshots from declarative .snap files.
.snap files are declarative build recipes -- like a Dockerfile, but for Vercel Sandbox environments. Each directive is a single line that says what to do: install packages, run commands, copy files, write configs. No programming language, no boilerplate. Just a clean, sequential recipe.
Installation
uv add vsnap
# or: pip install vsnap
Requires Python 3.10+ and a Vercel project with sandbox access.
Quick Start
Create a .snap file:
# my-app.snap
runtime node22 {
env NODE_ENV development
expose 3000
}
workdir /vercel/sandbox/my-app
install git jq
run "npm install"
run "npm run build"
Build a snapshot:
vsnap build my-app.snap
The CLI auto-loads .env.local for Vercel OIDC credentials. Run vercel env pull .env.local once to set up.
CLI
vsnap build <file.snap> # Build and snapshot (streams output by default)
vsnap build <file.snap> -q # Quiet mode (suppress streaming output)
vsnap build <file.snap> --dry-run # Parse and validate only, print the build plan
vsnap build <file.snap> --dry-run --output json # Machine-readable build plan
vsnap build <file.snap> --output json # JSON build result (agent-friendly)
vsnap build <file.snap> --env KEY=VALUE # Extra env vars (repeatable)
vsnap build <file.snap> --token <token> # Vercel API token (overrides .env.local)
DSL Reference
Source Directives
Every .snap file starts with a source directive that defines where the sandbox comes from. Exactly one is required.
runtime -- From a runtime image
runtime node22
runtime node22 {
vcpus 2
timeout 600000
env NODE_ENV production
env CI true
expose 3000 9229
network allow-all
}
Valid runtimes: node22, node24, python3.13
snapshot -- From an existing snapshot
snapshot snap_abc123
snapshot snap_abc123 {
runtime node22
vcpus 2
}
git -- From a git repository
git https://github.com/user/repo.git {
runtime node22
depth 1
revision main
}
tarball -- From an archive
tarball https://example.com/archive.tar.gz {
runtime node22
}
Source block sub-directives
| Sub-directive | Description |
|---|---|
vcpus <n> |
Number of vCPUs |
timeout <ms> |
Sandbox lifetime in ms (default: 30 min) |
env <KEY> <VALUE> |
Creation-time environment variable (repeatable) |
expose <port> [<port> ...] |
Ports to expose |
network allow-all | deny-all |
Network policy |
network { allow ...; subnet_allow ...; subnet_deny ... } |
Custom network policy |
runtime <name> |
Runtime override (snapshot/git/tarball only) |
depth <n> |
Git clone depth (git only) |
revision <ref> |
Git branch/tag/commit (git only) |
username <str> |
Git auth username (git only) |
password <str> |
Git auth password (git only) |
Step Directives
Steps execute in order, top to bottom. They appear after the source directive.
run -- Execute a shell command
run "npm install"
run "systemctl start nginx" {
sudo
}
run "npm run build" {
env NODE_ENV production
cwd /opt/build
}
# Multi-command block (each line = separate step)
run {
npm install
npm run build
npm prune --production
}
| Sub-directive | Description |
|---|---|
sudo |
Run with sudo |
cwd <path> |
Working directory override (this command only) |
env <KEY> <VALUE> |
Per-command env var (repeatable) |
script -- Execute a multi-line script
# From a local file
script ./scripts/setup.sh
# Inline (escape sequences in double quotes)
script "echo hello\necho world"
# Heredoc
script <<EOF
set -e
curl -fsSL https://example.com/install.sh | bash
ln -sf /usr/local/lib/tool /usr/local/bin/tool
EOF
# With options
script ./scripts/deploy.sh {
sudo
shell python3
cwd /opt/build
env CI true
}
| Sub-directive | Description |
|---|---|
sudo |
Run with sudo |
shell <name> |
Shell interpreter (default: bash) |
cwd <path> |
Working directory override |
env <KEY> <VALUE> |
Per-script env var (repeatable) |
install / remove / update -- System packages
install git curl jq make
remove nano
update curl
Uses dnf on Amazon Linux 2023 (the sandbox OS). Runs with sudo automatically.
copy -- Upload local files into the sandbox
copy ./assets/config.json /vercel/share/config.json
copy ./scripts/start.sh /usr/local/bin/start.sh {
mode 0755
}
copy ./assets/config/ /vercel/share/.config/
Paths are relative to the .snap file's directory.
file -- Write inline content to a file
file /etc/config.json '{"port": 3000}'
file /vercel/sandbox/config/app.json <<EOF
{
"port": 3000,
"debug": false
}
EOF
file /opt/script.sh "#!/bin/bash\nexec node server.js" {
mode 0755
}
Default mode is 0644.
download -- Download a remote file
download https://example.com/tool.tar.gz {
dest /usr/local/bin
extract
checksum sha256:abc123...
}
download https://example.com/config.json {
dest /etc/app/config.json
}
| Sub-directive | Description |
|---|---|
dest <path> |
Destination path in sandbox (required) |
extract |
Extract archive (tar/zip) after download |
checksum <algo>:<hash> |
Verify checksum |
workdir -- Set the working directory
workdir /vercel/sandbox/my-app
run "npm install" # runs in /vercel/sandbox/my-app
Creates the directory implicitly.
mkdir -- Create a directory
mkdir /vercel/sandbox/my-app/src
env -- Persist an environment variable
env CUSTOM_VAR value
env NODE_ENV production
Writes to /etc/environment so the variable survives snapshot restore. This is different from env inside a source block, which sets creation-time env vars.
Syntax
.snap files use a Caddyfile-inspired syntax:
- One directive per line. Each line starts with a directive name followed by arguments.
- Bare words. No quotes needed for simple values. Use double or single quotes for values with spaces.
- Blocks. Use
{ }for sub-directives (options) or multi-line content. - Heredocs. Use
<<MARKER ... MARKERfor multi-line file content and scripts. - Comments.
#to end of line. - Booleans by presence.
sudomeans true. Absence means false.
Quoting
run "echo hello world" # double quotes for spaces
run 'echo hello world' # single quotes also work
run "echo \"quoted\"" # escape quotes in double-quoted strings
script "line1\nline2" # \n = newline, \t = tab, \\ = backslash
install git jq # bare words when no spaces needed
Escape sequences (double quotes only)
| Escape | Result |
|---|---|
\n |
Newline |
\t |
Tab |
\\ |
Backslash |
\" |
Double quote |
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vsnap-0.1.0.tar.gz.
File metadata
- Download URL: vsnap-0.1.0.tar.gz
- Upload date:
- Size: 73.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
117d59ccbe4ce4f58b80699f7b273da58bf899bf899aa7c54090c491f916a720
|
|
| MD5 |
06e0235a83838daedbeb66f3647da2b2
|
|
| BLAKE2b-256 |
a933efe5490ce542192cda32c7bcca94dbc98413532f0c2b2d46850d2868eba4
|
Provenance
The following attestation bundles were made for vsnap-0.1.0.tar.gz:
Publisher:
release.yml on gscho/vsnap
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vsnap-0.1.0.tar.gz -
Subject digest:
117d59ccbe4ce4f58b80699f7b273da58bf899bf899aa7c54090c491f916a720 - Sigstore transparency entry: 1265119095
- Sigstore integration time:
-
Permalink:
gscho/vsnap@7d2bfc5875f1efe72e27db45cb48bb6d05b82351 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/gscho
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7d2bfc5875f1efe72e27db45cb48bb6d05b82351 -
Trigger Event:
push
-
Statement type:
File details
Details for the file vsnap-0.1.0-py3-none-any.whl.
File metadata
- Download URL: vsnap-0.1.0-py3-none-any.whl
- Upload date:
- Size: 25.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b30cfccac4330fefa0438e057b6942e5dee1bbc209947d80adb2f4b34b15b96c
|
|
| MD5 |
16bb2247aa6e5572eb44df47a3e5f1f6
|
|
| BLAKE2b-256 |
d804ea75968a4fdbf39a0f74f21642eda0bb5241425262ff5fe3cdaf89f39ca2
|
Provenance
The following attestation bundles were made for vsnap-0.1.0-py3-none-any.whl:
Publisher:
release.yml on gscho/vsnap
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vsnap-0.1.0-py3-none-any.whl -
Subject digest:
b30cfccac4330fefa0438e057b6942e5dee1bbc209947d80adb2f4b34b15b96c - Sigstore transparency entry: 1265119192
- Sigstore integration time:
-
Permalink:
gscho/vsnap@7d2bfc5875f1efe72e27db45cb48bb6d05b82351 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/gscho
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7d2bfc5875f1efe72e27db45cb48bb6d05b82351 -
Trigger Event:
push
-
Statement type: