Plane.so, Open-Source Pjm: Part 1: Setup and Usage

Published: Dec 26, 2023 by Isaac Johnson

Plane.so was started by Vamsi and Vihar Kurama who have worked together seven years and launched Plane in December of 2022. The first dev release was in January 2023. For some time they’ve been on my list of OS suites to checkout.

At just a year old, how good is Plane? It has both a SaaS option and self-hosted one. Let’s give both a try and see how easy they are to setup and use. Today, we’ll explore Docker, On-prem Kubernetes (K3s) and Azure Kubernetes Service (AKS) as well as the cloud hosted option (SaaS).

Self-Hosted Docker

I’ll pull down the source and run setup

Use the develop branch

builder@builder-T100:~$ git clone https://github.com/makeplane/plane.git
Cloning into 'plane'...
remote: Enumerating objects: 58418, done.
remote: Counting objects: 100% (13984/13984), done.
remote: Compressing objects: 100% (3986/3986), done.
remote: Total 58418 (delta 11060), reused 11884 (delta 9927), pack-reused 44434
Receiving objects: 100% (58418/58418), 57.14 MiB | 35.94 MiB/s, done.
Resolving deltas: 100% (41815/41815), done.
builder@builder-T100:~$ cd plane/

builder@builder-T100:~/plane$ git checkout develop
Branch 'develop' set up to track remote branch 'develop' from 'origin'.
Switched to a new branch 'develop'
builder@builder-T100:~/plane$ ./setup.sh

Then I ran docker compose -f docker-compose-local up -d to launch the stack

/content/images/2023/12/planeso-01.png

It took a while but I saw it starting

/content/images/2023/12/planeso-02.png

I noticed it launched quite a lot

/content/images/2023/12/planeso-03.png

My next step is to add an External reference to route traffic from K3s as well as test in my home network to 192.168.1.100:80

I’ll need to change the NGINX_PORT in the .env file from the default 80 and restart.

builder@builder-T100:~/plane$ docker compose -f ./docker-compose-local.yml down
[+] Running 10/10
 ✔ Container plane-proxy-1        Removed                                                                                                                                                            0.6s
 ✔ Container plane-plane-minio-1  Removed                                                                                                                                                            0.6s
 ✔ Container plane-beat-worker-1  Removed                                                                                                                                                            0.8s
 ✔ Container plane-space-1        Removed                                                                                                                                                            0.3s
 ✔ Container plane-web-1          Removed                                                                                                                                                            0.2s
 ✔ Container plane-worker-1       Removed                                                                                                                                                            2.2s
 ✔ Container plane-api-1          Removed                                                                                                                                                           10.2s
 ✔ Container plane-plane-redis-1  Removed                                                                                                                                                            0.3s
 ✔ Container plane-plane-db-1     Removed                                                                                                                                                            0.2s
 ✔ Network plane_dev_env          Removed

Then change the port

builder@builder-T100:~/plane$ cp .env .env.backup
builder@builder-T100:~/plane$ vi .env
builder@builder-T100:~/plane$ diff .env .env.backup
35c35
< NGINX_PORT=8885
---
> NGINX_PORT=80

Now relaunch it

builder@builder-T100:~/plane$ docker compose -f ./docker-compose-local.yml up -d
[+] Building 0.0s (0/0)
[+] Running 10/10
 ✔ Network plane_dev_env          Created                                                                                                                                                            0.1s
 ✔ Container plane-plane-minio-1  Started                                                                                                                                                           60.8s
 ✔ Container plane-plane-db-1     Started                                                                                                                                                           60.9s
 ✔ Container plane-plane-redis-1  Started                                                                                                                                                           60.8s
 ✔ Container plane-api-1          Started                                                                                                                                                           61.2s
 ✔ Container plane-beat-worker-1  Started                                                                                                                                                           61.7s
 ✔ Container plane-worker-1       Started                                                                                                                                                           61.8s
 ✔ Container plane-web-1          Started                                                                                                                                                           62.1s
 ✔ Container plane-space-1        Started                                                                                                                                                           30.4s
 ✔ Container plane-proxy-1        Started                                                                                                                                                            3.5s

