Running it on Kubernetes¶
The same image, the second runtime. Docker Compose trusts its files; Kubernetes refuses at a gate what a file forgot, and this deployment exists to make those refusals real on a laptop before any cloud is involved.
scripts/cluster-up.sh
export POSTGRES_PASSWORD=... MANIFEST_IDENTITY_APP_DB_PASSWORD=... MANIFEST_IDENTITY_ADMIN_USERNAME=... MANIFEST_IDENTITY_ADMIN_PASSWORD=...
scripts/deploy-app.sh
The first script fetches kind and kubectl from their canonical
releases, verifies their checksums, and brings up a cluster on a
digest-pinned node image with Calico installed from a vendored,
digest-verified manifest; the default network plugin is disabled
because it ignores network policies silently. The second builds the
image, loads it into the cluster so no registry is ever consulted,
creates the secrets from your environment, refusing to run while one
is missing, and waits for readiness. The page is at
http://127.0.0.1:8000, published on the loopback interface only,
matching the compose posture. scripts/cluster-down.sh removes it
all.
What the cluster enforces that compose cannot, each verifiable:
- Network policy, default deny. Everything is refused except the three flows the system has: operator to application, application to database, and name resolution. Calico enforces; the probes prove:
.tools/kubectl -n manifest-identity exec deploy/app -- python -c "import socket; socket.create_connection(('db', 5432), timeout=5); print('allowed')"
.tools/kubectl -n manifest-identity run probe --image=postgres@sha256:4ef4dbc939d61acea57712655ddb4b4ab27419c913f94cca0cd57cb3ea3c2280 --restart=Never --command -- sleep 300
.tools/kubectl -n manifest-identity exec probe -- timeout 4 bash -c "echo > /dev/tcp/db/5432" # hangs and dies: denied
- Admission, two layers. The namespace enforces the restricted Pod Security Standard, and a validating admission policy refuses any image not pinned by digest, with the locally built application image as the one recorded exception (D-047). A privileged pod and an unpinned image are both refused at creation, wording and all:
.tools/kubectl -n manifest-identity run unpinned --image=nginx:latest --restart=Never # refused by the image-pinning admission policy (D-047)
- No orchestrator identity to steal. The workloads run under service accounts with no permissions and no mounted token, because the application needs nothing from the Kubernetes API:
.tools/kubectl -n manifest-identity exec deploy/app -- ls /var/run/secrets/kubernetes.io # No such file or directory
The manifests are schema-validated and posture-linted in the pipeline by kubeconform and kube-linter, both fetched checksum-verified like every other tool.