Devspace and Loft.sh: Scalable self-managed kubernetes

Published: Jul 5, 2022 by Isaac Johnson

Devspace is an OSS toolset that let’s users easily launch interactive containers into managed namespaces. With just the source code and Dockerfile, It will discover any YAML, launch a container with port-forwarding and drop developers into an interactive shell to debug their code. Loft.sh which we covered back in May is a commercial tool with a nice free tier that lets one manage virtual clusters and namespaces.

Today, we’ll walk through using both, first Devspace on its own with our own Docker with Tests example. Then we’ll setup an example vcluster and use it with Devspace to see how our users could safely create self-contained clusters for testing their work. Lastly, we’ll create a demo user and show how non-admins can use the system.

Installing Devspace CLI

First, we need to install the Devspace CLI. We can follow the instructions on their site.

For me, I’ll use brew in WSL.

$ brew install devspace
==> Downloading https://ghcr.io/v2/homebrew/core/kubernetes-cli/manifests/1.24.2
######################################################################## 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/kubernetes-cli/blobs/sha256:42e2f4fbed12d8e5a55470f3d98e8d0a5f4c72da4377859cd280d67b224ee8e2
==> Downloading from https://pkg-containers.githubusercontent.com/ghcr1/blobs/sha256:42e2f4fbed12d8e5a55470f3d98e8d0a5f4c72da4377859cd280d67b224ee8e2?se=2022-07-04T20%3A45
######################################################################## 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/devspace/manifests/5.18.5
######################################################################## 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/devspace/blobs/sha256:e98e00b8e7de436cccc3417dcd9e74c7adf096352260f265a0ad06de61d16923
==> Downloading from https://pkg-containers.githubusercontent.com/ghcr1/blobs/sha256:e98e00b8e7de436cccc3417dcd9e74c7adf096352260f265a0ad06de61d16923?se=2022-07-04T20%3A45
######################################################################## 100.0%
==> Installing dependencies for devspace: kubernetes-cli
==> Installing devspace dependency: kubernetes-cli
==> Pouring kubernetes-cli--1.24.2.x86_64_linux.bottle.tar.gz
🍺  /home/linuxbrew/.linuxbrew/Cellar/kubernetes-cli/1.24.2: 228 files, 44.5MB
==> Installing devspace
==> Pouring devspace--5.18.5.x86_64_linux.bottle.tar.gz
🍺  /home/linuxbrew/.linuxbrew/Cellar/devspace/5.18.5: 5 files, 49.6MB
==> Running `brew cleanup devspace`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).

Alternatively, we could just use curl and move the binary

curl -s -L "https://github.com/loft-sh/devspace/releases/latest" | sed -nE 's!.*"([^"]*devspace-linux-amd64)".*!https://github.com\1!p' | xargs -n 1 curl -L -o devspace && chmod +x devspace;
sudo install devspace /usr/local/bin;

As of this moment, that will give us latest stable, 5.x

$ devspace --version
devspace version 5.18.5

If we wish to be bleeding edge (we do we do), then we can upgrade to the beta v6

$ devspace upgrade --version=v6.0.0-beta.0
[done] √ Successfully updated devspace to version v6.0.0-beta.0

$ devspace --version
devspace version 6.0.0-beta.0

Using Devspace with Loft

I was given the pro-tip that devspace + loft are a really solid combo so I’ll try mixing right at the start

First, I’ll login to my loft instance and create a Virtual Cluster for, let’s say the Super QC department.

/content/images/2022/07/devspaces-01.png

I’ll click Create Virtual Cluster and give it a name and ID. In fact, I’ll add a bit more details including some labels, annotations and a description

/content/images/2022/07/devspaces-02.png

After I click create, the Status will go from Starting to Pending to ContainerCreating in short order

/content/images/2022/07/devspaces-03.png

When it reaches Running, we can actually use it

/content/images/2022/07/devspaces-04.png

I had a couple nuances with Loft, namely the “k3s” template didn’t work so hot, but the “K8s” did. I also added the rbac.clusterRole.create option to the helm chart (otherwise you may see a repeating “couldn’t find virtual cluster kube-config, will retry in 1 seconds” error in your logs)

/content/images/2022/07/devspaces-05.png

Using the vCluster

