Snap!Cloud Deployment Guide
May 23, 2026 · View on GitHub
This guide covers production-only deployment concerns. For local
development setup see INSTALL.md.
Snap! checkouts on the server
The production server keeps several independent Snap! checkouts. They are
served from the locations defined in nginx.conf.d/snap-ide.conf:
| Path on disk | Served at | Tracks |
|---|---|---|
~/snapCloud/snap/ | /snap/ | The latest tagged Snap! release |
~/snap-versions/dev/ | /versions/dev/ | origin/master (development version) |
~/snap-versions/previous/ | /versions/previous/ | A pinned older tag (see bin/update-snap) |
bin/update-snap is the entry point for refreshing all three: it
git fetches each checkout, switches to the right ref, and then runs
bin/compress against the checkout to produce .gz siblings of every
text asset.
bin/deploy does the equivalent for ~/snapCloud/static/ itself.
gzip configuration
Gzip is configured once, at the http { } level in
nginx.conf:
gzip on; # on-the-fly compression for dynamic responses
gzip_static on; # serve a precompressed .gz sibling when present
gzip_min_length 1000; # don't bother for tiny responses
gzip_proxied any;
gzip_vary on;
gzip_comp_level 6;
gzip_types *;
Because these directives live at the http level, every server / location inherits them — including:
/snap/and/versions/— Snap! checkouts, pre-compressed bybin/compress.gzip_staticserves the.gzdirectly./static/— Snap!Cloud's own CSS/JS/SVG, pre-compressed bybin/compressduringbin/deploy./api/v1/and@lapisapp— lapis dynamic responses (JSON, HTML). No.gzexists, sogzip oncompresses them on the fly. This covers the large JSON payloads (project listings etc.) that can exceed 1 MB./,/old_site,/js-extensions— any other static aliases.
What is NOT pre-compressed
bin/compress deliberately skips .html files and sw.js. The
/snap/ and /versions/ locations use sub_filter <head> $cloud_loc;
to inject a <meta name="snap-cloud-domain"> tag at runtime, and
gzip_static would serve the .gz before sub_filter could touch the
response — silently dropping the substitution. nginx still gzips these
files dynamically via gzip on.
gzip_static requirements
gzip_static requires the ngx_http_gzip_static_module. OpenResty —
which is what bin/prereqs.sh installs — ships with it enabled. If you
build nginx from source, pass --with-http_gzip_static_module.
When pre-compression runs
| Trigger | Compresses |
|---|---|
bin/update-snap | each of the three Snap! checkouts |
bin/deploy | ~/snapCloud/static/ |
| git hook (see next) | the checkout the hook is installed in |
bin/compress is idempotent — it only re-gzips a file whose source is
newer than the existing .gz. Re-running it costs ~nothing.
Git hooks for automatic compression
bin/update-snap already pre-compresses each Snap! checkout when it
runs. For deployments that pull Snap! changes outside of update-snap
(e.g. a git pull you run by hand, or a webhook-driven update),
install a post-merge / post-checkout hook in each Snap! checkout
so compression always stays in sync with the source files.
Installing the hook
Run the following once per checkout on the server:
for repo in \
~/snapCloud/snap \
~/snap-versions/dev \
~/snap-versions/previous; do
hook="$repo/.git/hooks/post-merge"
cat > "$hook" <<'EOF'
#!/usr/bin/env bash
# Re-build .gz siblings whenever the working tree changes.
set -e
"$HOME/snapCloud/bin/compress" "$(git rev-parse --show-toplevel)"
EOF
chmod +x "$hook"
# `git checkout` (used by update-snap and manual switches) fires
# post-checkout, not post-merge — symlink the two so both trigger.
ln -sf post-merge "$repo/.git/hooks/post-checkout"
done
Notes:
- The hook body is identical for every checkout, so it's safe to copy and to re-install if any checkout gets reset.
bin/compressis idempotent, so leaving the hook installed costs nothing.- The
post-checkoutsymlink coversbin/update-snap, which usesgit checkout <ref>rather thangit merge. If your workflow also usesgit rebase, add apost-rewritesymlink the same way. *.gzis in the Snap! repo's.gitignore, so the generated files never show up ingit status.
A matching hook for snapCloud itself
If you want ~/snapCloud/static/ to recompress whenever snapCloud
itself updates (rather than only on bin/deploy), install the same
hook in ~/snapCloud/:
hook=~/snapCloud/.git/hooks/post-merge
cat > "$hook" <<'EOF'
#!/usr/bin/env bash
set -e
"$HOME/snapCloud/bin/compress" "$HOME/snapCloud/static"
EOF
chmod +x "$hook"
ln -sf post-merge ~/snapCloud/.git/hooks/post-checkout
Alternative: post-receive for push-to-deploy setups
If a checkout receives pushes directly (i.e. it has its own bare repo
upstream on the server), install a post-receive hook on the bare
repo's side instead. It needs to find the work tree for the checkout:
#!/usr/bin/env bash
set -e
"$HOME/snapCloud/bin/compress" /path/to/checkout
Save as <bare-repo>/hooks/post-receive and chmod +x it.
Verifying compression is live
After a deploy, sanity-check that nginx is actually serving the pre-compressed bytes:
curl -sI -H 'Accept-Encoding: gzip' https://snap.berkeley.edu/snap/src/morphic.js \
| grep -i 'content-encoding\|content-length'
You should see content-encoding: gzip and a content-length close to
the on-disk size of src/morphic.js.gz (much smaller than the
uncompressed .js). If the length matches the uncompressed source,
gzip_static did not find the .gz — re-run bin/update-snap (for a
Snap! checkout) or bin/compress ~/snapCloud/static (for snapCloud)
to regenerate.
For lapis API responses, expect content-encoding: gzip but no
content-length (responses are chunked):
curl -sI -H 'Accept-Encoding: gzip' https://snap.berkeley.edu/api/v1/projects \
| grep -i 'content-encoding\|transfer-encoding'