Deploy Plane project management to k8s with Cloudflare Access gate

Adds all k8s manifests (namespace, postgres, redis, api, worker, beat,
web, ingress, externalsecret, configmap, kustomization), Argo CD Application,
and Cloudflare Zero Trust access policy for plane.neuralplatform.ai.
This commit is contained in:
Will Anderson
2026-04-24 14:21:02 -05:00
parent e901b9a541
commit 2d98df75ad
13 changed files with 438 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: plane
namespace: argocd
spec:
project: default
source:
repoURL: https://git.neuralplatform.ai/will/infrastructure.git
targetRevision: main
path: servers/legion/k8s/plane
destination:
server: https://kubernetes.default.svc
namespace: plane
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
+57
View File
@@ -0,0 +1,57 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: plane-api
namespace: plane
spec:
replicas: 1
selector:
matchLabels:
app: plane-api
template:
metadata:
labels:
app: plane-api
spec:
containers:
- name: plane-api
image: makeplane/plane-backend:stable
command: ["./bin/takeoff"]
ports:
- containerPort: 8000
envFrom:
- configMapRef:
name: plane-config
env:
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: plane-secrets
key: secret_key
readinessProbe:
httpGet:
path: /
port: 8000
initialDelaySeconds: 20
periodSeconds: 10
failureThreshold: 6
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
---
apiVersion: v1
kind: Service
metadata:
name: plane-api
namespace: plane
spec:
selector:
app: plane-api
ports:
- port: 8000
targetPort: 8000
+35
View File
@@ -0,0 +1,35 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: plane-beat
namespace: plane
spec:
replicas: 1
selector:
matchLabels:
app: plane-beat
template:
metadata:
labels:
app: plane-beat
spec:
containers:
- name: plane-beat
image: makeplane/plane-backend:stable
command: ["./bin/beat"]
envFrom:
- configMapRef:
name: plane-config
env:
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: plane-secrets
key: secret_key
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
+15
View File
@@ -0,0 +1,15 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: plane-config
namespace: plane
data:
DATABASE_URL: "postgresql://plane:plane@plane-postgres:5432/plane"
REDIS_URL: "redis://plane-redis:6379/"
WEB_URL: "https://plane.neuralplatform.ai"
CORS_ALLOWED_ORIGINS: "https://plane.neuralplatform.ai"
EMAIL_BACKEND: "django.core.mail.backends.console.EmailBackend"
DEFAULT_FROM_EMAIL: "noreply@neuralplatform.ai"
ENABLE_SIGNUP: "0"
DJANGO_SETTINGS_MODULE: "plane.settings.production"
GUNICORN_WORKERS: "2"
@@ -0,0 +1,18 @@
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: plane-secrets
namespace: plane
spec:
refreshInterval: 1h
secretStoreRef:
name: vault
kind: ClusterSecretStore
target:
name: plane-secrets
creationPolicy: Owner
data:
- secretKey: secret_key
remoteRef:
key: secret/data/plane/config
property: secret_key
+39
View File
@@ -0,0 +1,39 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: plane
namespace: plane
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- plane.neuralplatform.ai
secretName: plane-tls
rules:
- host: plane.neuralplatform.ai
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: plane-api
port:
number: 8000
- path: /auth
pathType: Prefix
backend:
service:
name: plane-api
port:
number: 8000
- path: /
pathType: Prefix
backend:
service:
name: plane-web
port:
number: 3000
@@ -0,0 +1,13 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- externalsecret.yaml
- configmap.yaml
- postgres.yaml
- redis.yaml
- api.yaml
- worker.yaml
- beat.yaml
- web.yaml
- ingress.yaml
+6
View File
@@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
name: plane
labels:
app.kubernetes.io/managed-by: argocd
+73
View File
@@ -0,0 +1,73 @@
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: plane-postgres-data
namespace: plane
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: plane-postgres
namespace: plane
spec:
replicas: 1
selector:
matchLabels:
app: plane-postgres
template:
metadata:
labels:
app: plane-postgres
spec:
containers:
- name: postgres
image: postgres:16-alpine
ports:
- containerPort: 5432
env:
- name: POSTGRES_USER
value: "plane"
- name: POSTGRES_PASSWORD
value: "plane"
- name: POSTGRES_DB
value: "plane"
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
readinessProbe:
exec:
command: ["pg_isready", "-U", "plane"]
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: data
persistentVolumeClaim:
claimName: plane-postgres-data
---
apiVersion: v1
kind: Service
metadata:
name: plane-postgres
namespace: plane
spec:
selector:
app: plane-postgres
ports:
- port: 5432
targetPort: 5432
+45
View File
@@ -0,0 +1,45 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: plane-redis
namespace: plane
spec:
replicas: 1
selector:
matchLabels:
app: plane-redis
template:
metadata:
labels:
app: plane-redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
readinessProbe:
exec:
command: ["redis-cli", "ping"]
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: plane-redis
namespace: plane
spec:
selector:
app: plane-redis
ports:
- port: 6379
targetPort: 6379
+50
View File
@@ -0,0 +1,50 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: plane-web
namespace: plane
spec:
replicas: 1
selector:
matchLabels:
app: plane-web
template:
metadata:
labels:
app: plane-web
spec:
containers:
- name: plane-web
image: makeplane/plane-frontend:stable
ports:
- containerPort: 3000
env:
- name: NEXT_PUBLIC_API_BASE_URL
value: "https://plane.neuralplatform.ai"
readinessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 20
periodSeconds: 10
failureThreshold: 6
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: plane-web
namespace: plane
spec:
selector:
app: plane-web
ports:
- port: 3000
targetPort: 3000
+35
View File
@@ -0,0 +1,35 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: plane-worker
namespace: plane
spec:
replicas: 1
selector:
matchLabels:
app: plane-worker
template:
metadata:
labels:
app: plane-worker
spec:
containers:
- name: plane-worker
image: makeplane/plane-backend:stable
command: ["./bin/worker"]
envFrom:
- configMapRef:
name: plane-config
env:
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: plane-secrets
key: secret_key
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
+32
View File
@@ -0,0 +1,32 @@
# Cloudflare Zero Trust Access — Plane project management
# Protects plane.neuralplatform.ai with Google OAuth.
locals {
plane_zone_id = local.zone_neuralplatform_ai
plane_allowed_emails = [
"andersonwilliam85@gmail.com",
"1timlingo@gmail.com",
]
}
resource "cloudflare_zero_trust_access_application" "plane" {
zone_id = local.plane_zone_id
name = "Plane"
domain = "plane.neuralplatform.ai"
type = "self_hosted"
session_duration = "24h"
allowed_idps = ["808f1913-5a8e-4a97-9e68-8ec58e362110"] # Google
auto_redirect_to_identity = true
}
resource "cloudflare_zero_trust_access_policy" "plane_allow" {
application_id = cloudflare_zero_trust_access_application.plane.id
account_id = var.cloudflare_account_id
name = "Allow Will and Tim"
precedence = 1
decision = "allow"
include {
email = local.plane_allowed_emails
}
}