We can now login and use the vcluster. These steps assume you’ve installed the Loft CLI.

If you haven’t, you can find the steps here.

Let’s login (It will pop up a window for login)

$ loft list vclusters
[fatal]  not logged in, please make sure you have run 'loft login [loft-url]'

$ loft login loft.freshbrewed.science
[info]   If the browser does not open automatically, please navigate to https://loft.freshbrewed.science/login?cli=true
[info]   If you have problems logging in, please navigate to https://loft.freshbrewed.science/profile/access-keys, click on 'Create Access Key' and then login via 'loft login https://loft.freshbrewed.science --access-key ACCESS_KEY'
[done] √ Successfully logged into Loft instance https://loft.freshbrewed.science

Now I can list my vClusters

$ loft list vclusters

 Name                          Space                         Cluster        Status   Age
 My Super QC1 (my-super-qc1)   vcluster-my-super-qc1-pdsjy   loft-cluster   Active   7m39s

And then use it:

$ loft use vcluster my-super-qc1
[done] √ Successfully updated kube context to use space vcluster-my-super-qc1-pdsjy in cluster loft-cluster

$ kubectl get ns
NAME              STATUS   AGE
default           Active   7m22s
kube-node-lease   Active   7m23s
kube-public       Active   7m24s
kube-system       Active   7m24s

We can also see that a context was added out our kubeconfig

$ kubectx
default
docker-desktop
loft-vcluster_my-super-qc1_vcluster-my-super-qc1-pdsjy_loft-cluster

And one more verification, we can switch to default and se the “my-super-qc1” namespace, then switch back and see just the namespaces in our virtual cluster

$ kubectx default
Switched to context "default".
$ kubectl get namespaces
NAME                          STATUS   AGE
default                       Active   29d
kube-system                   Active   29d
kube-public                   Active   29d
kube-node-lease               Active   29d
cert-manager                  Active   22d
actions-runner-system         Active   17d
dapr-system                   Active   13d
loft                          Active   13d
crossplane-system             Active   13d
test2                         Active   6d9h
test                          Active   6d9h
vcluster-my-super-qc1-pdsjy   Active   14m
$ kubectx loft-vcluster_my-super-qc1_vcluster-my-super-qc1-pdsjy_loft-cluster
Switched to context "loft-vcluster_my-super-qc1_vcluster-my-super-qc1-pdsjy_loft-cluster".
$ kubectl get namespaces
NAME              STATUS   AGE
default           Active   13m
kube-node-lease   Active   13m
kube-public       Active   13m
kube-system       Active   13m

An interesting note, if you add or restore nodes, they don’t automatically get added to the virtual cluster.

For instance, I had a powered off node I had to bring back online. The “main” cluster saw it, but the VirtualCluster did not

$ kubectx default
Switched to context "default".

$ kubectl get nodes
NAME                     STATUS   ROLES                  AGE   VERSION
anna-macbookair          Ready    control-plane,master   29d   v1.23.6+k3s1
builder-macbookpro2      Ready    <none>                 21d   v1.23.6+k3s1
hp-hp-elitebook-850-g2   Ready    <none>                 16d   v1.23.6+k3s1

$ kubectx loft-vcluster_my-super-qc1_vcluster-my-super-qc1-pdsjy_loft-cluster
Switched to context "loft-vcluster_my-super-qc1_vcluster-my-super-qc1-pdsjy_loft-cluster".

$ kubectl get nodes
NAME                  STATUS   ROLES    AGE     VERSION
anna-macbookair       Ready    <none>   3h32m   v1.23.5
builder-macbookpro2   Ready    <none>   3h8m    v1.23.5

Using Devspace with a local Repo

Now that we are in a virtual cluster, we can the devspaces CLI to init.

We’ll continue with the dockerWithTest2 repo I’ve been enjoying lately to launch the app. The main has a Dockerfile that should work just fine

builder@DESKTOP-QADGF36:~/Workspaces/dockerWithTests2$ git checkout main
Switched to branch 'main'
Your branch is behind 'origin/main' by 6 commits, and can be fast-forwarded.
  (use "git pull" to update your local branch)
$ git pull
Updating f119408..e9d01aa
Fast-forward
 k8s/deployment.yaml | 7 -------
 1 file changed, 7 deletions(-)
