AstroBaaS

Self-hosted, one process

Running in about
ten minutes.

Node 22 or newer and a machine you control. No container required, no managed service, and no account with anybody — including us.

Get it and install

Node 22.12 or newer — check with node -v. On Node 20, npx prints a screen of EBADENGINE warnings and create stops with one line saying which version to install. The install pulls the optional image library; let it.

npx astrobaas create my-site
cd my-site
npm install

To work on AstroBaaS itself rather than run it, clone the repository instead: github.com/operator888/astrobaas.

Check the secret

AUTH_SECRET signs every session cookie. Under 16 characters, or missing, and a production build refuses to start rather than pretending to be secure. create already wrote a fresh one into .env; give each server its own.

npx astrobaas init --force   # new .env, new secret
npx astrobaas secret         # or just print one to paste

Create your account

Interactive. It replaces the seed admin, so there is no default password left behind — a production build refuses to accept one anyway.

npm run setup
npm run dev        # http://localhost:4321/admin

Tell it its own address

Settings → Address of this CMS. Skip this and media URLs are guessed from whatever host each request arrived on — right for the admin, wrong for a storefront on another domain. The health check warns until it is set.

Point your frontend at it

Everything is JSON over HTTP. The response envelope carries media_base, which is what you join relative image paths to.

curl -s localhost:4321/api/products?limit=3 | jq
curl -s localhost:4321/openapi.json | jq '.paths | keys'

Deploy, and assert it

A reference nginx vhost and systemd unit are in deploy/, with limits measured against real traffic rather than chosen. The deep health check answers 503 when anything essential is broken, so a deploy script can simply stop.

npm run build
curl -fsS -H "Authorization: Bearer $HEALTH_TOKEN" \
  https://cms.example.com/api/health/deep

Worth knowing before you deploy

Four things that
fail quietly.

Body size
nginx defaults to 1 MB, so every image upload fails with a 413 the app never sees and cannot explain. The reference vhost raises it per location.
Readiness
Poll /readyz, not /healthz, before switching traffic over. Migrations run at boot, and only readyz waits for them.
Uploads path
Point it outside the release directory. A deploy that swaps the release takes the uploads with it otherwise, and every image added since the last build 404s.
Two addresses, not one
site_url is where the SITE lives — canonicals, sitemap, feed. public_site_urlis where the CMS itself answers, and it is the one that makes media_base right for a storefront on another domain. Setting only the first leaves the deep health check warning and the images resolving against whatever host asked.