Deploy Plane (open-source project management) on AWS. This repository provides:
- Terraform — Provisions the AWS infrastructure (VPC, EKS, Redis, Amazon MQ RabbitMQ, OpenSearch, S3, RDS PostgreSQL)
- Kustomize — Deploys the Plane application on the EKS cluster (coming soon)
- Terraform >= 1.0 — Download
- AWS CLI configured with credentials — Install
- kubectl for cluster access — Install
- Kustomize (for application deployment) — Install (or use
kubectl kustomizebuilt-in)
From the repository root:
terraform init
terraform plan
terraform applyThe root main.tf deploys plane_infra with minimal required inputs. Defaults are used for EKS, cache, OpenSearch, object store, and database.
module "plane_infra" {
source = "git::https://github.com/makeplane/commercial-deployments.git//terraform?ref=main"
# source = "./terraform" # for local development
cluster_name = "plane-eks-cluster"
region = "us-west-2" # required
vpc_cidr = "10.0.0.0/16"
single_nat_gateway = true
enable_aws_lb_controller = true # to use aws ALB
cluster_version = "1.34"
tags = {
Environment = "plane"
}
}Plane includes a built-in email service for receiving inbound email. To expose it externally via a dedicated AWS Network Load Balancer, set enable_email_nlb = true.
Important: Deploy in two stages. The NLB targets EKS worker nodes via fixed NodePorts, so the EKS cluster and the email service must exist before the NLB is created.
Stage 1 — Deploy infrastructure and the Plane application:
module "plane_infra" {
source = "git::https://github.com/makeplane/commercial-deployments.git//terraform?ref=main"
cluster_name = "plane-eks-cluster"
region = "us-west-2"
enable_aws_lb_controller = true
# enable_email_nlb = false # default — omit until Stage 2
}terraform apply
aws eks update-kubeconfig --region us-west-2 --name plane-eks-cluster
kubectl apply -k kustomize/overlays/example # deploys email-service with fixed NodePortsStage 2 — Create the NLB once the email service is running:
module "plane_infra" {
source = "git::https://github.com/makeplane/commercial-deployments.git//terraform?ref=main"
cluster_name = "plane-eks-cluster"
region = "us-west-2"
enable_aws_lb_controller = true
enable_email_nlb = true # creates NLB + registers EKS nodes in target groups
}terraform apply
terraform output email_nlb_dns_name # use this value for your MX recordRoute53 MX record:
After apply, create the following records in Route53 for your domain:
| Type | Name | TTL | Value |
|---|---|---|---|
| MX | yourdomain.com |
300 | 10 <email_nlb_dns_name>. |
| TXT | yourdomain.com |
300 | "v=spf1 include:<email_nlb_dns_name> ~all" |
The NLB listens on:
- Port 25 — SMTP (inbound email from other mail servers)
- Port 465 — SMTPS
- Port 587 — Submission
Override defaults by passing eks, cache, mq, opensearch, object_store, or db objects. See terraform/README.md for all options.
Add these output blocks to your configuration to expose module outputs (e.g. in main.tf or outputs.tf):
output "vpc_id" {
description = "VPC ID"
value = module.plane_infra.vpc_id
}
output "private_subnet_ids" {
description = "Private subnet IDs"
value = module.plane_infra.private_subnet_ids
}
output "eks_cluster_id" {
description = "EKS cluster ID"
value = module.plane_infra.eks_cluster_id
}
output "eks_cluster_endpoint" {
description = "EKS cluster API endpoint"
value = module.plane_infra.eks_cluster_endpoint
}
output "configure_kubectl" {
description = "Command to configure kubectl"
value = module.plane_infra.configure_kubectl
}
output "redis_endpoint" {
description = "Redis endpoint"
value = module.plane_infra.redis_endpoint
}
output "mq_broker_id" {
description = "Amazon MQ RabbitMQ broker ID"
value = module.plane_infra.mq_broker_id
}
output "mq_broker_endpoints" {
description = "Amazon MQ RabbitMQ broker endpoints (AMQP)"
value = module.plane_infra.mq_broker_endpoints
}
output "mq_security_group_id" {
description = "Security group ID of the Amazon MQ broker"
value = module.plane_infra.mq_security_group_id
}
output "opensearch_endpoint" {
description = "OpenSearch endpoint"
value = module.plane_infra.opensearch_endpoint
}
output "s3_bucket_id" {
description = "S3 bucket ID"
value = module.plane_infra.s3_bucket_id
}
output "rds_cluster_endpoint" {
description = "RDS cluster writer endpoint"
value = module.plane_infra.rds_cluster_endpoint
}
output "rds_reader_endpoint" {
description = "RDS cluster reader endpoint"
value = module.plane_infra.rds_reader_endpoint
}
output "rds_db_name" {
description = "Database name"
value = module.plane_infra.rds_db_name
}
output "plane_password_secret_arn" {
description = "ARN of the plane-password secret (contains opensearch_password, mq_password)"
value = module.plane_infra.plane_password_secret_arn
sensitive = true
}
output "rds_password_secret_arn" {
description = "ARN of the RDS master user password secret in Secrets Manager"
value = module.plane_infra.rds_password_secret_arn
sensitive = true
}| Output | Description |
|---|---|
vpc_id |
VPC ID |
private_subnet_ids |
Private subnet IDs |
eks_cluster_id |
EKS cluster ID |
eks_cluster_endpoint |
EKS API endpoint |
configure_kubectl |
Command to configure kubectl |
redis_endpoint |
Redis endpoint |
mq_broker_id |
Amazon MQ RabbitMQ broker ID |
mq_broker_endpoints |
Amazon MQ RabbitMQ broker endpoints (AMQP) |
mq_security_group_id |
Amazon MQ broker security group ID |
opensearch_endpoint |
OpenSearch endpoint |
s3_bucket_id |
S3 bucket ID |
rds_cluster_endpoint |
RDS writer endpoint |
rds_reader_endpoint |
RDS reader endpoint |
rds_db_name |
Database name |
plane_password_secret_arn |
OpenSearch and MQ passwords (Secrets Manager) |
rds_password_secret_arn |
RDS password (Secrets Manager) |
aws eks update-kubeconfig --region us-west-2 --name plane-eks-clusterKustomize manifests and deployment steps will be added here.
After infrastructure is ready:
- Retrieve secrets from AWS Secrets Manager (OpenSearch, MQ, and RDS passwords)
- Apply Kustomize manifests to deploy Plane on the EKS cluster
- Configure ingress and access
To destroy infrastructure:
terraform destroyWarning: This deletes all data in RDS, OpenSearch, Redis, Amazon MQ, and S3. Ensure backups exist if needed.