diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts
index 7d2ce701..5d8f21c3 100644
--- a/docs/.vitepress/config.mts
+++ b/docs/.vitepress/config.mts
@@ -89,6 +89,7 @@ export default extendConfig(
ignoreFiles: [
"self-hosting/methods/install-methods-commercial/docker-compose.md",
"self-hosting/methods/install-methods-commercial/kubernetes.md",
+ "self-hosting/methods/install-methods-commercial/fips-deployment.md",
],
}),
],
@@ -349,6 +350,7 @@ export default extendConfig(
collapsed: true,
items: [{ text: "High availability", link: "/self-hosting/govern/high-availability" }],
},
+ { text: "FIPS deployment", link: "/self-hosting/methods/fips-deployment" },
{ text: "Podman Quadlets", link: "/self-hosting/methods/podman-quadlets" },
{
text: "Airgapped Edition",
diff --git a/docs/self-hosting/methods/fips-deployment.md b/docs/self-hosting/methods/fips-deployment.md
new file mode 100644
index 00000000..f374714d
--- /dev/null
+++ b/docs/self-hosting/methods/fips-deployment.md
@@ -0,0 +1,212 @@
+---
+title: FIPS deployment
+description: Deploy the FIPS variant of Plane Enterprise on a FIPS-enforcing host, including prerequisites, image list, and verification.
+keywords: plane fips, fips 140-3 deployment, plane commercial fips, govcloud plane, federal self-hosting, fips enabled containers
+head:
+ - - meta
+ - name: robots
+ content: noindex, nofollow
+---
+
+# FIPS deployment
+
+Plane publishes a FIPS variant of every application image alongside the standard set.
+These images are built on Red Hat UBI 10, apply the system-wide FIPS cryptographic policy, and run
+their cryptography against FIPS-validated modules (Red Hat's OpenSSL FIPS provider for the Python
+and static services; the Go FIPS 140-3 module for the Go services). They are intended for
+deployments that must meet FIPS 140-3 expectations, such as US Federal or GovCloud environments.
+
+::: warning **The single most important prerequisite**
+FIPS mode is a property of the **host**, not of the image. A FIPS image on a non-FIPS host starts cleanly and looks identical from the inside while providing none of the guarantees. Read [Host prerequisite](#host-prerequisite) first.
+:::
+
+## Images
+
+The FIPS images use the same names as the standard `-commercial` images with a `-fips` suffix, in the `makeplane` Docker Hub organization:
+
+| Service | Image |
+| ------------- | --------------------------------------- |
+| Backend / API | `makeplane/backend-commercial-fips` |
+| Web | `makeplane/web-commercial-fips` |
+| Admin | `makeplane/admin-commercial-fips` |
+| Space | `makeplane/space-commercial-fips` |
+| Live | `makeplane/live-commercial-fips` |
+| Silo | `makeplane/silo-commercial-fips` |
+| Monitor | `makeplane/monitor-commercial-fips` |
+| Email | `makeplane/email-commercial-fips` |
+| Plane AI | `makeplane/plane-pi-commercial-fips` |
+| Proxy | `makeplane/proxy-commercial-fips` |
+| Flux | `makeplane/flux-commercial-fips` |
+| Node runner | `makeplane/node-runner-commercial-fips` |
+
+Pin a specific release tag for any accredited deployment rather than tracking `latest` - a known,
+fixed image version is part of the audit trail.
+
+:::info
+There is no FIPS All-in-One (AIO) image. The AIO image is built on an Alpine base, which has no FIPS-validated cryptography, so a FIPS deployment uses the multi-container stack, not the AIO image.
+:::
+
+## Host prerequisite
+
+The host kernel must be booted in FIPS mode. The container inherits this through
+`/proc/sys/crypto/fips_enabled` and **cannot set it itself**. Verify before deploying:
+
+```bash
+cat /proc/sys/crypto/fips_enabled # must print 1
+```
+
+How you put the host into FIPS mode depends on the distribution and version:
+
+**Amazon Linux 2023, RHEL 8/9 (and Rocky, Alma)** - enable in place, then reboot:
+
+```bash
+sudo dnf install -y crypto-policies-scripts
+sudo fips-mode-setup --enable
+sudo reboot
+```
+
+**RHEL 10** - `fips-mode-setup` has been removed and post-install switching is not supported: enable FIPS **at install time** with `fips=1` on the kernel command line.
+
+**Other** - boot a vendor FIPS image (a RHEL FIPS AMI, Ubuntu Pro FIPS), or install OpenShift with FIPS enabled.
+
+As a safeguard, run the FIPS images with `PLANE_REQUIRE_FIPS=1`: the containers then **refuse to
+start** if the host is not in FIPS mode. Without it, a FIPS image on a non-FIPS host logs a startup
+warning but runs.
+
+## Deploy on Kubernetes
+
+Use the same `plane-enterprise` Helm chart as a [standard Kubernetes install](/self-hosting/methods/kubernetes) -
+FIPS is a values overlay, not a different chart. Three things change:
+
+1. **Nodes** - provision a node pool whose machine image boots in FIPS mode (see
+ [Host prerequisite](#host-prerequisite)). Label it (e.g. `fips: enabled`) and taint it (e.g.
+ `fips=true:NoSchedule`) so only FIPS workloads land there.
+2. **Images** - override every service image to its `-fips` variant.
+3. **Scheduling** - every service must carry the matching `nodeSelector` and `toleration`. A pod
+ that misses them schedules onto a stock node and **silently loses FIPS**.
+
+```yaml
+# values-fips.yaml
+planeVersion:
+
+# Non-root with group 0, matching the FIPS images' group-0-writable directories
+# (see the non-root section below).
+securityContext:
+ enabled: true
+ podSecurityContext:
+ runAsGroup: 0
+ fsGroup: 0
+
+_fips_sched: &fips
+ nodeSelector:
+ fips: enabled
+ tolerations:
+ - key: fips
+ operator: Equal
+ value: "true"
+ effect: NoSchedule
+
+services:
+ api:
+ image: makeplane/backend-commercial-fips
+ <<: *fips
+ web:
+ image: makeplane/web-commercial-fips
+ <<: *fips
+ space:
+ image: makeplane/space-commercial-fips
+ <<: *fips
+ admin:
+ image: makeplane/admin-commercial-fips
+ <<: *fips
+ live:
+ image: makeplane/live-commercial-fips
+ <<: *fips
+ silo:
+ image: makeplane/silo-commercial-fips
+ <<: *fips
+ monitor:
+ image: makeplane/monitor-commercial-fips
+ <<: *fips
+ worker:
+ <<: *fips
+ beatworker:
+ <<: *fips
+ # Every additional service you enable needs the same <<: *fips block -
+ # e.g. Plane AI also takes image: makeplane/plane-pi-commercial-fips, and
+ # its pi_worker / pi_beat_worker need the block too.
+ postgres:
+ <<: *fips
+ redis:
+ <<: *fips
+ rabbitmq:
+ <<: *fips
+ minio:
+ <<: *fips
+```
+
+```bash
+helm repo add plane https://helm.plane.so/
+helm upgrade --install plane-app plane/plane-enterprise \
+ --namespace plane --create-namespace \
+ -f values-fips.yaml
+```
+
+On OpenShift, drop the `securityContext` override and see
+[Running under a non-root or arbitrary UID](#running-under-a-non-root-or-arbitrary-uid-openshift).
+
+## Verify
+
+Each container logs its posture on startup:
+
+```text
+plane: FIPS mode ACTIVE (host kernel reports fips_enabled=1)
+```
+
+The Go services (monitor, email, proxy) log a corresponding line, for example
+`Go FIPS 140-3 module ACTIVE`.
+
+To check a running container directly:
+
+```bash
+# Kernel flag inherited from the host - must print 1
+docker exec plane-api cat /proc/sys/crypto/fips_enabled
+
+# The FIPS-validated OpenSSL provider must be loaded and "active"
+docker exec plane-api openssl list -providers
+
+# A non-approved digest must be refused - this must FAIL
+docker exec plane-api sh -c 'echo x | openssl md5'
+
+# Node services must report FIPS - must print 1
+docker exec plane-live node -p "require('crypto').getFips()"
+```
+
+On Kubernetes, run the same checks with `kubectl exec` against any application pod, e.g.
+`kubectl -n plane exec deploy/plane-app-api-wl -- cat /proc/sys/crypto/fips_enabled`.
+
+## Configuration defaults specific to FIPS images
+
+The FIPS images default to a stricter security posture than the standard images. A fresh FIPS
+install needs none of these changed; they matter mainly when moving an existing standard
+deployment onto the FIPS images.
+
+| Setting | FIPS default | Standard default | Notes |
+| ---------------------------------- | ------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- |
+| `LDAP_TLS_REQUIRE_CERT` | `demand` | `never` | Validates the LDAP server's TLS certificate. Set to `never` to restore the previous behavior. |
+| `SAML_REJECT_DEPRECATED_ALGORITHM` | on | off | Rejects assertions signed with RSA-SHA1. The IdP must sign with SHA-256. |
+| `SECRET_ENCRYPTION_V2` | on | off | Writes at-rest secrets as AES-256-GCM instead of the legacy format. Both formats are always readable. |
+| `USAGE_ID_DIGEST` | `sha256` (required) | `md5` | Digest for Plane AI usage-ledger keys. `md5` is incompatible with a FIPS-mode Postgres, so `sha256` is required. |
+
+## Running under a non-root or arbitrary UID (OpenShift)
+
+The FIPS application images run non-root, and FIPS mode itself requires no privilege.
+
+**Plain Kubernetes** - set `runAsUser: 1000` (the images' built-in user). For any other UID, add
+`runAsGroup: 0` and `fsGroup: 0`.
+
+**OpenShift (`restricted-v2`)** - works out of the box. Don't set `runAsUser`/`runAsGroup`/`fsGroup`
+yourself; the SCC assigns an arbitrary UID in group `0`, and the images' writable directories are
+group-`0` writable by design. One exception: the bundled proxy binds ports 80/443, which
+`restricted-v2` forbids - front it with an OpenShift Route instead. Ingress-based deployments don't
+use the bundled proxy.