At first, I just see a window saying the instance isn’t ready

/content/images/2023/12/planeso-28.png

To set this up we need to go to god-mode first

/content/images/2023/12/planeso-29.png

thing is, it seems to crash the dockerhost

/content/images/2023/12/planeso-30.png

and

/content/images/2023/12/planeso-31.png

After crashing the host a few times, I removed it

$ docker compose -f ./docker-compose-local.yml down
[+] Running 10/10
 ✔ Container plane-plane-minio-1  Removed                                                             0.5s
 ✔ Container plane-proxy-1        Removed                                                             0.6s
 ✔ Container plane-beat-worker-1  Removed                                                             1.1s
 ✔ Container plane-space-1        Removed                                                             0.4s
 ✔ Container plane-web-1          Removed                                                             0.5s
 ✔ Container plane-worker-1       Removed                                                             3.2s
 ✔ Container plane-api-1          Removed                                                            10.7s
 ✔ Container plane-plane-redis-1  Removed                                                             0.8s
 ✔ Container plane-plane-db-1     Removed                                                             0.8s
 ✔ Network plane_dev_env          Removed                                                             0.3s

We’ve tried Docker Compose. Let’s move on to on-prem kubernetes.

On-Prem Kubernetes (K3s)

Let’s add the helm chart

$ helm repo add makeplane https://helm.plane.so/
"makeplane" has been added to your repositories

Then deploy

$ helm install \
ate-name>     --create-namespace \
    --na>     --namespace plane-ns \
>     --set ingress.host="plane.freshbrewed.science" \
> my-plane makeplane/plane-ce
W1216 20:48:59.040804   25834 warnings.go:70] unknown field "spec.backend"
NAME: my-plane
LAST DEPLOYED: Sat Dec 16 20:48:57 2023
NAMESPACE: plane-ns
STATUS: deployed
REVISION: 1
TEST SUITE: None

Though it gets into a crash

Every 2.0s: kubectl get pods -n plane-ns                          DESKTOP-QADGF36: Sat Dec 16 20:52:35 2023

NAME                                       READY   STATUS             RESTARTS      AGE
my-plane-minio-wl-0                        0/1     Pending            0             3m36s
my-plane-pgdb-wl-0                         0/1     Pending            0             3m36s
my-plane-redis-wl-0                        0/1     Pending            0             3m36s
my-plane-minio-bucket-rgvgl                0/1     Init:0/1           0             3m36s
my-plane-space-wl-699dcb8f4-8lcqb          1/1     Running            0             3m36s
my-plane-web-wl-5967775d5b-hfv8n           1/1     Running            0             3m36s
my-plane-space-wl-699dcb8f4-qg8rf          1/1     Running            0             3m36s
my-plane-web-wl-5967775d5b-rs69h           1/1     Running            0             3m36s
my-plane-space-wl-699dcb8f4-lzwpr          1/1     Running            0             3m36s
my-plane-web-wl-5967775d5b-swdb2           1/1     Running            0             3m36s
my-plane-worker-wl-854687c594-qgr7n        1/1     Running            0             3m36s
my-plane-api-wl-599bd48dc8-qf57b           0/1     CrashLoopBackOff   4 (61s ago)   3m36s
my-plane-beat-worker-wl-68dbfdb544-tjpq9   0/1     CrashLoopBackOff   4 (58s ago)   3m36s
my-plane-api-wl-599bd48dc8-j9rt4           0/1     CrashLoopBackOff   3 (18s ago)   3m36s
my-plane-api-wl-599bd48dc8-qhtxb           1/1     Running            4 (45s ago)   3m36s

Let me fix the first issue, PVCs using ‘longhorn’ not set in this cluster