$ git remote show origin | head -n3
* remote origin
  Fetch URL: https://github.com/idjohnson/dockerWithTests2.git
  Push  URL: https://github.com/idjohnson/dockerWithTests2.git

Usuing init takes us through a questionare

$ devspace init


     %########%
     %###########%       ____                 _____
         %#########%    |  _ \   ___ __   __ / ___/  ____    ____   ____ ___
         %#########%    | | | | / _ \\ \ / / \___ \ |  _ \  / _  | / __// _ \
     %#############%    | |_| |(  __/ \ V /  ____) )| |_) )( (_| |( (__(  __/
     %#############%    |____/  \___|  \_/   \____/ |  __/  \__,_| \___\\___|
 %###############%                                  |_|
 %###########%


info Detecting programming language...

? Select the programming language of this project javascript

? How do you want to deploy this project? kustomize

? Please enter path to your Kustomization folder (e.g. ./kube/kustomization/) ./k8s

? Which image do you want to develop with DevSpace? idjacrdemo02.azurecr.io/dockerwithtests:devlatest

? How should DevSpace build the container image for this project? Use this existing Dockerfile: ./Dockerfile

? Which port is your application listening on? (Enter to skip)

done Project successfully initialized
info Configuration saved in devspace.yaml - you can make adjustments as needed

You can now run:
1. devspace use namespace - to pick which Kubernetes namespace to work in
2. devspace dev - to start developing your project in Kubernetes

Run `devspace -h` or `devspace [command] -h` to see a list of available commands and flags

We can see one folder and two files were added (and .gitignore updated to ignore the dotfolder)

$ ls -la | grep devspace
drwxr-xr-x   3 builder builder   4096 Jul  4 16:20 .devspace
-rw-r--r--   1 builder builder   3143 Jul  4 16:20 devspace.yaml
-rwxr-xr-x   1 builder builder   1447 Jul  4 16:20 devspace_start.sh

Next we want to tell devspace which cluster config to use. Devspace assumes we may not wish to use the currently selected one for this particular project

$ devspace use context

? Which context do you want to use? loft-vcluster_my-super-qc1_vcluster-my-super-qc1-pdsjy_loft-cluster
done Successfully set kube-context to 'loft-vcluster_my-super-qc1_vcluster-my-super-qc1-pdsjy_loft-cluster'

Lastly, I’ll tell devspace which namespace to create/use

$ devspace use namespace testingdockerwithtests2
info The default namespace of your current kube-context 'loft-vcluster_my-super-qc1_vcluster-my-super-qc1-pdsjy_loft-cluster' has been updated to 'testingdockerwithtests2'
         To revert this operation, run: devspace use namespace testingDockerWithTests2

done Successfully set default namespace to 'testingdockerwithtests2'

Then launch

$ devspace dev
info Using namespace 'testingdockerwithtests2'
info Using kube context 'loft-vcluster_my-super-qc1_vcluster-my-super-qc1-pdsjy_loft-cluster'
info Created namespace: testingdockerwithtests2
deploy:app Applying manifests with kubectl...
deploy:app Render manifests with 'kustomize build ./k8s'
deploy:app Error from server (NotFound): error when creating "STDIN": namespaces "test" not found
deploy:app Error from server (NotFound): error when creating "STDIN": namespaces "test" not found
create_deployments: error deploying app: error executing 'kubectl --context loft-vcluster_my-super-qc1_vcluster-my-super-qc1-pdsjy_loft-cluster apply --force -f -':
Please make sure the command `kubectl apply` does work locally with manifest `./k8s`
fatal exit status 1

I’ll remove the hardcoded namespace in the k8s deployment folder

$ find k8s -type f -exec sed -i 's/^.*namespace: test.*$//' {} \; -print
k8s/kustomization.yaml
k8s/flux-system/kustomization.yaml
k8s/flux-system/gotk-sync.yaml
k8s/flux-system/gotk-components.yaml
k8s/policies.yaml
k8s/deployment.yaml

Now I can do a dev deployment

