deploy: nginx and git-sync, no image built
Serves loom.hypertheory-labs.dev from the site branch, which holds dist/ and nothing else. Two public images; git-sync polls the branch and nginx serves the volume, so publishing a new version is a push and there is no rollout. This sidesteps the cluster's open gap on publishing container images rather than closing it. The first workload that genuinely needs an image we built still hits it, and nothing here is evidence the gap is smaller than it looked. The HTTPRoute carries no hostname of its own, per public-access.md: it pins to the loom listener by sectionName and inherits. A route with its own hostnames stops inheriting and stops being portable between staging and production. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
|||||||
|
# Deploying this site
|
||||||
|
|
||||||
|
**`loom.hypertheory-labs.dev`, on the home cluster.**
|
||||||
|
|
||||||
|
**No container image is built.** *Two public images — `nginx` and `git-sync` —
|
||||||
|
and the site arrives over git.* **`git-sync` follows the `site` branch of this
|
||||||
|
repository**, *which holds `dist/` and nothing else*, **and `nginx` serves what
|
||||||
|
lands in the shared volume.**
|
||||||
|
|
||||||
|
> **This deliberately sidesteps the cluster's open gap on
|
||||||
|
> [publishing container images](https://git.hypertheory-labs.dev/jeffry/homelab-cluster/src/branch/main/.loom/gaps/publishing-container-images.md).**
|
||||||
|
> *Sidestepping is not closing:* **the first workload here that genuinely needs
|
||||||
|
> an image we built will still hit it**, *and this repository is not evidence
|
||||||
|
> that the gap is smaller than it looked.*
|
||||||
|
|
||||||
|
## Publishing a new version
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
npm run publish-site # commits dist/ to the `site` branch and pushes
|
||||||
|
```
|
||||||
|
|
||||||
|
*`git-sync` polls, and `nginx` serves the new files without a restart —* **there
|
||||||
|
is no rollout, because there is no image.**
|
||||||
|
|
||||||
|
## What is here
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| `namespace.yaml` | the `loom` namespace |
|
||||||
|
| `deployment.yaml` | `nginx` + `git-sync`, sharing an `emptyDir` |
|
||||||
|
| `service.yaml` | ClusterIP on 80 |
|
||||||
|
| `httproute.yaml` | pins to the `loom` listener and **carries no hostname of its own** |
|
||||||
|
|
||||||
|
**The `Certificate` and the Gateway listener are not here** — *they are cluster
|
||||||
|
infrastructure and live in `homelab-impl`, beside the other four.*
|
||||||
|
|
||||||
|
## Why the route carries no hostname
|
||||||
|
|
||||||
|
*Because `public-access.md` says so:* **a route that carries its own `hostnames:`
|
||||||
|
stops inheriting from the listener and stops being portable between staging and
|
||||||
|
production.** *It pins by `sectionName` and takes the hostname it is given.*
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# The site is served from a git branch, not from an image we built.
|
||||||
|
#
|
||||||
|
# git-sync clones the `site` branch of loom/docs into a shared emptyDir and
|
||||||
|
# polls it; nginx serves whatever is there. Both images are public, which is
|
||||||
|
# what lets this deploy while the cluster's container-registry gap is open.
|
||||||
|
#
|
||||||
|
# Anonymous clone: loom/docs is a public repository. Nothing here holds a
|
||||||
|
# credential, and a private repository would need one — which is the same
|
||||||
|
# unanswered question the registry gap names.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: docs
|
||||||
|
namespace: loom
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels: { app: docs }
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels: { app: docs }
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 65533
|
||||||
|
volumes:
|
||||||
|
- name: site
|
||||||
|
emptyDir: {}
|
||||||
|
- name: nginx-conf
|
||||||
|
configMap: { name: docs-nginx }
|
||||||
|
initContainers:
|
||||||
|
# --one-time, so the pod is not Ready until the site is actually on
|
||||||
|
# disk. Without this nginx serves 404s for the first few seconds after
|
||||||
|
# every reschedule.
|
||||||
|
- name: git-sync-init
|
||||||
|
image: registry.k8s.io/git-sync/git-sync:v4.4.0
|
||||||
|
args:
|
||||||
|
- --repo=https://git.hypertheory-labs.dev/loom/docs.git
|
||||||
|
- --ref=site
|
||||||
|
- --root=/site
|
||||||
|
- --link=current
|
||||||
|
- --one-time
|
||||||
|
volumeMounts:
|
||||||
|
- { name: site, mountPath: /site }
|
||||||
|
containers:
|
||||||
|
- name: git-sync
|
||||||
|
image: registry.k8s.io/git-sync/git-sync:v4.4.0
|
||||||
|
args:
|
||||||
|
- --repo=https://git.hypertheory-labs.dev/loom/docs.git
|
||||||
|
- --ref=site
|
||||||
|
- --root=/site
|
||||||
|
- --link=current
|
||||||
|
- --period=60s
|
||||||
|
volumeMounts:
|
||||||
|
- { name: site, mountPath: /site }
|
||||||
|
- name: nginx
|
||||||
|
image: nginx:1.29-alpine
|
||||||
|
ports:
|
||||||
|
- { containerPort: 8080, name: http }
|
||||||
|
volumeMounts:
|
||||||
|
- { name: site, mountPath: /site, readOnly: true }
|
||||||
|
- { name: nginx-conf, mountPath: /etc/nginx/conf.d }
|
||||||
|
readinessProbe:
|
||||||
|
httpGet: { path: /, port: http }
|
||||||
|
initialDelaySeconds: 2
|
||||||
|
resources:
|
||||||
|
requests: { cpu: 10m, memory: 32Mi }
|
||||||
|
limits: { memory: 128Mi }
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Carries no hostname of its own, on purpose: it pins to the `loom` listener by
|
||||||
|
# sectionName and inherits the hostname from it. A route with its own
|
||||||
|
# `hostnames:` stops inheriting and stops being portable between environments.
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: docs
|
||||||
|
namespace: loom
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: traefik-gateway
|
||||||
|
namespace: traefik
|
||||||
|
sectionName: loom
|
||||||
|
rules:
|
||||||
|
- backendRefs:
|
||||||
|
- name: docs
|
||||||
|
port: 80
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: loom
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: docs-nginx
|
||||||
|
namespace: loom
|
||||||
|
data:
|
||||||
|
default.conf: |
|
||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
# git-sync keeps `current` pointed at the checked-out worktree, so this
|
||||||
|
# path follows a new commit without nginx restarting.
|
||||||
|
root /site/current/dist;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# Astro emits directory-style URLs: /bedrock/starting/ -> .../index.html
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ $uri/index.html =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Hashed assets are immutable; pages are not, and a cached page is how a
|
||||||
|
# corrected document keeps reading as true.
|
||||||
|
location /_astro/ {
|
||||||
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
|
error_page 404 /404.html;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: docs
|
||||||
|
namespace: loom
|
||||||
|
spec:
|
||||||
|
selector: { app: docs }
|
||||||
|
ports:
|
||||||
|
- { name: http, port: 80, targetPort: http }
|
||||||
+2
-1
@@ -8,7 +8,8 @@
|
|||||||
"dev": "npm run generate && astro dev",
|
"dev": "npm run generate && astro dev",
|
||||||
"build": "npm run generate && astro build",
|
"build": "npm run generate && astro build",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"check": "node scripts/generate.mjs --strict"
|
"check": "node scripts/generate.mjs --strict",
|
||||||
|
"publish-site": "bash scripts/publish-site.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/starlight": "^0.36.0",
|
"@astrojs/starlight": "^0.36.0",
|
||||||
|
|||||||
Executable
+21
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Put dist/ on the `site` branch, which holds build output and nothing else.
|
||||||
|
# git-sync follows that branch; there is no image and no rollout.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
[ -d dist ] || { echo "no dist/ — run npm run build first" >&2; exit 1; }
|
||||||
|
SHA=$(git rev-parse --short HEAD)
|
||||||
|
WORK=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$WORK"' EXIT
|
||||||
|
git worktree add -q --detach "$WORK"
|
||||||
|
cd "$WORK"
|
||||||
|
git checkout -q --orphan site
|
||||||
|
git rm -rq --cached . 2>/dev/null || true
|
||||||
|
find . -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} +
|
||||||
|
mkdir dist && cp -R "$OLDPWD/dist/." dist/
|
||||||
|
git add -A
|
||||||
|
git commit -q -m "site: built from $SHA"
|
||||||
|
git push -qf origin site
|
||||||
|
cd "$OLDPWD"
|
||||||
|
git worktree remove --force "$WORK"
|
||||||
|
echo "published site branch from $SHA"
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
title: loom
|
title: loom
|
||||||
description: Conventions for holding what a repository knows, so somebody arriving can find it.
|
description: Conventions for holding what a repository knows, so somebody arriving can find it.
|
||||||
template: splash
|
template: splash
|
||||||
|
head:
|
||||||
|
- tag: title
|
||||||
|
content: loom — conventions for what a repository knows
|
||||||
hero:
|
hero:
|
||||||
tagline: Conventions for holding what a repository knows — so that whoever arrives next, person or agent, can find it.
|
tagline: Conventions for holding what a repository knows — so that whoever arrives next, person or agent, can find it.
|
||||||
actions:
|
actions:
|
||||||
|
|||||||
Reference in New Issue
Block a user