From 314a80d6decdcd4a96f1a310e70818d325e56e4f Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Wed, 29 Apr 2026 04:19:20 -0600 Subject: [PATCH] docs: deployment scaffolding + logos + live cluster examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three additions to the docs site, all atomic to docs/: 1. Deployment configs (Dockerfile + Caddyfile + docker-compose.yml + .env.example + Makefile) mirroring bingham/cucx's pattern. The compose service uses caddy-docker-proxy labels with the operator's .mcp.l.supported.systems wildcard DNS pattern; suggested subdomain is mcaxl-docs.mcp.l.supported.systems. 2. Logo + favicon (forest-green palette matching the existing custom.css accent). Wordmark uses ui-monospace with currentColor so Starlight inverts on light/dark; icon-mark is a terminal chevron + three diminishing query-row lines (audit-by-query motif). 3. Live cluster examples in reference/tools.md for axl_version, axl_list_tables (route% pattern), and axl_describe_table (routepartition). Outputs sanitized per python.md PII rules (15.0.1.12900(234) → 15.0(1); cluster-fingerprinting build string removed). Build clean: 17 pages built, pagefind search index across all, favicon resolves to /favicon.svg, logo fingerprinted into _astro/. Not yet deployed — operator wires docker compose up when ready. --- docs/.dockerignore | 10 ++++ docs/.env.example | 18 ++++++ docs/Caddyfile | 31 +++++++++++ docs/Dockerfile | 62 +++++++++++++++++++++ docs/Makefile | 57 +++++++++++++++++++ docs/astro.config.mjs | 1 + docs/docker-compose.yml | 71 ++++++++++++++++++++++++ docs/public/favicon.svg | 8 +++ docs/src/assets/logo.svg | 18 ++++-- docs/src/content/docs/reference/tools.md | 49 ++++++++++++++++ 10 files changed, 321 insertions(+), 4 deletions(-) create mode 100644 docs/.dockerignore create mode 100644 docs/.env.example create mode 100644 docs/Caddyfile create mode 100644 docs/Dockerfile create mode 100644 docs/Makefile create mode 100644 docs/docker-compose.yml create mode 100644 docs/public/favicon.svg diff --git a/docs/.dockerignore b/docs/.dockerignore new file mode 100644 index 0000000..97aa7fe --- /dev/null +++ b/docs/.dockerignore @@ -0,0 +1,10 @@ +node_modules +dist +.astro +.git +.gitignore +.env +.env.* +!.env.example +*.log +.DS_Store diff --git a/docs/.env.example b/docs/.env.example new file mode 100644 index 0000000..7b11923 --- /dev/null +++ b/docs/.env.example @@ -0,0 +1,18 @@ +# mcaxl docs — local environment. +# +# Copy to .env and adjust. `.env` is gitignored. + +# Distinguishes this compose project from others on the host. +COMPOSE_PROJECT=mcaxl-docs + +# Public hostnames (resolved via the wildcard DNS for *.mcp.l.supported.systems +# that already points at the host's edge caddy-docker-proxy). +# DOMAIN → prod static build +# DEV_DOMAIN → dev server, hot-reload +DOMAIN=mcaxl-docs.mcp.l.supported.systems +DEV_DOMAIN=dev-mcaxl-docs.mcp.l.supported.systems + +# Mode switch — drives which compose profile is active. +# dev → astro dev server + HMR (hot reload) at DEV_DOMAIN +# prod → static build served by in-container Caddy at DOMAIN +COMPOSE_PROFILES=dev diff --git a/docs/Caddyfile b/docs/Caddyfile new file mode 100644 index 0000000..2f5f1a7 --- /dev/null +++ b/docs/Caddyfile @@ -0,0 +1,31 @@ +# Internal Caddy used by the prod container to serve the static site. +# caddy-docker-proxy terminates TLS at the edge, forwards to :80 here. + +:80 { + root * /srv + file_server + encode gzip zstd + + # Long-cache everything Astro puts under /_astro/ (hashed filenames) + @immutable path /_astro/* + header @immutable Cache-Control "public, max-age=31536000, immutable" + + # Short-cache HTML so content updates appear quickly after redeploy. + @html path_regexp htmlrx \.(html)?$ + header @html Cache-Control "public, max-age=60, must-revalidate" + + # Basic security headers + header { + X-Content-Type-Options nosniff + Referrer-Policy strict-origin-when-cross-origin + X-Frame-Options SAMEORIGIN + -Server + } + + # Render 404 for missing pages + handle_errors { + @404 expression {http.error.status_code} == 404 + rewrite @404 /404.html + file_server + } +} diff --git a/docs/Dockerfile b/docs/Dockerfile new file mode 100644 index 0000000..5100086 --- /dev/null +++ b/docs/Dockerfile @@ -0,0 +1,62 @@ +# mcaxl docs — multi-stage build. +# +# Stages: +# deps — install node_modules once, cached by package.json +# dev — runs `astro dev` with the source bind-mounted (compose `dev` profile) +# build — produces the static site in /app/dist +# prod — serves /app/dist with Caddy (static file server, no Node runtime) + +# ---------- Stage: deps -------------------------------------------------- +FROM mirror.gcr.io/library/node:22-alpine AS deps + +ENV ASTRO_TELEMETRY_DISABLED=1 \ + NODE_ENV=development + +WORKDIR /app + +COPY package.json package-lock.json* ./ +RUN --mount=type=cache,target=/root/.npm \ + npm install --no-audit --no-fund + +# ---------- Stage: dev --------------------------------------------------- +FROM mirror.gcr.io/library/node:22-alpine AS dev + +ENV ASTRO_TELEMETRY_DISABLED=1 \ + NODE_ENV=development \ + HOST=0.0.0.0 + +WORKDIR /app + +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +EXPOSE 4321 +CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"] + +# ---------- Stage: build ------------------------------------------------- +FROM mirror.gcr.io/library/node:22-alpine AS build + +ENV ASTRO_TELEMETRY_DISABLED=1 \ + NODE_ENV=production + +WORKDIR /app + +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# DOMAIN must be set at build time so Astro bakes the canonical site URL +# (sitemaps, og: tags, etc.) into the static output. +ARG DOMAIN +ENV DOMAIN=${DOMAIN} + +RUN npm run build + +# ---------- Stage: prod -------------------------------------------------- +# Static site served by Caddy. No Node in the final image. +FROM mirror.gcr.io/library/caddy:2-alpine AS prod + +COPY --from=build /app/dist /srv +COPY Caddyfile /etc/caddy/Caddyfile + +EXPOSE 80 +CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..c347914 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,57 @@ +# mcaxl docs — compose wrapper. +# +# Mode (dev/prod) is driven by COMPOSE_PROFILES in .env. Flip it there. +# `docker compose` honors COMPOSE_PROJECT and COMPOSE_PROFILES automatically. + +COMPOSE ?= docker compose + +.PHONY: help up down restart logs shell ps build clean prod dev net pull-prod pull-dev + +help: ## Show this help + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " \033[36m%-10s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) + +up: ## Start the active profile (set COMPOSE_PROFILES in .env) + $(COMPOSE) up -d --build + +dev: ## Start dev explicitly (hot reload at https://$${DEV_DOMAIN}) + COMPOSE_PROFILES=dev $(COMPOSE) up -d --build + +prod: ## Start prod explicitly (static site at https://$${DOMAIN}) + COMPOSE_PROFILES=prod $(COMPOSE) up -d --build + +down: ## Stop and remove all containers for this project + COMPOSE_PROFILES=dev $(COMPOSE) down + COMPOSE_PROFILES=prod $(COMPOSE) down + +restart: ## Restart the active profile + $(COMPOSE) restart + +logs: ## Tail logs for active services + $(COMPOSE) logs -f --tail=200 + +shell: ## Shell into the dev container + $(COMPOSE) exec docs-dev sh + +ps: ## Show running containers for this project + $(COMPOSE) ps + +build: ## Build images for both profiles + COMPOSE_PROFILES=dev,prod $(COMPOSE) build + +clean: ## Remove containers, images, and volumes for this project + COMPOSE_PROFILES=dev,prod $(COMPOSE) down --rmi local -v + +net: ## Create the external `caddy` network (one-time, host-wide) + docker network inspect caddy >/dev/null 2>&1 || docker network create caddy + +pull-prod: ## Deploy: git pull, rebuild, and restart the prod container + git pull --ff-only + COMPOSE_PROFILES=prod $(COMPOSE) up -d --build docs-prod + @echo + @COMPOSE_PROFILES=prod $(COMPOSE) ps docs-prod + +pull-dev: ## Deploy: git pull, rebuild, and restart the dev container + git pull --ff-only + COMPOSE_PROFILES=dev $(COMPOSE) up -d --build docs-dev + @echo + @COMPOSE_PROFILES=dev $(COMPOSE) ps docs-dev diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 541b781..d879ef7 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -52,6 +52,7 @@ export default defineConfig({ src: './src/assets/logo.svg', replacesTitle: false, }, + favicon: '/favicon.svg', customCss: ['./src/styles/custom.css'], // Per-page action buttons: "Copy Markdown" + "View in Markdown" diff --git a/docs/docker-compose.yml b/docs/docker-compose.yml new file mode 100644 index 0000000..40b34fe --- /dev/null +++ b/docs/docker-compose.yml @@ -0,0 +1,71 @@ +# mcaxl docs — dev/prod via compose profiles. +# +# Mode is driven by COMPOSE_PROFILES in .env: +# COMPOSE_PROFILES=dev → hot-reload astro dev server at https://${DEV_DOMAIN} +# COMPOSE_PROFILES=prod → static site via in-container Caddy at https://${DOMAIN} +# +# Both profiles sit behind the host's external caddy-docker-proxy on the +# `caddy` network. mcaxl is a public OSS project; no basic auth. +# +# Prerequisite: `docker network create caddy` (one-time, shared with the +# edge caddy-docker-proxy instance). + +services: + docs-dev: + profiles: ["dev"] + build: + context: . + dockerfile: Dockerfile + target: dev + image: ${COMPOSE_PROJECT}-dev + container_name: ${COMPOSE_PROJECT}-dev + restart: unless-stopped + environment: + ASTRO_TELEMETRY_DISABLED: "1" + NODE_ENV: development + DOMAIN: ${DEV_DOMAIN} # Vite HMR binds wss://${DEV_DOMAIN}:443 + volumes: + - .:/app + - /app/node_modules + - /app/.astro + networks: + - caddy + labels: + caddy: ${DEV_DOMAIN} + caddy.reverse_proxy: "{{upstreams 4321}}" + + # ── HMR-through-Caddy streaming tweaks ── + # Vite HMR doesn't send app-level pings; Caddy closes idle WebSockets + # after ~10-15s without these. Refs in ~/.claude/CLAUDE.md. + caddy.reverse_proxy.flush_interval: "-1" + caddy.reverse_proxy.transport: "http" + caddy.reverse_proxy.transport.read_timeout: "0" + caddy.reverse_proxy.transport.write_timeout: "0" + caddy.reverse_proxy.transport.keepalive: "5m" + caddy.reverse_proxy.transport.keepalive_idle_conns: "10" + caddy.reverse_proxy.stream_timeout: "24h" + caddy.reverse_proxy.stream_close_delay: "5s" + + caddy.encode: "gzip zstd" + + docs-prod: + profiles: ["prod"] + build: + context: . + dockerfile: Dockerfile + target: prod + args: + DOMAIN: ${DOMAIN} + image: ${COMPOSE_PROJECT}-prod + container_name: ${COMPOSE_PROJECT}-prod + restart: unless-stopped + networks: + - caddy + labels: + caddy: ${DOMAIN} + caddy.reverse_proxy: "{{upstreams 80}}" + caddy.encode: "gzip zstd" + +networks: + caddy: + external: true diff --git a/docs/public/favicon.svg b/docs/public/favicon.svg new file mode 100644 index 0000000..88c73c1 --- /dev/null +++ b/docs/public/favicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/docs/src/assets/logo.svg b/docs/src/assets/logo.svg index d46845b..d89aa25 100644 --- a/docs/src/assets/logo.svg +++ b/docs/src/assets/logo.svg @@ -1,6 +1,16 @@ -