$ devspace dev
info Using namespace 'testingdockerwithtests2'
info Using kube context 'loft-vcluster_my-super-qc1_vcluster-my-super-qc1-pdsjy_loft-cluster'
deploy:app Applying manifests with kubectl...
deploy:app Render manifests with 'kustomize build ./k8s'
deploy:app service/nginx-run-svc created
deploy:app deployment.apps/my-nginx created
deploy:app Successfully deployed app with kubectl
dev:app Waiting for pod to become ready...
dev:app DevSpace is waiting, because Pod my-nginx-devspace-5dd965c5d9-l59rs has status: ContainerCreating

In a few moments, we see it’s launched the container and is port-forwarding

$ devspace dev
info Using namespace 'testingdockerwithtests2'
info Using kube context 'loft-vcluster_my-super-qc1_vcluster-my-super-qc1-pdsjy_loft-cluster'
deploy:app Applying manifests with kubectl...
deploy:app Render manifests with 'kustomize build ./k8s'
deploy:app service/nginx-run-svc created
deploy:app deployment.apps/my-nginx created
deploy:app Successfully deployed app with kubectl
dev:app Waiting for pod to become ready...
dev:app DevSpace is waiting, because Pod my-nginx-devspace-5dd965c5d9-l59rs has status: ContainerCreating
dev:app DevSpace is waiting, because Pod my-nginx-devspace-5dd965c5d9-l59rs has status: ContainerCreating
dev:app DevSpace is waiting, because Pod my-nginx-devspace-5dd965c5d9-l59rs has status: ContainerCreating
dev:app Selected my-nginx-devspace-5dd965c5d9-l59rs:my-nginx (pod:container)
dev:app ssh   Port forwarding started on: 11103 -> 8022
dev:app ports Port forwarding started on: 9229 -> 9229
dev:app sync  Sync started on: ./ <-> ./
dev:app sync  Waiting for initial sync to complete
dev:app proxy Port forwarding started on: 10187 <- 10567
dev:app ssh   Use 'ssh app.dockerwithtests2.devspace' to connect via SSH

If I do nothing, in a few moments it logs me into the container so I can debug.

/content/images/2022/07/devspaces-06.png

Trying npm start I realize I neglected to install nodemon

devspace ./app # npm start

> nodewithtests@1.0.0 start
> nodemon --inspect=0.0.0.0:9229 server.js

sh: nodemon: not found
devspace ./app # cat package.json
{
  "name": "nodewithtests",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "mocha ./**/*.js",
    "start": "nodemon --inspect=0.0.0.0:9229 server.js",
    "testawesome": "mocha --reporter mochawesome ./**/*.js"
  },
  "keywords": [
    "nodejs"
  ],
  "author": "Isaac Johnson",
  "license": "MIT",
  "devDependencies": {
    "mocha": "^9.2.2"
  },
  "dependencies": {
    "mochawesome": "^7.1.3",
    "ronin-mocks": "^0.1.6",
    "ronin-server": "^0.1.3"
  }
}

I install my missing library

devspace ./app # npm install nodemon

added 81 packages, and audited 313 packages in 6s

37 packages are looking for funding
  run `npm fund` for details

8 vulnerabilities (5 moderate, 3 high)

To address all issues, run:
  npm audit fix

Run `npm audit` for details.

And now start works just fine

devspace ./app # npm start

> nodewithtests@1.0.0 start
> nodemon --inspect=0.0.0.0:9229 server.js

