Skip to content

Operations

Health checks

Terminal window
astropress doctor # env contract, runtime plan, schema drift warnings
astropress doctor --json # machine-readable output
astropress doctor --strict # exit non-zero on any warning
astropress services verify # confirm content-services keys + service origin
bun run audit:security # inline-handler and hardening checks

astropress doctor warns on:

  • missing or weak SESSION_SECRET
  • scaffold-default ADMIN_PASSWORD still in use
  • ADMIN_BOOTSTRAP_DISABLED not set
  • missing .data/ directory
  • projects still using legacy ASTROPRESS_*PROVIDER vars

Local bootstrap

Terminal window
astropress new my-site
astropress new my-site --app-host vercel --content-services supabase
astropress new my-site --app-host cloudflare-pages --content-services cloudflare
cd my-site
bun install
astropress doctor
astropress services bootstrap
astropress services verify
astropress dev

Backup and restore

Terminal window
# Create a snapshot
astropress backup --project-dir <site> --out <snapshot-dir>
# Restore from a snapshot
astropress restore --project-dir <site> --from <snapshot-dir>

Snapshots are file-based exports. For hosted providers, supplement with provider-native point-in-time restore (Cloudflare D1 time travel, Supabase Point-in-Time Recovery).

Importing content

WordPress

Terminal window
astropress import wordpress --project-dir <site> --source export.xml

Optional flags:

  • --artifact-dir <dir> — write artifacts here (default: .astropress/import/)
  • --download-media — download attachment assets into artifacts/downloads/
  • --apply-local — apply staged artifacts into the local SQLite runtime
  • --resume — re-enter a previous import and skip already-downloaded media

The import is staged, not instant. It writes structured JSON artifacts (content, media, comments, users, taxonomies, redirects) for review before applying. Run without --apply-local first to inspect what will change.

Wix

Export your site from Wix’s dashboard (Settings → General → Export Site Data), then:

Terminal window
astropress import wix --project-dir <site> --source wix-export.csv

Same flags as WordPress: --artifact-dir, --download-media, --apply-local, --resume.

If you need to download the export programmatically, the Playwright-based credential fetcher handles authenticated Wix dashboard access — see the import wix --fetch flag.

Crawling any site

For sites without an export format:

Terminal window
astropress import crawl --project-dir <site> --url https://example.com

The crawler walks links from the start URL, extracts page titles and body HTML, and writes the results as importable content records. Useful for migrating from static generators or hand-authored HTML sites.

Content scheduling

Set a publish time in the post editor (Scheduled Publish Time field) to queue a draft for future publication.

The SQLite runtime exposes runScheduledPublishes() which promotes all due posts atomically:

import { runScheduledPublishes } from "astropress/sqlite-admin-runtime";
// Call this on a schedule — e.g., every 5 minutes
const published = runScheduledPublishes(db);
console.log(`Published ${published} scheduled posts`);

Cloudflare Workers: wire it in a scheduled handler in worker.ts:

export default {
async scheduled(_event, env, _ctx) {
const runtime = await createRuntime(env);
await runtime.content.runScheduledPublishes();
},
};

GitHub Actions: use a cron workflow to hit a secured API route that calls runScheduledPublishes().

The posts list shows a Scheduled filter tab. Posts stay draft until runScheduledPublishes fires; cancelling clears the scheduled time.

Secret handling

Before any real deployment:

  1. Generate a strong SESSION_SECRET: SESSION_SECRET=$(openssl rand -hex 32)
  2. Change ADMIN_PASSWORD and EDITOR_PASSWORD from scaffold defaults
  3. Set ADMIN_BOOTSTRAP_DISABLED=1 once named admin accounts exist
  4. Set TURNSTILE_SECRET_KEY before exposing the login form publicly

Secret rotation

Astropress supports a two-phase session-secret rotation window.

Package/runtime-managed sessions

  1. Deploy the new secret as SESSION_SECRET
  2. Keep the previous secret in SESSION_SECRET_PREV
  3. Wait for old sessions to expire naturally, or revoke them explicitly once the migration window is complete
  4. Remove SESSION_SECRET_PREV in a follow-up deploy

During the rotation window:

  • existing sessions signed with SESSION_SECRET_PREV remain valid
  • all newly created sessions are signed only with SESSION_SECRET

If you use the advanced root-secret override instead of SESSION_SECRET, the same pattern applies with ASTROPRESS_ROOT_SECRET and ASTROPRESS_ROOT_SECRET_PREV.

Cloudflare adapter sessions

  1. Deploy the new secret as CLOUDFLARE_SESSION_SECRET
  2. Keep the previous secret in CLOUDFLARE_SESSION_SECRET_PREV
  3. Wait for old sessions to expire naturally, or revoke them explicitly once the migration window is complete
  4. Remove CLOUDFLARE_SESSION_SECRET_PREV in a follow-up deploy

During the rotation window:

  • existing sessions signed with CLOUDFLARE_SESSION_SECRET_PREV remain valid
  • all newly created sessions are signed only with CLOUDFLARE_SESSION_SECRET

To revoke all active sessions immediately:

UPDATE admin_sessions SET revoked_at = CURRENT_TIMESTAMP
WHERE revoked_at IS NULL;

Schema migrations

Framework-managed (SQLite)