$ helm install     --create-namespace     --namespace plane-ns     --set ingress.host="plane.freshbrewed.science" --set postgres.storageClass='local-path' --set redis.storageClass='local-path' --set minio.storageClass='local-path' my-p
lane makeplane/plane-ce
W1216 20:59:12.400540   31543 warnings.go:70] unknown field "spec.backend"
NAME: my-plane
LAST DEPLOYED: Sat Dec 16 20:59:10 2023
NAMESPACE: plane-ns
STATUS: deployed
REVISION: 1
TEST SUITE: None

After a bit, I saw the pods come up

builder@DESKTOP-QADGF36:~$ kubectl get pvc -n plane-ns
NAME                                         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc-my-plane-pgdb-vol-my-plane-pgdb-wl-0     Bound    pvc-43f1336e-94e8-4527-910d-3106ee8e106c   5Gi        RWO            local-path     109s
pvc-my-plane-minio-vol-my-plane-minio-wl-0   Bound    pvc-d14a5115-fdbd-4f21-a814-6ec7747ede7b   5Gi        RWO            local-path     109s
pvc-my-plane-redis-vol-my-plane-redis-wl-0   Bound    pvc-77d9f82d-30f5-4f79-b59f-b1c4e7f90b74   1Gi        RWO            local-path     109s
builder@DESKTOP-QADGF36:~$ kubectl get pods -n plane-ns
NAME                                       READY   STATUS      RESTARTS       AGE
my-plane-worker-wl-854687c594-dwzt8        1/1     Running     0              118s
my-plane-space-wl-699dcb8f4-dbgq9          1/1     Running     0              118s
my-plane-web-wl-5967775d5b-hthhc           1/1     Running     0              118s
my-plane-web-wl-5967775d5b-gdmjz           1/1     Running     0              118s
my-plane-space-wl-699dcb8f4-ppcpc          1/1     Running     0              118s
my-plane-api-wl-599bd48dc8-hmt7h           1/1     Running     1 (104s ago)   118s
my-plane-pgdb-wl-0                         1/1     Running     0              118s
my-plane-minio-wl-0                        1/1     Running     0              118s
my-plane-web-wl-5967775d5b-khv2j           1/1     Running     0              118s
my-plane-space-wl-699dcb8f4-v59n6          1/1     Running     0              118s
my-plane-api-wl-599bd48dc8-rwdvd           1/1     Running     3 (50s ago)    118s
my-plane-beat-worker-wl-68dbfdb544-b2r49   1/1     Running     1 (60s ago)    119s
my-plane-api-wl-599bd48dc8-28bvh           1/1     Running     1 (50s ago)    118s
my-plane-redis-wl-0                        1/1     Running     0              118s
my-plane-minio-bucket-llfc6                0/1     Completed   0              118s

I’ll create a NodePort service

apiVersion: v1
kind: Service
metadata:
  name: my-plane-web-np
  namespace: plane-ns
spec:
  ports:
  - targetPort: 30008
    port: 3000
    targetPort: 3000
  selector:
    app.name: plane-ns-my-plane-web
  type: NodePort

Then create based on a YAML

$ kubectl apply -f ./nodeport.yaml
service/my-plane-web-np created

I can now see the nodeport

builder@DESKTOP-QADGF36:~$ kubectl get svc -n plane-ns
NAME                        TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)             AGE
my-plane-minio              ClusterIP   None           <none>        9000/TCP,9090/TCP   10m
my-plane-pgdb               ClusterIP   None           <none>        5432/TCP            10m
my-plane-web                ClusterIP   None           <none>        3000/TCP            10m
my-plane-redis              ClusterIP   None           <none>        6379/TCP            10m
my-plane-api                ClusterIP   None           <none>        8000/TCP            10m
my-plane-space              ClusterIP   None           <none>        3000/TCP            10m
my-plane-pgdb-cli-connect   NodePort    10.43.203.46   <none>        5432:30000/TCP      10m
my-plane-web-np             NodePort    10.43.15.33    <none>        3000:32332/TCP      60s

