PRISM

PRISM Infrastructure (prism-infra)

Terraform and Ansible for the whole estate: Proxmox VMs, the Cloudflare Zero Trust edge, self-hosted Git and CI, identity, storage and observability.

2026 – now
213commits
20Ansible roles
6Terraform workspaces
27 → 4CI org secrets after SOPS
Secrets target state: SOPS with three age recipients, targets hold no key, Ansible Vault and 27 CI org secrets retired
Secrets target state: SOPS with three age recipients, targets hold no key, Ansible Vault and 27 CI org secrets retired

PRISM runs on on-premise Proxmox hardware with no public inbound ports, so ingress, identity, CI, registry, wiki, object storage and observability all had to be built. This repo is the single source of truth for Dev, Test and Prod, and deliberately contains no application code.

Shape of the estate

Terraform (run as OpenTofu) with the Proxmox and Cloudflare v5 providers across six workspaces: per-environment Proxmox, shared Proxmox, per-environment Cloudflare, shared Cloudflare, private DNS, and a reusable VM module. State lives in self-hosted MinIO. Ansible has twenty roles covering Docker, cloudflared on Linux and Windows, Forgejo and its Actions runner, the two PRISM applications, Authentik, ClickHouse, MinIO, Outline, backups and a Prometheus, Loki, Promtail and Grafana observability stack. Four inventories with Vault-encrypted group variables.

The Cloudflare footprint: Zero Trust Tunnel (one per environment plus one shared), Access applications and policies, Gateway DNS override policies, proxied DNS records, WAF rate-limiting rulesets, Access bypass rules, WARP enrolment and AI Gateway.

Things worth reading in the code

The private DNS file is a forensic artefact. The original Terraform for it was never committed; only state and variables survived. The file was reconstructed from eleven live Gateway policies and verified with a zero-diff plan. Its header then explains the mechanism: the internal domain is delegated nowhere and is NXDOMAIN on every public resolver. Each name exists only as a Gateway DNS override answering with a LAN address, so it resolves only on WARP in Gateway mode. It also notes that resolving is half the job and routing comes from the WARP split tunnel, so a device that resolves the name but cannot reach the address has a routing problem, not a DNS problem.

Compensating controls, explained. The Access challenge must be bypassed on the registry's /v2/* path for the Docker bearer-token handshake, which exposes the token and catalog endpoints publicly. A WAF rate-limit ruleset caps per-IP rate, tuned to sit above a normal multi-layer image pull but below brute-force territory.

Adoption over recreation. The central ClickHouse VM was a proof of concept promoted into production infrastructure as code. Its Terraform parameters match the existing VM exactly so the tool adopts it with no recreate and no downtime.

Living with a self-hosted CI's bugs. A document catalogues three bookkeeping bugs in the Forgejo release in use, and the application repos carry explicit workarounds rather than abandoning the platform.

The OT host is the documented exception. The PLC collector runs only on edge hosts, so Dev and Test were stripped of the PLC stack and read live data from production. Its promote workflow deploys straight to the OT host and refuses unless CI passed on that exact commit.

Secrets programme

An in-flight migration from Ansible Vault plus 27 Forgejo organisation secrets to SOPS with age. Three recipients: the laptop, the CI runner, and an offline recovery key that never lives on a working disk. Targets hold no key at all; decryption happens at use time. Comes with a rotation-due checker and a secrets inventory. The diagram on this page is the target state.

Process

Terraform pull requests post a plan as a comment; apply runs only after merge and manual approval. Every Ansible role must be check-mode clean before merge. Docs are synced from the repos into the Outline wiki by a script that tracks content hashes.