Run it
Staging copies
Generated from docs/STAGING.md in the AstroBaaS repository. The repository is the source of truth; this page is a copy of it.
A staging site is a clone of production, and that is exactly what makes it dangerous: the switches that ought to make it safe live in the database, so they arrive with the clone. A fresh copy of a live shop starts out indexable, pointed at production’s webhook endpoints, and reporting into production’s analytics property.
A checklist would fix that. A checklist also gets skipped on the third refresh, which is why the three that matter are enforced by the environment instead.
Set one variable
STAGING=1
That is the whole switch. With it set, this deployment:
| Stays out of search | noindex on every page, Disallow: / in robots.txt, no sitemap, no feed, and llms.txt and openapi.json refuse to describe the site. |
| Fires no webhooks | fireEvent returns immediately. A test order cannot call the real fulfilment partner. |
| Loads no analytics | configuredAnalytics returns nothing, so production’s GA4 or Plausible property never sees test traffic. |
It is deliberately one-way. STAGING=1 can force a site to hide; nothing in
the environment can force a site to be indexed. A variable that could un-hide a
site the operator deliberately hid would be a way to publish a private site by
editing a deploy config.
The admin’s Settings → Reading screen says so in words when it is on, rather than showing a ticked box the operator never ticked — the checkbox keeps showing the stored value, and a note explains who is overriding whom.
The rest of the environment
STAGING=1 covers the three that travel in the database. These are separate
because they are environment to begin with, and getting them wrong is quieter:
# Its own everything. Sharing any of these with production is the whole risk.
AUTH_SECRET=<a different secret> # sharing it makes production sessions valid here
DATABASE_URL=<its own database> # or DB_PATH for the lowdb driver
UPLOADS_DIR=/var/www/shop-staging/shared/uploads
# Off, or pointed somewhere harmless.
SCHEDULER_DISABLED=1 # no scheduled publishing, no off-site backup
# EMAIL_TRANSPORT unset → falls back to console; nothing reaches a real inbox
# BACKUP_S3_* unset → staging must never write to production's bucket
# PAYMENTS_ENABLED unset → no live payment session can be created
AUTH_SECRET is the one people share by accident. It signs session cookies,
so a shared secret means a session minted on staging is valid on production and
the other way round — including one minted for a test admin account.
Refreshing from production
# 1. On production: export.
curl -sS -X GET https://shop.example.com/api/backup/export \
-H "Cookie: $ADMIN_COOKIE" -o backup.json
# 2. On staging: import.
curl -sS -X POST https://staging.example.com/api/backup/import \
-H "Cookie: $STAGING_COOKIE" -H 'Content-Type: application/json' \
--data-binary @backup.json
The backup tools currently work on the lowdb driver only; both endpoints
refuse with a 400 when DATABASE_URL is set. On a libSQL or relational staging
site, copy the database file or use the provider’s own clone, and copy
UPLOADS_DIR with rsync.
After every refresh
The import brings production’s settings back, so re-do the things that are
data rather than environment. STAGING=1 already neutralises indexing,
webhooks and analytics — these are the rest:
- Customer email addresses are now on staging. They are personal data in a second place. Either accept that and secure it like production, or scrub the customers, orders and subscribers collections after the import.
- Re-check Settings → Payments: a stored provider key is data and travelled with the clone.
Assert it, do not assume it
Commands rather than prose, because a checklist item you cannot run is a checklist item nobody runs:
curl -s https://staging.example.com/robots.txt | grep -q '^Disallow: /$' && echo "hidden OK"
curl -s https://staging.example.com/ | grep -q 'name="robots" content="noindex' && echo "noindex OK"
curl -s -o /dev/null -w '%{http_code}\n' https://staging.example.com/readyz
curl -s https://staging.example.com/sitemap.xml | grep -c '<url>' # expect 0
If the first two do not print OK, STAGING=1 is not reaching the process —
check the unit’s EnvironmentFile rather than editing settings in the admin,
because the admin cannot fix this one.
Why the environment and not a setting
Because a setting is data, and data is what a refresh overwrites. The failure this design prevents is specific and it has happened to other people: a staging clone of a live shop enters the index, ranks for the shop’s own product names, and splits its traffic — discovered weeks later, from Search Console, by somebody wondering why sales dipped.