Though it seems to time out

/content/images/2023/12/planeso-32.png

And port-forwarding also failed

$ kubectl get pods -l app.name=plane-ns-my-plane-web -n plane-ns
NAME                               READY   STATUS    RESTARTS   AGE
my-plane-web-wl-5967775d5b-gdmjz   1/1     Running   0          15m
my-plane-web-wl-5967775d5b-khv2j   1/1     Running   0          15m

$ kubectl port-forward my-plane-web-wl-5967775d5b-gdmjz -n plane-ns 6543:80
Forwarding from 127.0.0.1:6543 -> 80
Forwarding from [::1]:6543 -> 80
Handling connection for 6543
Handling connection for 6543
E1216 21:17:20.488016    7108 portforward.go:409] an error occurred forwarding 6543 -> 80: error forwarding port 80 to pod 60beecac1c4b26774ff8736c3800fa1ca0155e8886dce1825f86805b74322d11, uid : failed to execute portforward in network namespace "/var/run/netns/cni-8afccb7e-6d01-44bf-e57a-d13ebec9bea1": failed to connect to localhost:80 inside namespace "60beecac1c4b26774ff8736c3800fa1ca0155e8886dce1825f86805b74322d11", IPv4: dial tcp4 127.0.0.1:80: connect: connection refused IPv6 dial tcp6: address localhost: no suitable address found
error: lost connection to pod

I tried to fetch logs and then tried port 3000

builder@DESKTOP-QADGF36:~$ kubectl logs my-plane-web-wl-5967775d5b-khv2j -n plane-ns
+ echo 'Starting Plane Frontend..'
Starting Plane Frontend..
+ node web/server.js
   ▲ Next.js 14.0.3
   - Local:        http://my-plane-web-wl-5967775d5b-khv2j:3000
   - Network:      http://10.42.2.27:3000

 ✓ Ready in 11.6s

builder@DESKTOP-QADGF36:~$ kubectl port-forward my-plane-web-wl-5967775d5b-khv2j -n plane-ns 6543:3000
Forwarding from 127.0.0.1:6543 -> 3000
Forwarding from [::1]:6543 -> 3000
Handling connection for 6543
Handling connection for 6543
E1216 21:19:23.941310    8000 portforward.go:409] an error occurred forwarding 6543 -> 3000: error forwarding port 3000 to pod cb9c9d09ea63478b563d4bfde43d6326407c45aa72b8e581bb55767732c2d447, uid : failed to execute portforward in network namespace "/var/run/netns/cni-d06b3ff2-cb63-f7b5-91a3-131f3dacbb4e": failed to connect to localhost:3000 inside namespace "cb9c9d09ea63478b563d4bfde43d6326407c45aa72b8e581bb55767732c2d447", IPv4: dial tcp4 127.0.0.1:3000: connect: connection refused IPv6 dial tcp6: address localhost: no suitable address found
error: lost connection to pod

I again tried to create a NodePort

$ kubectl get svc -n plane-ns
NAME                        TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)             AGE
my-plane-minio              ClusterIP   None           <none>        9000/TCP,9090/TCP   22m
my-plane-pgdb               ClusterIP   None           <none>        5432/TCP            22m
my-plane-web                ClusterIP   None           <none>        3000/TCP            22m
my-plane-redis              ClusterIP   None           <none>        6379/TCP            22m
my-plane-api                ClusterIP   None           <none>        8000/TCP            22m
my-plane-space              ClusterIP   None           <none>        3000/TCP            22m
my-plane-pgdb-cli-connect   NodePort    10.43.203.46   <none>        5432:30000/TCP      22m
my-plane-web-np             NodePort    10.43.12.170   <none>        3000:30333/TCP      11s

