From 2d98df75ada8f103a3a0c35c91c699810fb7adc9 Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Fri, 24 Apr 2026 14:21:02 -0500 Subject: [PATCH] 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. --- servers/legion/apps/plane.yaml | 20 ++++++ servers/legion/k8s/plane/api.yaml | 57 +++++++++++++++ servers/legion/k8s/plane/beat.yaml | 35 ++++++++++ servers/legion/k8s/plane/configmap.yaml | 15 ++++ servers/legion/k8s/plane/externalsecret.yaml | 18 +++++ servers/legion/k8s/plane/ingress.yaml | 39 +++++++++++ servers/legion/k8s/plane/kustomization.yaml | 13 ++++ servers/legion/k8s/plane/namespace.yaml | 6 ++ servers/legion/k8s/plane/postgres.yaml | 73 ++++++++++++++++++++ servers/legion/k8s/plane/redis.yaml | 45 ++++++++++++ servers/legion/k8s/plane/web.yaml | 50 ++++++++++++++ servers/legion/k8s/plane/worker.yaml | 35 ++++++++++ servers/legion/plane-access.tf | 32 +++++++++ 13 files changed, 438 insertions(+) create mode 100644 servers/legion/apps/plane.yaml create mode 100644 servers/legion/k8s/plane/api.yaml create mode 100644 servers/legion/k8s/plane/beat.yaml create mode 100644 servers/legion/k8s/plane/configmap.yaml create mode 100644 servers/legion/k8s/plane/externalsecret.yaml create mode 100644 servers/legion/k8s/plane/ingress.yaml create mode 100644 servers/legion/k8s/plane/kustomization.yaml create mode 100644 servers/legion/k8s/plane/namespace.yaml create mode 100644 servers/legion/k8s/plane/postgres.yaml create mode 100644 servers/legion/k8s/plane/redis.yaml create mode 100644 servers/legion/k8s/plane/web.yaml create mode 100644 servers/legion/k8s/plane/worker.yaml create mode 100644 servers/legion/plane-access.tf diff --git a/servers/legion/apps/plane.yaml b/servers/legion/apps/plane.yaml new file mode 100644 index 0000000..4d85305 --- /dev/null +++ b/servers/legion/apps/plane.yaml @@ -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 diff --git a/servers/legion/k8s/plane/api.yaml b/servers/legion/k8s/plane/api.yaml new file mode 100644 index 0000000..a25c67d --- /dev/null +++ b/servers/legion/k8s/plane/api.yaml @@ -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 diff --git a/servers/legion/k8s/plane/beat.yaml b/servers/legion/k8s/plane/beat.yaml new file mode 100644 index 0000000..066de27 --- /dev/null +++ b/servers/legion/k8s/plane/beat.yaml @@ -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 diff --git a/servers/legion/k8s/plane/configmap.yaml b/servers/legion/k8s/plane/configmap.yaml new file mode 100644 index 0000000..24204c2 --- /dev/null +++ b/servers/legion/k8s/plane/configmap.yaml @@ -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" diff --git a/servers/legion/k8s/plane/externalsecret.yaml b/servers/legion/k8s/plane/externalsecret.yaml new file mode 100644 index 0000000..368f604 --- /dev/null +++ b/servers/legion/k8s/plane/externalsecret.yaml @@ -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 diff --git a/servers/legion/k8s/plane/ingress.yaml b/servers/legion/k8s/plane/ingress.yaml new file mode 100644 index 0000000..9f6fc29 --- /dev/null +++ b/servers/legion/k8s/plane/ingress.yaml @@ -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 diff --git a/servers/legion/k8s/plane/kustomization.yaml b/servers/legion/k8s/plane/kustomization.yaml new file mode 100644 index 0000000..b4882c5 --- /dev/null +++ b/servers/legion/k8s/plane/kustomization.yaml @@ -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 diff --git a/servers/legion/k8s/plane/namespace.yaml b/servers/legion/k8s/plane/namespace.yaml new file mode 100644 index 0000000..0a68e7e --- /dev/null +++ b/servers/legion/k8s/plane/namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: plane + labels: + app.kubernetes.io/managed-by: argocd diff --git a/servers/legion/k8s/plane/postgres.yaml b/servers/legion/k8s/plane/postgres.yaml new file mode 100644 index 0000000..7cf2434 --- /dev/null +++ b/servers/legion/k8s/plane/postgres.yaml @@ -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 diff --git a/servers/legion/k8s/plane/redis.yaml b/servers/legion/k8s/plane/redis.yaml new file mode 100644 index 0000000..e88e0d4 --- /dev/null +++ b/servers/legion/k8s/plane/redis.yaml @@ -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 diff --git a/servers/legion/k8s/plane/web.yaml b/servers/legion/k8s/plane/web.yaml new file mode 100644 index 0000000..bd16975 --- /dev/null +++ b/servers/legion/k8s/plane/web.yaml @@ -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 diff --git a/servers/legion/k8s/plane/worker.yaml b/servers/legion/k8s/plane/worker.yaml new file mode 100644 index 0000000..39c96ca --- /dev/null +++ b/servers/legion/k8s/plane/worker.yaml @@ -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 diff --git a/servers/legion/plane-access.tf b/servers/legion/plane-access.tf new file mode 100644 index 0000000..0b892ed --- /dev/null +++ b/servers/legion/plane-access.tf @@ -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 + } +}