[nodemon] 2.0.18
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node --inspect=0.0.0.0:9229 server.js`
Debugger listening on ws://0.0.0.0:9229/01e942c5-8fe8-47f4-b7b7-56c10ea2ec66
For help, see: https://nodejs.org/en/docs/inspector

I can test locally and indeed see we can hit websockets on the 9229 port

/content/images/2022/07/devspaces-07.png

Adding ports

Because it noticed 9229 for websockets, but not the 8000 port used by server.js, I needed to go and update the devspaces.sh script to add a port

/content/images/2022/07/devspaces-08.png

Now i can see the port in the list at invokation

builder@DESKTOP-QADGF36:~/Workspaces/dockerWithTests2$ devspace dev
info Using namespace 'testingdockerwithtests2'
info Using kube context 'loft-vcluster_my-super-qc1_vcluster-my-super-qc1-pdsjy_loft-cluster'
deploy:app Applying manifests with  kubectl...
deploy:app Render manifests with 'kustomize build ./k8s'
deploy:app service/nginx-run-svc unchanged
deploy:app deployment.apps/my-nginx configured
deploy:app Successfully deployed app with kubectl
dev:app Waiting for pod to become ready...
dev:app Selected my-nginx-devspace-5dd965c5d9-l59rs:my-nginx (pod:container)
dev:app ssh   Port forwarding started on: 11103 -> 8022
dev:app ports Port forwarding started on: 9229 -> 9229, 8000 -> 8000
dev:app proxy Port forwarding started on: 11956 <- 10567
dev:app sync  Sync started on: ./ <-> ./
dev:app sync  Waiting for initial sync to complete
dev:app ssh   Use 'ssh app.dockerwithtests2.devspace' to connect via SSH
dev:app sync  Initial sync completed
dev:app term  Opening shell to my-nginx:my-nginx-devspace-5dd965c5d9-l59rs (pod:container)

and can use it in debugging

/content/images/2022/07/devspaces-09.png

Results

What exactly is this creating?

Inside our Virtual Cluster, we see Devspace created a new namespace and pod

$ kubectl get ns
NAME                      STATUS   AGE
default                   Active   3h24m
kube-node-lease           Active   3h24m
kube-public               Active   3h24m
kube-system               Active   3h24m
testingdockerwithtests2   Active   3h3m

$ kubectl get pods -n testingdockerwithtests2
NAME                                 READY   STATUS    RESTARTS   AGE
my-nginx-devspace-5dd965c5d9-l59rs   1/1     Running   0          179m

It has also created a Nginx Loadbalancer which is attempting to get an external IP

$ kubectl get svc -n testingdockerwithtests2
NAME            TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
nginx-run-svc   LoadBalancer   10.43.107.120   <pending>     80:30407/TCP   3h

If we free up a host or add a node, we can then see the nginx get served an external IP

$ kubectl get svc --all-namespaces
NAMESPACE                 NAME            TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)                  AGE
default                   kubernetes      ClusterIP      10.43.130.208   <none>         443/TCP                  4h3m
kube-system               kube-dns        ClusterIP      10.43.155.248   <none>         53/UDP,53/TCP,9153/TCP   4h3m
testingdockerwithtests2   nginx-run-svc   LoadBalancer   10.43.107.120   192.168.1.57   80:30407/TCP             3h38m

which will server the traffic from our NodePort

/content/images/2022/07/devspaces-10.png

You’ll also notice that in my virtualcluster, the Loadbalancer is used by “testingdockerwithtests2” (using 1.57). But in my main cluster, we see that is really nginx-run-svc-x-testingdockerwithtests2-x-my-super-qc1.

$ kubectl get svc --all-namespaces
NAMESPACE                     NAME                                                       TYPE           CLUSTER-IP      EXTERNAL-IP                  PORT
..,snip...
vcluster-my-super-qc1-pdsjy   my-super-qc1-etcd-headless                                 ClusterIP      None            <none>                       2379/TCP,2380/TCP                                                                                           3h40m
vcluster-my-super-qc1-pdsjy   my-super-qc1                                               ClusterIP      10.43.130.208   <none>                       443/TCP                                                                                                     3h40m
vcluster-my-super-qc1-pdsjy   my-super-qc1-api                                           ClusterIP      10.43.56.248    <none>                       443/TCP                                                                                                     3h40m
vcluster-my-super-qc1-pdsjy   my-super-qc1-etcd                                          ClusterIP      10.43.99.253    <none>                       2379/TCP,2380/TCP                                                                                           3h40m
vcluster-my-super-qc1-pdsjy   kube-dns-x-kube-system-x-my-super-qc1                      ClusterIP      10.43.155.248   <none>                       53/UDP,53/TCP,9153/TCP                                                                                      3h39m
vcluster-my-super-qc1-pdsjy   my-super-qc1-node-anna-macbookair                          ClusterIP      10.43.157.178   <none>                       10250/TCP                                                                                                   3h39m
vcluster-my-super-qc1-pdsjy   my-super-qc1-node-builder-macbookpro2                      ClusterIP      10.43.225.168   <none>                       10250/TCP                                                                                                   3h14m
vcluster-my-super-qc1-pdsjy   nginx-run-svc-x-testingdockerwithtests2-x-my-super-qc1     LoadBalancer   10.43.107.120   192.168.1.57                 80:30407/TCP                                                                                                3h15m
default                       nginx-ingress-release-nginx-ingress                        LoadBalancer   10.43.209.58    192.168.1.159,192.168.1.81   80:30272/TCP,443:30558/

Loft with Users and Teams

Let’s create a Team in Loft for the QC Dept.

Under Users, in the Teams tab, we click “Add Team”

/content/images/2022/07/devspaces-11.png

We can assign groups and users, but for now, we’ll just use a label

/content/images/2022/07/devspaces-12.png

Now that I have my QC Team, let’s add some users. Under the Users tab, click “Add User”

/content/images/2022/07/devspaces-13.png

I’ll add a “QC Bob” user. This user can view most things and is now a member of the QC Team.

/content/images/2022/07/devspaces-14.png

This will invite the user

/content/images/2022/07/devspaces-15.png

While I tried to login with the set password

/content/images/2022/07/devspaces-16.png

In the end, I had to reset it and use the reset link

/content/images/2022/07/devspaces-17.png

By default, QC Bob has no clusters to view

/content/images/2022/07/devspaces-18.png

However, if I go to Edit on the Virtual Cluster and add the QC Team

/content/images/2022/07/devspaces-19.png

Then select the role level (here I’ll use Cluster Admin since I want to assume QC can manage themselves). I’ll set the value and click “Update”

/content/images/2022/07/devspaces-20.png

I had to play with permissions to get it to work. First, adding the team (and then user) to view the primary cluster

/content/images/2022/07/devspaces-21.png

Then I added the user directly as cluster admin (the Team didn’t seem to suffice)

/content/images/2022/07/devspaces-22.png

Now QC Bob can see the VirtualCluster

/content/images/2022/07/devspaces-23.png

Using the Virtual Cluster as a User

On a different host (one without a GUI), I can login with an access key (create under user profile)

$ loft login https://loft.freshbrewed.science --access-key IBasdfasdfasdfsadfasdfasdfasdfasdfsadfasdfasdfasfdasdf69
[done] √ Successfully logged into Loft instance https://loft.freshbrewed.science

Then “use” the Virtual Cluster

$ loft use vcluster

? Please choose a virtual cluster to use
 vCluster: My Super QC1 (my-super-qc1) | Space: vcluster-my-super-qc1-pdsjy | Cluster: loft-cluster
[done] √ Successfully updated kube context to use space vcluster-my-super-qc1-pdsjy in cluster loft-cluster

Then we can see our nodes and namespaces

$ kubectl get nodes --kubeconfig=/home/isaac/.kube/config
NAME                  STATUS   ROLES    AGE   VERSION
anna-macbookair       Ready    <none>   15h   v1.23.5
builder-macbookpro2   Ready    <none>   14h   v1.23.5

$ kubectl get ns --kubeconfig=/home/isaac/.kube/config
NAME                      STATUS   AGE
default                   Active   15h
kube-node-lease           Active   15h
kube-public               Active   15h
kube-system               Active   15h
testingdockerwithtests2   Active   14h

(note: this host I used for test seemed to struggle with competing kubeconfigs so i was explicit with which kubeconfig to use)

Summary

Credit where credit is due; the idea of using Loft.sh and DevSpace was suggested by my colleague Chad Prey back in April.

I think the pairing is quit nice. Between the vCluster access which let’s our users have cluster admin to virtual clusters via Loft and the DevSpace ability to quickly grab ports and open a shell to a debug container, we have a very handy self service system for small organizations. This can also work handily for On-Prem or non SaaS Cloud Kubernetes.

I would argue that if one scales up using Cloud Offerings like AKS, switching to Clusters-per-team with VMSS based node pools and AAD based RBAC would work perhaps a bit better, but at a higher cost.

I should point that while Devspace is a truely Open Source project, Loft.sh is commercial with a solid free version.

The limits of that are just 3 users,2 clusters and 5 vclusters:

/content/images/2022/07/devspaces-24.png

Their plans are $25/user/month (or $240/year)

/content/images/2022/07/devspaces-25.png

In my mind, for small teams, that could scale (10 users would run me $2400/year - the cost of 1 laptop).

devspace loft

Have something to add? Feedback? Try our new forums

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