$ kubectl get pods -n plane-ns
NAME                                       READY   STATUS      RESTARTS      AGE
my-plane-worker-wl-854687c594-dwzt8        1/1     Running     0             24m
my-plane-space-wl-699dcb8f4-dbgq9          1/1     Running     0             24m
my-plane-web-wl-5967775d5b-gdmjz           1/1     Running     0             24m
my-plane-space-wl-699dcb8f4-ppcpc          1/1     Running     0             24m
my-plane-api-wl-599bd48dc8-hmt7h           1/1     Running     1 (23m ago)   24m
my-plane-pgdb-wl-0                         1/1     Running     0             24m
my-plane-minio-wl-0                        1/1     Running     0             24m
my-plane-web-wl-5967775d5b-khv2j           1/1     Running     0             24m
my-plane-api-wl-599bd48dc8-rwdvd           1/1     Running     3 (22m ago)   24m
my-plane-beat-worker-wl-68dbfdb544-b2r49   1/1     Running     1 (23m ago)   24m
my-plane-api-wl-599bd48dc8-28bvh           1/1     Running     1 (22m ago)   24m
my-plane-redis-wl-0                        1/1     Running     0             24m
my-plane-minio-bucket-llfc6                0/1     Completed   0             24m

But nothing came up

/content/images/2023/12/planeso-33.png

I tried the ports on all three nodes

/content/images/2023/12/planeso-34.png

I tried 11 hours later from both WSL and Windows, still without success

$ kubectl get svc -n plane-ns
NAME                        TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)             AGE
my-plane-minio              ClusterIP   None           <none>        9000/TCP,9090/TCP   11h
my-plane-pgdb               ClusterIP   None           <none>        5432/TCP            11h
my-plane-web                ClusterIP   None           <none>        3000/TCP            11h
my-plane-redis              ClusterIP   None           <none>        6379/TCP            11h
my-plane-api                ClusterIP   None           <none>        8000/TCP            11h
my-plane-space              ClusterIP   None           <none>        3000/TCP            11h
my-plane-pgdb-cli-connect   NodePort    10.43.203.46   <none>        5432:30000/TCP      11h
my-plane-web-np             NodePort    10.43.12.170   <none>        3000:30333/TCP      11h


PS C:\Users\isaac> kubectl get nodes
NAME                  STATUS   ROLES                  AGE     VERSION
builder-macbookpro2   Ready    <none>                 4d10h   v1.27.6+k3s1
isaac-macbookpro      Ready    <none>                 4d10h   v1.27.6+k3s1
anna-macbookair       Ready    control-plane,master   4d11h   v1.27.6+k3s1
PS C:\Users\isaac> kubectl port-forward svc/my-plane-web -n plane-ns 3500:3000
Forwarding from 127.0.0.1:3500 -> 3000
Forwarding from [::1]:3500 -> 3000
Handling connection for 3500
Handling connection for 3500
E1217 08:46:33.673148   29828 portforward.go:406] an error occurred forwarding 3500 -> 3000: error forwarding port 3000 to pod 60beecac1c4b26774ff8736c3800fa1ca0155e8886dce1825f86805b74322d11, uid : failed to execute portforward in network namespace "/var/run/netns/cni-8afccb7e-6d01-44bf-e57a-d13ebec9bea1": failed to connect to localhost:3000 inside namespace "60beecac1c4b26774ff8736c3800fa1ca0155e8886dce1825f86805b74322d11", IPv4: dial tcp4 127.0.0.1:3000: connect: connection refused IPv6 dial tcp6: address localhost: no suitable address found
E1217 08:46:33.673670   29828 portforward.go:234] lost connection to pod
Handling connection for 3500
E1217 08:46:33.674206   29828 portforward.go:346] error creating error stream for port 3500 -> 3000: EOF
PS C:\Users\isaac>