Most upgrades require no manual steps for SQLite deployments. On every boot, Astropress applies any new ALTER TABLE ... ADD COLUMN statements idempotently. Applied migrations are recorded in schema_migrations.

User-managed migrations

Place numbered .sql files in your migrations/ directory:

migrations/
0001_add_event_taxonomy.sql
0002_add_event_date_index.sql
Terminal window
astropress db migrate --migrations-dir ./migrations
astropress db migrate --migrations-dir ./migrations --dry-run # preview only

Companion .down.sql files are read and stored in schema_migrations.rollback_sql for safe rollback. To apply the most recent migration’s rollback SQL:

Terminal window
astropress db rollback # apply rollback_sql from the last migration
astropress db rollback --dry-run # preview what would be rolled back

Cloudflare D1 migrations

astropress db migrate --target=d1 applies pending migration files to your D1 database via wrangler d1 execute. Requirements:

  • wrangler on PATH: bun add -g wrangler or npm install -g wrangler
  • Authenticated: wrangler login or CLOUDFLARE_API_TOKEN env var
  • CLOUDFLARE_D1_BINDING env var set to your D1 binding name (default: DB)
Terminal window
# Preview which files would run (no writes):
astropress db migrate --target=d1 --dry-run
# Apply to the remote D1 database:
astropress db migrate --target=d1

Supabase migrations

Supabase projects use the Supabase CLI for schema migrations. Astropress migration .sql files are compatible — place them in supabase/migrations/ and use the Supabase CLI workflow:

Terminal window
# Link to your project (once):
supabase link --project-ref <ref>
# Push pending migrations to the remote database:
supabase db push

Version upgrade procedure

  1. Snapshot: astropress backup --project-dir <site> --out <snapshot-dir>
  2. Upgrade the package: bun add astropress@latest
  3. Run astropress doctor — flags env contract changes and schema drift
  4. For SQLite: run astropress db migrate (defaults to --target=local)
  5. For D1: run astropress db migrate --target=d1
  6. For Supabase: run supabase db push
  7. Deploy. If migration fails, restore from snapshot and replay in isolation.

Caching

applyAstropressSecurityHeaders() sets Cache-Control by area:

AreaValueEffect
publicpublic, max-age=300, s-maxage=3600, stale-while-revalidate=864005 min browser / 1 hr CDN
adminprivate, no-storeNever cached
authprivate, no-storeNever cached
apiprivate, no-storeNever cached

CDN purge on publish: Astropress fires purgeCdnCache(slug, config) on every content publish — supports Cloudflare Cache API and generic webhook URLs:

registerCms({
cdnPurgeWebhook: "https://api.netlify.com/build_hooks/your-hook-id",
});

Static host publishing

Full rebuild

Terminal window
astropress build # full Astro build → dist/
astropress publish # build + push to configured static host

Incremental rebuild

For sites with large page counts, use --incremental to regenerate only the pages affected by a given set of slug changes:

Terminal window
astropress publish --incremental --slugs /blog/my-post,/blog/another-post
echo -e "/blog/my-post\n/blog/another-post" | astropress publish --incremental --stdin
ModeBuild timeBest for
Full rebuildProportional to site sizeSmall sites, release deploys
IncrementalProportional to changed pagesLarge sites, frequent edits

For sites with fewer than ~500 pages, full rebuild is simpler and fast enough.

Observability

Structured logs

Set LOG_LEVEL to emit structured JSON lines to stderr. Fields: level, context, message, timestamp, requestId.

Metrics endpoint

GET /ap-api/v1/metrics Bearer token required (content:read scope)

Returns { posts, pages, media, comments, uptime }.

Prometheus metrics

Enable the unauthenticated Prometheus text format endpoint:

registerCms({
monitoring: { prometheusEnabled: true },
});
GET /ap/metrics No authentication required
Content-Type: text/plain; version=0.0.4

Exported metrics: ap_content_total{kind}, ap_media_total, ap_uptime_seconds.

Incident handling

Auth breach

  1. Revoke all sessions:
    UPDATE admin_sessions SET revoked_at = CURRENT_TIMESTAMP
    WHERE revoked_at IS NULL;
  2. Rotate SESSION_SECRET / SESSION_SECRET_PREV and CLOUDFLARE_SESSION_SECRET / CLOUDFLARE_SESSION_SECRET_PREV
  3. Audit admin_sessions and audit_events for suspicious activity

Data loss

  1. Restore from the last clean backup
  2. Run astropress doctor and astropress services verify
  3. Re-apply content created since the snapshot using audit logs as reference

Admin panel down

  1. Run astropress doctor — check env and service health
  2. Check server logs for runtime errors or migration failures
  3. If a failed migration caused it: restore from snapshot, replay migration in isolation, then redeploy

Disaster recovery

RTO / RPO targets

TierData storeRPORTO
Local + SQLiteSQLite fileLast astropress backup snapshot< 5 min
Cloudflare Pages + D1Cloudflare D1D1 time-travel (30 days)< 15 min
SupabasePostgreSQLPoint-in-time recovery (up to 7 days on Pro)< 30 min

Post-restore checklist

  • astropress doctor — all checks pass
  • astropress services verify — database connectivity confirmed
  • Admin panel login succeeds
  • GET /ap/health returns { "status": "ok" }