Skip to content

Installation

Theodor Dimitriou edited this page Sep 24, 2026 · 2 revisions

How to get AstroBaaS running: on your own machine for development, and as a production build you start yourself. It is for anyone installing the CMS for the first time; for putting it on a server behind a reverse proxy, continue with Deployment.

AstroBaaS is pre-alpha (version 0.1.3). Read Security before exposing an install to the internet.

Requirements

Requirement Notes
Node.js 22.12 or newer Enforced by engines in package.json; .nvmrc pins 22.12.
npm Ships with Node. The repository has a package-lock.json.
git To clone the repository.
Disk for data The default database is a JSON file plus an uploads directory. Both must be on persistent, writable storage in production.
sharp (optional dependency) Installed automatically by npm install where a prebuilt binary exists. Without it, uploads get no dimensions and no WebP derivatives; the deep health check reports this as image_pipeline.

No separate database server is needed for the default setup. SQLite and remote libSQL (Turso) are optional; see Choosing a database driver.

Running the CMS vs. the npm package

These are two different things, and it matters which one you want.

You want to... Use
Run the CMS: admin, public site, REST API Clone the repository. This is the supported install.
Call a running AstroBaaS from your own project (astrobaas/client), write a plugin or theme against astrobaas/core, or use the CLI npm install astrobaas@alpha
Let an AI agent drive a running install npx -y astrobaas-mcp (a separate, zero-dependency package)

The astrobaas npm package contains the libraries (astrobaas/core, astrobaas/client, astrobaas/plugins), the astrobaas CLI and a few helper scripts. It does not contain the application source, so installing it does not give you a running site. Pre-1.0 releases are published under the alpha dist-tag; see Upgrading.

npx astrobaas init is not a project scaffolder either: it writes a .env file with a freshly generated AUTH_SECRET in the current directory (and refuses to overwrite an existing one without --force). npx astrobaas secret prints a new secret without touching any file.

See SDK, CLI and MCP for the package in detail.

Quick start (development)

git clone https://github.com/operator888/astrobaas.git
cd astrobaas
cp .env.example .env       # optional in development
npm install
npm run dev                # http://localhost:4321

npm run dev runs astro dev. With no configuration at all it:

  • uses the JSON-file database (db.json in the project root, created on first boot);
  • stores uploads under public/uploads/;
  • seeds a bootstrap admin account and two demo blog posts;
  • logs outgoing email to the console instead of sending it.

Open http://localhost:4321/login to sign in and http://localhost:4321/admin for the admin.

Copying .env.example is optional in development. The file is almost entirely commented out; the only active lines are the AUTH_SECRET placeholder and SEED_DEMO_CONTENT=1.

Loading .env in development

AstroBaaS reads its configuration from process.env. npm run dev runs scripts/dev.mjs, which loads .env.local and then .env into it before starting astro dev:

npm run dev                       # loads .env.local, then .env
npm run dev -- --port 5000        # arguments pass through to astro dev

Variables already set in your shell win over the files.

On 0.1.3 and earlier, npm run dev ran plain astro dev, which leaves process.env empty, so values in .env (for example SITE_LOCALES) silently did nothing. Run node scripts/dev.mjs instead on those versions. The same rule applies to npm start and the offline scripts: none of them read .env on their own (see below).

First sign-in

On the first boot of an empty database, AstroBaaS creates one admin account. What it looks like depends on the environment:

Variable Effect Default
ADMIN_EMAIL Address of the bootstrap admin. An invalid value falls back to the default with a warning. admin@local
ADMIN_NAME Display name, so demo posts carry your byline. Admin
ADMIN_PASSWORD Password of the bootstrap admin. Development: admin. Production: randomly generated (see below).

These are read once, when an empty database is first opened. Changing them later does nothing; edit the account under Admin → Users instead.

admin@local cannot receive mail, so a password reset has nowhere to go. Set ADMIN_EMAIL to a real address before the first start of anything you intend to keep.

In development

Sign in with admin@local / admin (unless you set ADMIN_EMAIL or ADMIN_PASSWORD). Change the password straight away.

In production

"Production" here means a built server: npm run build output started with npm start or node ./dist/server/entry.mjs. AstroBaaS decides this from the build itself, not only from NODE_ENV, so the bare node ./dist/server/entry.mjs path is treated as production even when NODE_ENV is unset (isProductionRuntime() in src/lib/auth.ts).

In production:

  1. No well-known default password is seeded. If ADMIN_PASSWORD is not set, a random password is generated and printed once to stdout, in a box headed "generated an admin password for this install". Under systemd that means the journal; under Docker, the container log. It is not stored anywhere in plain text and is never shown again.
  2. The login refuses the seeded admin password, even for an older install that still has it. The response is HTTP 403 with the code SEED_PASSWORD_REFUSED and a message telling you to run npm run reset-password. ALLOW_SEED_PASSWORD=1 overrides this for a trusted private deployment; do not use it on anything reachable from the internet.
  3. AUTH_SECRET must be real. A missing secret, one shorter than 16 characters, or the .env.example placeholder is refused; nobody can sign in until you replace it, and the reason is logged. Generate one with openssl rand -hex 32 (or npx astrobaas secret).
  4. Session and CSRF cookies get the Secure flag, so the site must be served over HTTPS. To test a production build over plain http://localhost, set COOKIE_SECURE=0.

