Installing and Operating a QueryLayer Site
Choose the installation path that matches your hosting and see what is available today.
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.
#Choose an installation path
QueryLayer is being built to install into the hosting you already use, and to detect which path applies instead of asking you to decide. The full contract is the universal installation plan.
| Path | Status | Guide |
|---|---|---|
| Shared hosting ZIP (SiteGround, Hostinger, cPanel) | In development | None yet (no Node/Docker on the host) |
| Vercel + Supabase | In development | None yet (browser authorization) |
| Native MySQL shared hosting | Proposed | None yet |
| VPS / Docker | Available now — Advanced | This guide |
The rest of this document is the Advanced (VPS/Docker) operator guide for the currently verified path.
#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.