Docs
GitHub
Get started

Installing and Operating a QueryLayer Site

Bootstrap a site-local QueryLayer installation and connect it to an Astro website.

Edit this page on GitHub ↗

This is the operator guide for one QueryLayer installation serving one website. The architecture rationale is in architecture.md; this page is the how-to.

#Requirements

  • A Linux or macOS host reachable at your site's domain
  • Docker Engine + Docker Compose (v2)
  • Node.js ≥ 22.12 (used by the setup scripts and admin build)
  • Roughly 4 GB RAM for the stack
  • The managed site's DNS pointing at the host (for public use)

#Human path: zero-question bootstrap + browser wizard

git clone https://github.com/archer-clawbot/querylayer.git
cd querylayer && npm install
./setup

./setup asks nothing and accepts no credentials. It generates fresh secrets, stages and starts the stack, applies every database migration, and prints the one line that matters:

Finish setup in the browser:
  http://localhost:8080/admin/setup/

Then, in the browser:

  1. Opening /admin on an unprovisioned installation redirects to /admin/setup (a one-time window, like WordPress's install.php).
  2. Confirm the site address (prefilled from your browser's address bar), optionally name the site, and create the first local administrator.
  3. Setup provisions the owner membership, organization, site record, and built-in editorial templates, then seeds a starter draft you can preview and publish immediately.
  4. The setup window locks itself permanently: once an owner exists, every setup route returns 404 and /admin serves the workspace directly.

Options for ./setup: --install-dir <path> (default <repo>/querylayer-install), --url <origin> (boot with a known domain instead of localhost), --http-port, --api-gw-port, --site-dist <path> (serve an existing Astro build), --skip-build, --no-start.

Re-running ./setup is safe: it refreshes runtime configuration and applies only new migrations while preserving secrets, data, and the provisioned installation.

#Agent path: fully noninteractive provisioning

Agents and CI that must create the administrator account without a browser:

node scripts/install-site.mjs \
  --site-url https://your-site.example \
  --admin-email owner@your-site.example \
  --admin-password '<strong password>' \
  --install-dir /opt/querylayer \
  --non-interactive

This performs everything the human path does plus owner creation, then the acceptance test can run immediately. Interactive prompts exist as a fallback when flags are omitted. Optional flags: --admin-base /admin, --http-port, --api-gw-port, --site-dist, --skip-build, --no-start.

Multiple installations may share one host: each install directory gets a unique Docker Compose project slug automatically.

#Using an existing Astro site

  1. Finish setup (either path above).

  2. In your site repo, set:

    PUBLIC_SUPABASE_URL=https://your-site.example/api
    PUBLIC_SUPABASE_PUBLISHABLE_KEY=<installation publishable key>
    

    The publishable key is in the installation's secrets/install-secrets.json (never commit it to the site repo).

  3. Build your site, then make the output reachable by the installation gateway: copy the build into the installation's public-site/ directory (pass it at setup time with --site-dist <dir>, or replace the directory and run docker compose up -d --force-recreate web).

  4. Publishing inside QueryLayer rebuilds your site through a delivery provider; with none configured, publication still completes in QueryLayer and delivery is recorded as pending/failed with the reason.

#Going live with TLS

Point your domain at the host, then:

node scripts/use-site-url.mjs https://your-site.example --install-dir /opt/querylayer
cd /opt/querylayer
# set PROXY_DOMAIN=your-site.example in .env first
docker compose -f docker-compose.yml -f docker-compose.caddy.yml up -d

use-site-url rewrites the installation's URL configuration, updates the recorded canonical site URL and site identity, and recreates the affected containers. Caddy terminates HTTPS on 443 with automatic Let's Encrypt; all platform services stay on the internal Docker network.

#First sign-in and publishing

  1. Open https://your-site.example/admin/ and sign in with the administrator credentials you created.
  2. A starter draft ("Welcome to QueryLayer") is waiting: edit it, preview the exact revision, and publish. Publishing advances the immutable pointer and records the job, preflight, and audit trail locally.

#Connecting agents

Create a connection in Admin → Agents (choose harness, scopes, expiry) and store the one-time bearer token. Point the agent harness at your installation's MCP endpoint:

https://your-site.example/api/functions/v1/querylayer-mcp
  • Codex: codex mcp add querylayer --url <endpoint> --bearer-token-env-var QUERYLAYER_AGENT_TOKEN
  • Hermes: hermes mcp add querylayer --url <endpoint> --auth header

Scopes map to server-enforced permissions; publishing always requires a human approval card unless you explicitly configure an autonomous publication policy. Details: agent-onboarding.md.

#Integrations (optional, all site-owned)

Secrets are set in /opt/querylayer/.env (consumed by the local edge functions) and take effect on docker compose up -d functions:

Integration Variables
Contact form notifications RESEND_API_KEY
Turnstile (forms) TURNSTILE_SECRET, TURNSTILE_EXPECTED_HOSTNAME
Google Search Console GOOGLE_SEARCH_CONSOLE_CLIENT_ID/SECRET, GSC_OAUTH_REDIRECT_URI
Google Analytics 4 GOOGLE_ANALYTICS_CLIENT_ID/SECRET, GA4_OAUTH_REDIRECT_URI
Bing / IndexNow BING_WEBMASTER_CLIENT_ID/SECRET, BING_OAUTH_REDIRECT_URI

OAuth redirect URIs are your installation's own URLs, e.g. https://your-site.example/api/functions/v1/gsc-connector?action=callback.

#Acceptance test

node scripts/acceptance-single-site.mjs \
  --base-url https://your-site.example \
  --admin-email owner@your-site.example \
  --admin-password '…'

Nine checks prove the site-local contract (see architecture.md). Run it after install, upgrade, or restore.

#Backup and restore

All content, identity, and governance state lives in the Postgres volume; uploaded files live in volumes/storage/.

# backup (from the installation directory)
docker compose exec -T db pg_dump -U postgres -Fc postgres > "backup-$(date +%F).dump"
tar czf storage-$(date +%F).tgz volumes/storage

# restore into a fresh installation directory with the SAME secrets
docker compose down -v
rm -rf volumes/db/data
docker compose up -d db
cat backup-YYYY-MM-DD.dump | docker compose exec -T db pg_restore -U postgres -d postgres --clean --if-exists
docker compose up -d
node /path/to/querylayer/scripts/acceptance-single-site.mjs --base-url …

Keep secrets/ and .env out of backups handed to third parties; restore requires the same JWT secret and database password.

#Upgrades

cd /path/to/querylayer   # git pull in the checkout
npm install
node scripts/install-site.mjs …   # same arguments as the original install

The installer is idempotent: new migrations apply in order, already-applied ones are skipped, the admin build is re-staged, and containers are recreated. Before upgrading, take a backup and (optionally) pin your current commit. If an upgrade misbehaves, restore the backup and re-run the previous commit.

#Uninstall

cd /opt/querylayer
docker compose --profile studio down -v   # also removes data volumes

The checkout directory and secrets remain; delete them manually if desired.

#Troubleshooting

  • docker compose ps — every core service should be healthy/running.
  • Gateway logs: docker compose logs web; platform: docker compose logs api-gw rest auth.
  • PostgREST 404 immediately after install: schema cache settles after the installer's rest restart; re-run the acceptance test to confirm.
  • Admin sign-in fails without SMTP configured: expected for password sign-in (no email needed). Configure SMTP in .env if you want recovery emails.
  • Port conflicts: --http-port and --api-gw-port are yours to choose; the platform itself is not exposed.