PS C:\Users\isaac> kubectl port-forward my-plane-web-wl-5967775d5b-gdmjz -n plane-ns 3500:3000
Forwarding from 127.0.0.1:3500 -> 3000
Forwarding from [::1]:3500 -> 3000
Handling connection for 3500
Handling connection for 3500
E1217 08:48:02.557787   26684 portforward.go:406] an error occurred forwarding 3500 -> 3000: error forwarding port 3000 to pod 60beecac1c4b26774ff8736c3800fa1ca0155e8886dce1825f86805b74322d11, uid : failed to execute portforward in network namespace "/var/run/netns/cni-8afccb7e-6d01-44bf-e57a-d13ebec9bea1": failed to connect to localhost:3000 inside namespace "60beecac1c4b26774ff8736c3800fa1ca0155e8886dce1825f86805b74322d11", IPv4: dial tcp4 127.0.0.1:3000: connect: connection refused IPv6 dial tcp6: address localhost: no suitable address found
E1217 08:48:02.558815   26684 portforward.go:234] lost connection to pod
Handling connection for 3500
E1217 08:48:02.559345   26684 portforward.go:346] error creating error stream for port 3500 -> 3000: EOF

I can see the web server is running, however

PS C:\Users\isaac> kubectl logs my-plane-web-wl-5967775d5b-gdmjz -n plane-ns
+ echo 'Starting Plane Frontend..'
Starting Plane Frontend..
+ node web/server.js
   ▲ Next.js 14.0.3
   - Local:        http://my-plane-web-wl-5967775d5b-gdmjz:3000
   - Network:      http://10.42.1.21:3000

 ✓ Ready in 14s

Let’s do this in Azure.

I’ll create a subscription and SP

$ az group create --name planesorg --location centralus
{
  "id": "/subscriptions/8defc61d-657a-453d-a6ff-cb9f91289a61/resourceGroups/planesorg",
  "location": "centralus",
  "managedBy": null,
  "name": "planesorg",
  "properties": {
    "provisioningState": "Succeeded"
  },
  "tags": null,
  "type": "Microsoft.Resources/resourceGroups"
}


$ az ad sp create-for-rbac -n idjaksupg01sp --skip-assignment --output json > my_sp.json
WARNING: Option '--skip-assignment' has been deprecated and will be removed in a future release.
WARNING: Found an existing application instance: (id) 34fede26-2ae7-4ccd-9c29-3bc220b9784a. We will patch it.
WARNING: The output includes credentials that you must protect. Be sure that you do not include these credentials in your code or check the credentials into your source control. For more information, see https://aka.ms/azadsp-cli


$ export SP_PASS=`cat my_sp.json | jq -r .password`
$ export SP_ID=`cat my_sp.json | jq -r .appId`

Now I’ll create the AKS cluster

$ az aks create --resource-group planesorg --name idjplanetest --location centralus --node-count 3 --enable-cluster-autoscaler --min-count 2 --max-count 4 --generate-ssh-keys --network-plugin azure --network-policy azure --service-principal $SP_ID --client-secret $SP_PASS
docker_bridge_cidr is not a known attribute of class <class 'azure.mgmt.containerservice.v2023_10_01.models._models_py3.ContainerServiceNetworkProfile'> and will be ignored
 \ Running ..

Now let’s get admin creds

$ az aks get-credentials -n idjplanetest -g planesorg --admin

I need a basic Nginx ingress controller

$ NAMESPACE=ingress-basic
$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
"ingress-nginx" has been added to your repositories
$ helm repo update


$ helm install ingress-nginx ingress-nginx/ingress-nginx \
  --create-namespace \
  --namespace $NAMESPACE \
  --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz

This really will need cert-manager as well

$ helm repo add jetstack https://charts.jetstack.io
$ helm repo update

Then install with CRDs

$ helm install \
>   cert-manager jetstack/cert-manager \
>   --namespace cert-manager \
>   --create-namespace \
>   --version v1.13.3 \
>   --set installCRDs=true

I’ll want to get the external IP of the Nginx LB:

$ kubectl get svc --all-namespaces | grep Load
ingress-basic       ingress-nginx-controller             LoadBalancer   10.0.60.100    20.98.176.239   80:31488/TCP,443:31468/TCP   3m16s

And use it in a new A Record

/content/images/2023/12/planeso-35.png

Finally, I should have the basics setup for a helm deploy of Plane.so into AKS

$ helm install --create-namespace --namespace plane-ns --set ingress.appHost="plane.azdo.space"  --set ingress.host="plane.azdo.space" --set postgres.storageClass=default --set redis.storageClass=default --set minio.storageClass=default my-plane makeplane/plane-ce
W1217 16:57:46.340037   26259 warnings.go:70] unknown field "spec.backend"
NAME: my-plane
LAST DEPLOYED: Sun Dec 17 16:57:43 2023
NAMESPACE: plane-ns
STATUS: deployed
REVISION: 1
TEST SUITE: None

I have a few crashing pods, but it sees basically up

$ kubectl get pods -n plane-ns
NAME                                       READY   STATUS             RESTARTS      AGE
my-plane-api-wl-599bd48dc8-c59ll           1/1     Running            0             102s
my-plane-api-wl-599bd48dc8-d75dr           0/1     CrashLoopBackOff   2 (20s ago)   102s
my-plane-api-wl-599bd48dc8-v6x55           0/1     CrashLoopBackOff   2 (20s ago)   102s
my-plane-beat-worker-wl-68dbfdb544-zt8js   1/1     Running            3 (34s ago)   102s
my-plane-minio-bucket-hfrc2                0/1     Completed          0             102s
my-plane-minio-wl-0                        1/1     Running            0             102s
my-plane-pgdb-wl-0                         1/1     Running            0             102s
my-plane-redis-wl-0                        1/1     Running            0             102s
my-plane-space-wl-699dcb8f4-75fnm          1/1     Running            0             102s
my-plane-space-wl-699dcb8f4-cd5dp          1/1     Running            0             102s
my-plane-space-wl-699dcb8f4-fljc9          1/1     Running            0             102s
my-plane-web-wl-5967775d5b-mndh9           1/1     Running            0             102s
my-plane-web-wl-5967775d5b-n7vj2           1/1     Running            0             102s
my-plane-web-wl-5967775d5b-nbx77           1/1     Running            0             102s
my-plane-worker-wl-854687c594-g2v9g        1/1     Running            0             102s

/content/images/2023/12/planeso-36.png

I’ll try god mode, albeit in http

/content/images/2023/12/planeso-37.png

the Cert is not valid with HTTPS. I’m guessing we need to add the LE Acme provider.

/content/images/2023/12/planeso-38.png

This time initial setup did work

/content/images/2023/12/planeso-39.png

To use it, there are a few things we need to handle…

1. Email SMTP:

Here we could set the outgoing mail server host and credentials

/content/images/2023/12/planeso-40.png

2. SSO / OAuth

Out of the box, Plane supports Google and Github federated IdPs. We could configurate that here.

/content/images/2023/12/planeso-41.png

3. AI

AI setup is used for the “AI” issue description generation, if you want that.

/content/images/2023/12/planeso-42.png

4. Unsplash Libraries

To add some nice backgrounds and images, Plane uses Unsplash which requires an Access key

/content/images/2023/12/planeso-43.png

With regard to these above setup steps; At this time, I don’t have GPT key but I could use Resend.dev or Sendgrid for the email and setup proper OAuth, at least through Github.

Since I do not plan to keep this instance up, I’ll skip over it for now and do a tour of what we get just out-of-the-box.

User mode

Since we are already logged in, we can create a workspace

/content/images/2023/12/planeso-44.png

and give ourselves a title

/content/images/2023/12/planeso-45.png

Then move on to creating a Project

/content/images/2023/12/planeso-46.png

and when created

/content/images/2023/12/planeso-47.png