The simplest route is to set ADMIN_EMAIL and ADMIN_PASSWORD in the environment before the first start.

npm run setup and npm run reset-password

Two offline scripts manage admin accounts without the web UI:

npm run setup                          # create or replace an admin account (prompts)
npm run reset-password -- you@example.com   # set a new password for an existing user
  • setup prompts for email, password (at least 8 characters, entered twice) and display name, then writes an admin account. If you create an account with a different address, the seeded admin@local account is removed. It is safe to re-run.

  • reset-password prompts for a new password for an existing user and errors out if the email does not exist.

  • Password input is not masked. For unattended use, pipe the answers in, for example:

    printf 'you@example.com\nyour-strong-password\nyour-strong-password\nYour Name\n' | npm run setup

Both work only with the JSON-file database. They honour DB_PATH, and on an install configured with DATABASE_URL (SQLite, libSQL or relational) they refuse to run and tell you so, because there is no db.json to write to. On those installs, manage users through the admin screens or the REST API, set ADMIN_EMAIL/ADMIN_PASSWORD before the first boot, or use the forgot-password flow once email is configured (see Email).

Because npm scripts do not read .env, export DB_PATH in your shell first if your install uses a non-default path.

Production build and start

npm run build
node --env-file=.env ./dist/server/entry.mjs

npm run build runs astro check, astro build, and builds dist/mail-test.mjs (the email self-test used after deploys).

npm start does not read .env. It runs NODE_ENV=production node ./dist/server/entry.mjs and nothing more. Either export the variables yourself, or start Node with --env-file as above. In the reference systemd unit the variables come from EnvironmentFile=.

To try a production build on your own machine over plain HTTP, put these in .env:

AUTH_SECRET=<output of: openssl rand -hex 32>
COOKIE_SECURE=0

The built server is the @astrojs/node standalone server. It listens on the address and port given by the HOST and PORT environment variables (the Docker image sets HOST=0.0.0.0, PORT=4321; the reference systemd unit uses HOST=127.0.0.1, PORT=3002).

Build-time variables

Some variables are read by astro.config.ts while the bundle is built, so they must be set before npm run build, not only at runtime:

  • SITE_URL (Astro's site, used for absolute URLs)
  • every CSP_* variable and ANALYTICS_PROVIDERS (the Content-Security-Policy is assembled at build time)

See Configuration.

Docker

The repository includes a Dockerfile and a docker-compose.yml:

echo "AUTH_SECRET=$(openssl rand -hex 32)" > .env
docker compose up -d --build

The image runs node ./dist/server/entry.mjs as a non-root user, sets NODE_ENV=production, and stores the database, uploads and private uploads under the /app/data volume. The compose file publishes the port on 127.0.0.1:4321 only; put a reverse proxy in front for public traffic. More in Deployment.

Choosing a database driver

The storage engine is selected by environment variables at boot. All application code goes through one Storage interface, and the full HTTP smoke suite runs against all three drivers in CI.

DATABASE_URL DATABASE_DRIVER Driver Use for
unset — lowdb: one JSON file at DB_PATH (default ./db.json) Local development; a small single-process site.
file:./data/astrobaas.db unset libSQL doc-blob, local SQLite file Durable single host.
libsql://<db>.turso.io plus DATABASE_AUTH_TOKEN unset libSQL doc-blob, remote (Turso) Durable, not tied to one machine's disk.
file: or libsql:// relational Relational libSQL: one row per entity Production, especially more than one writer or host.

How to choose:

  • lowdb is zero-configuration and the default. It is a single-process file: it cannot be shared between replicas and is lost on ephemeral filesystems.
  • Doc-blob keeps the whole dataset in one row, so writes are last-write-wins. That is fine for one long-lived process, and risky if two processes write (a second instance, or a CLI writing while the site runs).
  • Relational gives row-level writes and is what the project recommends for production multi-writer or multi-host use.

Examples:

# Local SQLite, relational
DATABASE_URL=file:./data/astrobaas.db
DATABASE_DRIVER=relational

# Turso, relational
DATABASE_URL=libsql://my-app.turso.io
DATABASE_AUTH_TOKEN=...
DATABASE_DRIVER=relational

On first boot against an empty database, AstroBaaS creates its schema defaults and the bootstrap admin automatically, on every driver. There is no separate migration command: pending schema migrations run at startup.

Switching drivers later is not automatic. Backup export and restore are same-driver only, and doc-blob and relational are different schemas. Choose before you have content you care about. STORAGE.md explains how to move content between drivers (through the API or by re-running an importer) and how to back up SQLite files safely under WAL.

Where data lives

What Default location Override
JSON database ./db.json DB_PATH
SQLite database the path in the file: URL DATABASE_URL
Public uploads public/uploads/<yyyy>/<mm>/... UPLOADS_DIR
Private uploads (files attached to public forms) private-uploads/ beside the database PRIVATE_UPLOADS_DIR

db.json and uploads are gitignored. In development, deleting db.json resets the install to a fresh seed.

Verifying the install

curl -s http://localhost:4321/healthz    # liveness
curl -s http://localhost:4321/readyz     # readiness: 200 once storage is reachable and the schema is current

Signed in as an admin, GET /api/health/deep runs the fuller checks (image pipeline, uploads writable, database write, email channel and more). See Deployment.

To check email before you depend on it:

npm run mail:test -- you@example.com

Next steps

See also

Clone this wiki locally