Add gw-03 deploy artifacts (Docker + Traefik + nginx)

Stages the production deploy bundle next to the existing ClawBooks stack
on zeroclaw-gw-03. Reuses ClawBooks' Traefik v3.6 via shared
clawbooks-net Docker network, no separate proxy.

- deploy/Dockerfile.web: multi-stage Node 22 build -> nginx 1.27 alpine
- deploy/nginx.conf: SPA history fallback, asset caching, /healthz probe
- deploy/docker-compose.yml: apess-web service with Traefik labels for
  apess.redclaw.dev on the websecure entrypoint
- deploy/traefik/apess.yml: TLS dynamic config pointing at the
  *.redclaw.dev Cloudflare Origin Certificate (to be installed at
  /etc/ssl/redclaw/apess.{pem,key} on gw-03)
- deploy/README.md: prereqs (CF DNS + Origin Cert), deploy commands,
  verification, rollback

No infra changes applied yet -- this is the artifact bundle. Deploy
pending three out-of-repo prereqs: CF DNS records, CF Origin Cert
issuance, and rsync+up on gw-03.
This commit is contained in:
Omar Sobh
2026-06-09 18:32:32 -05:00
parent 3d433b9b1d
commit 394e226975
5 changed files with 129 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
FROM node:22-alpine AS build
WORKDIR /app
RUN corepack enable
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM nginx:1.27-alpine
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://127.0.0.1/ > /dev/null || exit 1
+56
View File
@@ -0,0 +1,56 @@
# APESS 2026 — gw-03 deploy
Drops `apess-web` next to the existing ClawBooks stack on `zeroclaw-gw-03` (Architect). Reuses ClawBooks' Traefik v3.6 instance via the shared `clawbooks-net` Docker network.
## Prerequisites (one-time, on gw-03)
1. **Cloudflare DNS** — add `A` records (proxied):
- `apess.redclaw.dev` → `37.27.250.130`
- `api.apess.redclaw.dev` → `37.27.250.130` (backend, future)
2. **Cloudflare Origin Certificate** — issue from CF dashboard:
- Hostnames: `*.redclaw.dev`, `redclaw.dev` (covers all subdomains)
- Validity: 15 years
- Save:
- `/etc/ssl/redclaw/apess.pem` (root:root 644)
- `/etc/ssl/redclaw/apess.key` (root:root 600)
3. **Traefik dynamic config** — copy `traefik/apess.yml` into the live config dir:
```bash
sudo cp deploy/traefik/apess.yml /home/redclaw/projects/clawbooks/traefik/dynamic/
```
Traefik hot-reloads the dynamic dir; no restart needed.
## Deploy
```bash
# On macbook
ssh [email protected] "mkdir -p ~/projects/apress"
rsync -avz --exclude node_modules --exclude dist --exclude .git \
~/projects/apress/ [email protected]:~/projects/apress/
# On gw-03
ssh [email protected]
cd ~/projects/apress
docker compose -f deploy/docker-compose.yml up -d --build
docker logs -f apess-web # verify nginx is up
```
## Verify
```bash
curl -fsS https://apess.redclaw.dev/ | head -5
curl -fsS https://apess.redclaw.dev/healthz # → ok
```
## Rollback
```bash
ssh [email protected] "cd ~/projects/apress && docker compose -f deploy/docker-compose.yml down"
# Remove DNS records if needed
```
## Why this lives on gw-03
- 104 GB free disk, 16 GB Intel RAM, idle CPU
- Traefik v3.6 already terminating TLS for `clawbooks.app` & `finclaw.net`
- Shared `clawbooks-net` Docker network — Traefik auto-discovers via labels
- Single-node simplicity for a 5.5-hour workshop event
+24
View File
@@ -0,0 +1,24 @@
name: apess
services:
apess-web:
build:
context: ..
dockerfile: deploy/Dockerfile.web
image: apess-web:latest
container_name: apess-web
restart: unless-stopped
networks:
- clawbooks-net
labels:
- traefik.enable=true
- traefik.docker.network=clawbooks-net
- traefik.http.routers.apess-web.rule=Host(`apess.redclaw.dev`)
- traefik.http.routers.apess-web.entrypoints=websecure
- traefik.http.routers.apess-web.tls=true
- traefik.http.routers.apess-web.service=apess-web
- traefik.http.services.apess-web.loadbalancer.server.port=80
networks:
clawbooks-net:
external: true
+30
View File
@@ -0,0 +1,30 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
gzip_vary on;
gzip_min_length 256;
location / {
try_files $uri $uri/ /index.html;
}
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
location = /healthz {
access_log off;
return 200 "ok\n";
}
}
+6
View File
@@ -0,0 +1,6 @@
tls:
certificates:
- certFile: /etc/ssl/redclaw/apess.pem
keyFile: /etc/ssl/redclaw/apess.key
stores:
- default