Since I’m paying for the AKS, I don’t want to take too much time - the point was to see that I could self-host if I wanted to… instead let’s pivot to using SaaS instance.

Always, before we do, let’s cleanup first

$ az aks delete -n idjplanetest -g planesorg
Are you sure you want to perform this operation? (y/n): y

$ az group delete -n planesorg
Are you sure you want to perform this operation? (y/n): y

Plane.so Cloud SaaS

To sign up for the Cloud hosted option, we can go to https://app.plane.so/ where we can use, today, Github or Google as federated IdP’s or create a local account

/content/images/2023/12/planeso-04.png

That then takes me to a page to set name details, role and time zone

/content/images/2023/12/planeso-05.png

I’ll then have a chance to name my workspace and org size

/content/images/2023/12/planeso-06.png

I can invite people into my org. I’ll choose some different roles so we can see how they behave

/content/images/2023/12/planeso-07.png

Projects

My first step will be to create a project

/content/images/2023/12/planeso-08.png

I’ll start with a public facing “Blog Posts” project (BLOGP)

/content/images/2023/12/planeso-09.png

This returns us to a nice-looking activity dashboard for the project

/content/images/2023/12/planeso-10.png

One handy feature of Plane.so is it has slick keyboard shortcuts. We can see the list by clicking the lightning bolt icon in the lower left

/content/images/2023/12/planeso-11.png

I’ll enter ‘c’ to bring up the create window for an issue. We can even try “AI”, why not.

We can now view the issue and set fields like Priority

/content/images/2023/12/planeso-13.png

Interestingly I tried to view this issue in a different browser and it told me I wasn’t a member of the workspace. So we’ll have to review what “public” means or perhaps I’m using it errantly.

Other Users

Just hitting the site as a different user prompted for my IdP

/content/images/2023/12/planeso-14.png

I confirmed my TZ and it then noticed an invite was awaiting this identity

/content/images/2023/12/planeso-15.png

Even though Tristan is just user/engineer, it would seem everyone gets their own “space”, so we’ll create a ‘tcmself’ space

/content/images/2023/12/planeso-16.png

While they were dropped into TPK, they can hop back to the ‘tcmself’ space from the Workspaces dropdown

/content/images/2023/12/planeso-17.png

Just because Tristan is a user in TPK, doesn’t make them a member of all projects. To contribute, Tristan must “join” the Blog Posts project

/content/images/2023/12/planeso-18.png

They’ll click the join button

/content/images/2023/12/planeso-19.png

Now, the assignee list has two choices

/content/images/2023/12/planeso-20.png

Let’s see how this looks in mobile for my last user who doesn’t use Google IdP.

The Executive get’s an email in outlook

/content/images/2023/12/planeso-21.jpg

He sees he is invited to the workspace already

/content/images/2023/12/planeso-22.jpg

In this flow, the user is sent a code

/content/images/2023/12/planeso-23.jpg

They put in the code

/content/images/2023/12/planeso-24.png

Then it’s the normal flow of TZ and role

/content/images/2023/12/planeso-25.jpg

One last accept

/content/images/2023/12/planeso-26.jpg

and we can see the mobile version of the site

/content/images/2023/12/planeso-27.jpg

Summary

We have given a decent shot at getting this running on Docker and Kubernetes on prem. Clearly it’s a bit hefty for my current cluster as moving to Azure Kubernetes Service (AKS) sorted things out. We did a quick tour of the self-hosted before pivoting to the Cloud SaaS option.

We’ve only scratched the surface; Setting up a Workspace, project and users of various levels. We touched on AI descriptions and keyboard shortcuts. I’m rather excited to do more with Plane.so. In our next article, we’ll follow this up with a dive into Github integrations (it supposedly can do syncing with Github issues), API usage and how the open-source/free scales to the paid plans.

Agile OpenSource Plane

Have something to add? Feedback? You can use the feedback form

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes