Installing and Operating a QueryLayer Site
Bootstrap a site-local QueryLayer installation and connect it to an Astro website.
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:
- Opening
/adminon an unprovisioned installation redirects to/admin/setup(a one-time window, like WordPress'sinstall.php). - Confirm the site address (prefilled from your browser's address bar), optionally name the site, and create the first local administrator.
- Setup provisions the owner membership, organization, site record, and built-in editorial templates, then seeds a starter draft you can preview and publish immediately.
- The setup window locks itself permanently: once an owner exists, every
setup route returns 404 and
/adminserves 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
Finish setup (either path above).
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).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 rundocker compose up -d --force-recreate web).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
- Open
https://your-site.example/admin/and sign in with the administrator credentials you created. - 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
restrestart; 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
.envif you want recovery emails. - Port conflicts:
--http-portand--api-gw-portare yours to choose; the platform itself is not exposed.