Add Helm chart for Kubernetes deployment of Plane - #2
Open
crewletbot wants to merge 3 commits into
Open
Conversation
Upstream ships compose, swarm and AIO deployments plus a link to makeplane's chart on Artifact Hub, but nothing that fits how this fork is actually run: a shared RDS instance instead of a bundled Postgres, S3 with IRSA instead of MinIO, and ingress through a Cloudflare Tunnel instead of a load balancer. The chart deploys all six images this repository publishes to GHCR -- proxy, web, space, admin, live and backend (as api, worker, beat and a migration job) -- plus the Valkey and RabbitMQ they depend on. Because the images sit side by side under one namespace, image.repository is treated as a prefix that each component completes with its own suffix; a single repository/tag pair then configures all six, which is what lets a generic GitOps Application template drive a multi-image chart. Caddy keeps the path split from apps/proxy/Caddyfile.ce but drops the MinIO route and listens on 8080, so it needs no NET_BIND_SERVICE capability. The migration job runs as a pre-install/pre-upgrade hook, which Argo CD maps onto PreSync, so the schema is current before any new pod starts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY defaulted to the literal strings "access-key" and "secret-key". Anything reading them through django settings -- the workspace export task and the expired-export cleanup task both build their own boto3 client that way -- therefore handed S3 a placeholder key whenever the variables were unset, and got InvalidAccessKeyId back instead of falling through to boto3's credential chain. That is exactly how a deployment on an instance profile or an EKS service account role is configured: no keys in the environment at all. S3Storage already got this right by reading os.environ directly with no default, so uploads worked while exports did not. The placeholders only ever fitted the bundled MinIO, and both compose files set the pair explicitly, so nothing that relied on them loses it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The chart offered two ways to reach Redis and RabbitMQ: its own statefulsets, or a plaintext endpoint in values. A managed cache or broker fits neither -- its URL carries credentials, so it belongs in a secret, and requiring externalRedis.url in values failed the render for a deployment that supplies REDIS_URL through extraEnvFrom instead. Disabling a statefulset without naming a replacement in values now simply omits those keys from the ConfigMap, leaving the URL to arrive from the secret. Plane already prefers AMQP_URL over the assembled RABBITMQ_* parts, so the broker needs nothing further, and both TLS schemes these endpoints use are understood already: django-redis and redis-py branch on rediss://, ioredis turns TLS on from the scheme alone, and kombu speaks amqps://. The other two paths are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds a Helm chart at
deployments/helm/planefor deploying Plane (community edition) on Kubernetes, from the images this repository publishes to GHCR. It packages the Plane components (web, space, admin, live, api, worker, beat) behind the Caddy proxy, with optional Valkey and RabbitMQ statefulsets, into one configurable release.Upstream links to makeplane's chart on Artifact Hub. To be clear about why this one exists, since that chart is more configurable than it first appears: it does support an external Postgres (
postgres.local_setup: false), real S3 instead of MinIO (minio.local_setup: false), and no ingress (ingress.enabled: false). What it has no extension point for is a service account annotation —templates/service-account.yamlaccepts none, so there is no way to reach S3 through a workload identity role rather than static keys — or an externally managed image pull secret, which private registry images need. It also splits the image across seven separate values, where this chart takes one.Notable design points:
image.repositoryis treated as a prefix that each component completes with its own suffix (-backend,-frontend,-space,-admin,-live,-proxy). A singleimage.repository/image.tagpair therefore configures the whole release, which is what lets a generic GitOps Application template — one that only knows how to inject a single image reference — drive a multi-image chart. Settingimageon an individual component still pins that one elsewhere.apps/proxy/Caddyfile.ce(/spaces,/god-mode,/live,/api,/auth,/static, everything else to web), minus the MinIO route. It listens on 8080 rather than 80 so it needs noNET_BIND_SERVICEcapability. TLS terminates upstream; Django readsX-Forwarded-Proto, which Caddy only forwards from a peer inproxy.trustedProxies.manage.py migrateruns as apre-install,pre-upgradeHelm hook, which Argo CD maps onto its PreSync phase, so the schema is current before any new api, worker or beat pod starts.REDIS_URLandAMQP_URLarriving throughextraEnvFrom. That last path is what a managed cache or broker needs, since its URL carries credentials and belongs in a secret. Both TLS schemes involved are already handled: django-redis and redis-py branch onrediss://, ioredis enables TLS from the scheme alone, and kombu speaksamqps://.webandadmin(nginx hands its workers to thenginxuser) keepSETUID/SETGID;redisandrabbitmq(entrypoints chown the data dir andgosuinto the service user) additionally keepCHOWN,DAC_OVERRIDEandFOWNER. DroppingALLon those four stops them booting, so they are deliberately not uniform.secrets.create: true, guarded so a missing required value fails the render) or consumed from an external store viaextraEnvFrom.Also included is a one-line settings fix that the chart depends on.
AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEYdefaulted to the literal stringsaccess-keyandsecret-key. The workspace export task and the expired-export cleanup task both build their own boto3 client from those Django settings, so with the variables unset they handed S3 a placeholder key and gotInvalidAccessKeyIdinstead of falling through to boto3's credential chain — which is exactly how a deployment on an instance profile or a service account role is configured.S3Storagealready reados.environdirectly with no default, so uploads worked while exports did not. The placeholders only ever fitted the bundled MinIO, and both compose files set the pair explicitly, so nothing that relied on them loses it.Type of Change
Screenshots and Media (if applicable)
n/a — no user-facing UI change.
Test Scenarios
Chart:
helm lint— clean.helm templatewith chart defaults, and again with a GitOps-style values overlay. Rendered manifests were parsed and asserted on: the six image references resolve per component, the service account annotation lands, no static AWS credentials are emitted (so boto3 falls through to the role), and the ConfigMap/secret wiring is correct.helm templatewithsecrets.create=true, including the guards that fail the render when a required secret value is missing.Settings change — imported
plane.settings.commonunder three environments and asserted on the result:AWS_ACCESS_KEY_IDNoneNoneaccess-key/secret-keysetAnd the consequence, against boto3 itself: a client built with
aws_access_key_id=Noneresolves credentials from the default chain, whereas one built with the placeholder strings resolves toaccess-key— which is the bug, the placeholder shadowing the role.Note for reviewers: no workflow in this repository runs the Django/pytest suite —
Lint APIisruff checkonly — so the checks on this PR do not exerciseapps/apibehaviour. The assertions above were run locally in place of that; the full suite (docker-compose-test.yml) needs Docker, which was unavailable in the environment this was authored in.References