Compose convention: dev/prod target switch, HMR-friendly Caddy labels
Aligns with the warehack-ing host convention (matches birdcage-docs):
Dockerfile — multi-stage with named targets:
base shared npm ci + COPY
dev npx astro dev --host 0.0.0.0 --port 4321 (HMR enabled)
build npx astro build (produces /app/dist)
prod caddy:2-alpine serves /srv with /health probe + HEALTHCHECK
docker-compose.yml — picks the target via APP_ENV in .env:
build.target = ${APP_ENV:-dev}
caddy.reverse_proxy upstream port = ${APP_PORT:-4321}
Adds the streaming/HMR caddy labels CLAUDE.md requires for Vite-over-
Caddy: flush_interval=-1, transport.read/write_timeout=0, keepalive,
stream_timeout=24h, stream_close_delay=5s
Volume mounts ./src, ./public, astro.config.mjs (live in dev, harmless
in prod since the prod stage doesn't reference /app)
astro.config.mjs — vite.server.hmr block now picks up VITE_HMR_HOST
env var and configures host/protocol/clientPort for wss-on-443 HMR
through Caddy. Falls back to undefined when unset so plain
'npm run dev' still works.
Caddyfile — adds /health endpoint for the Dockerfile HEALTHCHECK,
SPA-style try_files fallback chain (path -> path/index.html -> /index.html).
Makefile — adds 'make dev' / 'make prod' that rewrite APP_ENV+APP_PORT
in .env then rebuild. Tail-logs after up/restart so you see startup
diagnostics without a separate 'make logs'.
.env.example — five vars (COMPOSE_PROJECT_NAME, APP_ENV, APP_PORT,
PUBLIC_DOMAIN, VITE_HMR_HOST), defaulting to prod mode against the
public hai-omni-pro-ii.warehack.ing domain. .env stays gitignored so
each environment (local, remote) can override.
Local container rebuilt + recreated, healthy, /health returns 200,
PUBLIC_DOMAIN locally still set to .l.warehack.ing in the host .env.
This commit is contained in:
parent
0525895dbe
commit
17cd57f1bf
20
.env.example
20
.env.example
@ -1,3 +1,17 @@
|
|||||||
# Copy to .env and adjust as needed.
|
# Copy to .env and edit. `make dev` and `make prod` rewrite APP_ENV/APP_PORT
|
||||||
COMPOSE_PROJECT=hai-omni-docs
|
# in place, so leave those two lines as-is — the Makefile owns them.
|
||||||
DOMAIN=hai-omni-pro-ii.l.warehack.ing
|
|
||||||
|
# Container + network names. Keep distinct so multiple stacks don't clash.
|
||||||
|
COMPOSE_PROJECT_NAME=omni-pca-docs
|
||||||
|
|
||||||
|
# Mode toggle. dev = Astro dev server + HMR on 4321; prod = static caddy on 80.
|
||||||
|
APP_ENV=prod
|
||||||
|
APP_PORT=80
|
||||||
|
|
||||||
|
# Public hostname Caddy will route to this container. Match the DNS record.
|
||||||
|
PUBLIC_DOMAIN=hai-omni-pro-ii.warehack.ing
|
||||||
|
|
||||||
|
# Vite HMR client hostname — only used in dev. Same as PUBLIC_DOMAIN when
|
||||||
|
# you want to develop through the live reverse proxy with a real cert,
|
||||||
|
# or 'localhost' for local-only.
|
||||||
|
VITE_HMR_HOST=hai-omni-pro-ii.warehack.ing
|
||||||
|
|||||||
25
Caddyfile
25
Caddyfile
@ -1,22 +1,13 @@
|
|||||||
# Inner Caddy: just serves the static dist on :80.
|
|
||||||
# The host's caddy-docker-proxy handles TLS and routing for the public hostname.
|
|
||||||
|
|
||||||
:80 {
|
:80 {
|
||||||
root * /srv
|
root * /srv
|
||||||
encode zstd gzip
|
encode gzip
|
||||||
|
|
||||||
|
handle /health {
|
||||||
|
respond "ok" 200
|
||||||
|
}
|
||||||
|
|
||||||
|
handle {
|
||||||
|
try_files {path} {path}/index.html {path}/ /index.html
|
||||||
file_server
|
file_server
|
||||||
|
|
||||||
# SPA-ish fallback: prefer the directory's index.html, then a 404 page.
|
|
||||||
try_files {path} {path}/ /404.html
|
|
||||||
|
|
||||||
header {
|
|
||||||
# Light security defaults; the outer Caddy can override.
|
|
||||||
X-Content-Type-Options "nosniff"
|
|
||||||
Referrer-Policy "strict-origin-when-cross-origin"
|
|
||||||
Permissions-Policy "interest-cohort=()"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Long cache for fingerprinted assets emitted by Astro.
|
|
||||||
@hashed path_regexp \.[A-Za-z0-9_-]{8,}\.(js|css|woff2?|svg|png|jpg|jpeg|webp|avif)$
|
|
||||||
header @hashed Cache-Control "public, max-age=31536000, immutable"
|
|
||||||
}
|
}
|
||||||
|
|||||||
53
Dockerfile
53
Dockerfile
@ -1,39 +1,34 @@
|
|||||||
# syntax=docker/dockerfile:1.7
|
# syntax=docker/dockerfile:1.7
|
||||||
|
#
|
||||||
|
# Multi-stage Dockerfile matching the warehack-ing services convention:
|
||||||
|
# * dev — Astro dev server with HMR, source-mounted via compose volumes
|
||||||
|
# * build — runs `astro build`, produces /app/dist
|
||||||
|
# * prod — Caddy serves the static dist on port 80, plus a /health probe
|
||||||
|
#
|
||||||
|
# Pick the stage at compose-build time via APP_ENV={dev,prod} in .env.
|
||||||
|
|
||||||
# ---- builder ----
|
# ── base: shared dependency install ──────────────────────────────
|
||||||
# Pinned to lts-alpine so it works against Docker Hub's cached image set.
|
FROM node:lts-slim AS base
|
||||||
# Bump to node:25-alpine when 25 stabilises in the registry.
|
|
||||||
FROM node:lts-alpine AS builder
|
|
||||||
|
|
||||||
ENV ASTRO_TELEMETRY_DISABLED=1 \
|
|
||||||
NODE_ENV=production \
|
|
||||||
CI=true
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
ENV ASTRO_TELEMETRY_DISABLED=1
|
||||||
# Install deps in their own layer for better caching.
|
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN --mount=type=cache,target=/root/.npm \
|
RUN npm ci
|
||||||
npm ci --include=dev
|
|
||||||
|
|
||||||
# Copy the rest of the source and build.
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
# ---- runtime ----
|
# ── dev: Astro dev server with HMR ──────────────────────────────
|
||||||
FROM caddy:latest AS runtime
|
FROM base AS dev
|
||||||
|
ENV HOST=0.0.0.0
|
||||||
|
EXPOSE 4321
|
||||||
|
CMD ["npx", "astro", "dev", "--host", "0.0.0.0", "--port", "4321"]
|
||||||
|
|
||||||
# Run as non-root.
|
# ── build: static site generation ───────────────────────────────
|
||||||
RUN addgroup -S docs && adduser -S -G docs docs
|
FROM base AS build
|
||||||
|
RUN npx astro build
|
||||||
|
|
||||||
WORKDIR /srv
|
# ── prod: Caddy serving static files ────────────────────────────
|
||||||
|
FROM caddy:2-alpine AS prod
|
||||||
COPY --from=builder /app/dist /srv
|
|
||||||
COPY Caddyfile /etc/caddy/Caddyfile
|
COPY Caddyfile /etc/caddy/Caddyfile
|
||||||
|
COPY --from=build /app/dist /srv
|
||||||
# caddy:2-alpine ships with a sane default user model; keep ports unprivileged.
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
USER docs
|
CMD wget -qO- http://127.0.0.1:80/health || exit 1
|
||||||
|
|
||||||
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
|
|
||||||
|
|||||||
71
Makefile
71
Makefile
@ -1,50 +1,37 @@
|
|||||||
# hai-omni-docs Makefile
|
.PHONY: up down logs rebuild dev prod clean ci
|
||||||
#
|
|
||||||
# Local dev: make dev (Astro dev server, hot reload, no docker)
|
|
||||||
# Smoke build: make ci (npm run build — verify static output)
|
|
||||||
# Deploy: make build && make up
|
|
||||||
#
|
|
||||||
# Reads .env if present so DOMAIN / COMPOSE_PROJECT propagate to compose.
|
|
||||||
|
|
||||||
SHELL := /bin/bash
|
up:
|
||||||
COMPOSE := docker compose
|
docker compose up -d --build
|
||||||
|
|
||||||
.PHONY: help dev install ci build up down restart logs ps clean
|
|
||||||
|
|
||||||
help:
|
|
||||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
|
|
||||||
| awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-12s\033[0m %s\n", $$1, $$2}'
|
|
||||||
|
|
||||||
install: ## Install npm dependencies
|
|
||||||
npm install
|
|
||||||
|
|
||||||
dev: ## Start Astro dev server with hot reload (host)
|
|
||||||
npm run dev
|
|
||||||
|
|
||||||
ci: ## Smoke-test the static build locally
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
build: ## Build the docker image
|
|
||||||
$(COMPOSE) build
|
|
||||||
|
|
||||||
up: ## Start the docs container in the background
|
|
||||||
$(COMPOSE) up -d
|
|
||||||
@echo
|
@echo
|
||||||
@echo "==> Tailing recent logs ..."
|
@echo "==> Tailing recent logs ..."
|
||||||
@$(COMPOSE) logs -f --tail=20
|
@docker compose logs -f --tail=20
|
||||||
|
|
||||||
down: ## Stop and remove the docs container
|
down:
|
||||||
$(COMPOSE) down
|
docker compose down
|
||||||
|
|
||||||
restart: ## Recreate the docs container
|
logs:
|
||||||
$(COMPOSE) up -d --force-recreate
|
docker compose logs -f --tail=50
|
||||||
@$(COMPOSE) logs -f --tail=20
|
|
||||||
|
|
||||||
logs: ## Follow logs
|
rebuild:
|
||||||
$(COMPOSE) logs -f --tail=50
|
docker compose down
|
||||||
|
docker compose up -d --build
|
||||||
|
@docker compose logs -f --tail=20
|
||||||
|
|
||||||
ps: ## Show container status
|
# Flip .env into dev mode (Astro dev server + HMR on 4321) and rebuild.
|
||||||
$(COMPOSE) ps
|
dev:
|
||||||
|
@sed -i 's/^APP_ENV=.*/APP_ENV=dev/' .env
|
||||||
|
@sed -i 's/^APP_PORT=.*/APP_PORT=4321/' .env
|
||||||
|
$(MAKE) rebuild
|
||||||
|
|
||||||
clean: ## Remove build artifacts
|
# Flip .env into prod mode (Caddy serving static dist on 80) and rebuild.
|
||||||
rm -rf dist .astro
|
prod:
|
||||||
|
@sed -i 's/^APP_ENV=.*/APP_ENV=prod/' .env
|
||||||
|
@sed -i 's/^APP_PORT=.*/APP_PORT=80/' .env
|
||||||
|
$(MAKE) rebuild
|
||||||
|
|
||||||
|
clean:
|
||||||
|
docker compose down --rmi local -v
|
||||||
|
|
||||||
|
# Smoke-test the static build outside Docker.
|
||||||
|
ci:
|
||||||
|
npm ci && npm run build
|
||||||
|
|||||||
@ -84,6 +84,17 @@ export default defineConfig({
|
|||||||
vite: {
|
vite: {
|
||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
|
// HMR behind Caddy reverse proxy: explicit hostname + wss + 443
|
||||||
|
// (per CLAUDE.md "HMR Behind Reverse Proxy" section). Falls
|
||||||
|
// back to defaults when no VITE_HMR_HOST env var is set so
|
||||||
|
// `npm run dev` outside Docker still works.
|
||||||
|
hmr: process.env.VITE_HMR_HOST
|
||||||
|
? {
|
||||||
|
host: process.env.VITE_HMR_HOST,
|
||||||
|
protocol: 'wss',
|
||||||
|
clientPort: 443,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,18 +1,41 @@
|
|||||||
|
# Astro + Starlight docs site for warehack.ing/omni-pca.
|
||||||
|
#
|
||||||
|
# Two modes via APP_ENV in .env:
|
||||||
|
# APP_ENV=dev APP_PORT=4321 astro dev with HMR (volume-mounted)
|
||||||
|
# APP_ENV=prod APP_PORT=80 caddy serving the built static dist
|
||||||
|
#
|
||||||
|
# Switch with `make dev` or `make prod` (both rebuild + restart).
|
||||||
|
|
||||||
services:
|
services:
|
||||||
docs:
|
docs:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
target: ${APP_ENV:-dev}
|
||||||
image: ${COMPOSE_PROJECT:-hai-omni-docs}/docs:latest
|
container_name: ${COMPOSE_PROJECT_NAME:-omni-pca-docs}
|
||||||
container_name: ${COMPOSE_PROJECT:-hai-omni-docs}-docs
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- PUBLIC_DOMAIN=${PUBLIC_DOMAIN}
|
||||||
|
- VITE_HMR_HOST=${VITE_HMR_HOST}
|
||||||
networks:
|
networks:
|
||||||
- caddy
|
- caddy
|
||||||
expose:
|
|
||||||
- "80"
|
|
||||||
labels:
|
labels:
|
||||||
caddy: ${DOMAIN:-hai-omni-pro-ii.l.warehack.ing}
|
caddy: ${PUBLIC_DOMAIN}
|
||||||
caddy.reverse_proxy: "{{upstreams 80}}"
|
caddy.reverse_proxy: "{{upstreams ${APP_PORT:-4321}}}"
|
||||||
|
# Streaming/HMR-friendly Caddy config (CLAUDE.md "HMR Behind Caddy"):
|
||||||
|
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
|
||||||
|
# Volume mounts only matter in dev mode; in prod they're harmless
|
||||||
|
# (the prod stage doesn't reference /app — it serves /srv from caddy).
|
||||||
|
volumes:
|
||||||
|
- ./src:/app/src
|
||||||
|
- ./public:/app/public
|
||||||
|
- ./astro.config.mjs:/app/astro.config.mjs
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
caddy:
|
caddy:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user