Dumb OS Apps: Dumbpad, Dumbterm and ISO

Published: Aug 12, 2025 by Isaac Johnson

Today we’ll look at some apps from the DumbWare group. The first is Dumbpad which I first noted from a Marius post some time back. It’s a quick markdown gist sharing app.

Next we’ll checkout Dumbterm which will offer a very nice web-based terminal we can run in Docker or kubernetes.

Lastly, we’ll look at a simple but very elegant Homelab/Bookmark site (landing page), ISO.

Dumbpad

Back in February Marius posted about Dumbpad which bills itself as “a stupid simple, no auth, modern notepad with auto-save”.

Let’s start with a basic docker invokation

$ docker run -p 3443:3000 \
  -v ./data:/app/data \
  dumbwareio/dumbpad:latest

Which works just fine

/content/images/2025/08/dumbpad-01.png

It seems functional, albeit without Mermaid chart support

I noticed it works well for collaboration as the cursor shows other users logged in

/content/images/2025/08/Dumbpad-03.png

I’ll add a title and a pin next

$ docker run -p 3443:3000 -e DUMBPAD_PIN=1234 -e SITE_TITLE="Happy Fun Zone" -v ./data:/app/data
 dumbwareio/dumbpad:latest

> dumbpad@1.0.4 start
> node server.js

we are presented with a pin now at login

/content/images/2025/08/tamped-04.png

We can delete notes as one would expect

/content/images/2025/08/Dumbpad-05.png

We can use light or dark mode and there are some settings like autos timing and showing remote connection messages (useful for presentations I imagine)

/content/images/2025/08/demobbed-06.png

Let’s expose this, it might be useful at times.

I first need an A record

$ az account set --subscription "Pay-As-You-Go" && az network dns record-set a add-record -g idjdnsrg -z tpk.pw -a 75.73.224.240 -n pad
{
  "ARecords": [
    {
      "ipv4Address": "75.73.224.240"
    }
  ],
  "TTL": 3600,
  "etag": "6dce5d31-18c8-49bf-b1f2-5bad6eda4dc8",
  "fqdn": "pad.tpk.pw.",
  "id": "/subscriptions/d955c0ba-13dc-44cf-a29a-8fed74cbb22d/resourceGroups/idjdnsrg/providers/Microsoft.Network/dnszones/tpk.pw/A/pad",
  "name": "pad",
  "provisioningState": "Succeeded",
  "resourceGroup": "idjdnsrg",
  "targetResource": {},
  "trafficManagementProfile": {},
  "type": "Microsoft.Network/dnszones/A"
}

I don’t need to use Docker as we can easily pivot to K8s

$ cat manifest.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dumbpad-data
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: local-path
  resources:
    requests:
      storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dumbpad
spec:
  replicas: 1
  selector:
    matchLabels:
      app: dumbpad
  template:
    metadata:
      labels:
        app: dumbpad
    spec:
      containers:
        - name: dumbpad
          image: dumbwareio/dumbpad:latest
          ports:
            - containerPort: 3000
          env:
            - name: DUMBPAD_PIN
              value: "1234"
            - name: SITE_TITLE
              value: "Happy Fun Zone"
          volumeMounts:
            - name: data
              mountPath: /app/data
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: dumbpad-data
---
apiVersion: v1
kind: Service
metadata:
  name: dumbpad
spec:
  type: ClusterIP
  selector:
    app: dumbpad
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000

$ kubectl apply -f ./manifest.yaml
persistentvolumeclaim/dumbpad-data created
deployment.apps/dumbpad created
service/dumbpad created

I can see the pod running

$ kubectl get po  | grep dumb
dumbpad-f8585b46-crsq8                               1/1     Running     0                46s

I can now create a quick ingress to use that A record

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: azuredns-tpkpw
    ingress.kubernetes.io/ssl-redirect: "true"
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.org/client-max-body-size: "0"
    nginx.org/proxy-connect-timeout: "3600"
    nginx.org/proxy-read-timeout: "3600"
    nginx.org/websocket-services: dumbpad
  name: dumbpad-ingress
spec:
  rules:
  - host: pad.tpk.pw
    http:
      paths:
      - backend:
          service:
            name: dumbpad
            port:
              number: 80
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - pad.tpk.pw
    secretName: dumbpad-tls

I can the apply to create it

$ kubectl apply -f ./manifest.yaml
persistentvolumeclaim/dumbpad-data unchanged
deployment.apps/dumbpad unchanged
service/dumbpad unchanged
Warning: annotation "kubernetes.io/ingress.class" is deprecated, please use 'spec.ingressClassName' instead
ingress.networking.k8s.io/dumbpad-ingress created

When I see the cert created

$ kubectl get cert dumbpad-tls
NAME          READY   SECRET        AGE
dumbpad-tls   True    dumbpad-tls   86s

And it works just fine with the pin 1234

/content/images/2025/08/dumbest-07.png

DumbTerm

That actually got me excited about looking at other apps from Dumbware IO.

The first one I want to try is a Terminal app that could be handy for creating a simple backdoor back into our cluster.

The docker compose is

services:
  dumbterm:
    image: dumbwareio/dumbterm:latest
    container_name: dumbterm
    restart: unless-stopped
    ports:
      - ${DUMBTERM_PORT:-3000}:3000
    volumes:
      - ${DUMBTERM_CONFIG:-./config}:/root/.config
      - ${DUMBTERM_DATA_DIR:-./data}:/root/data
    environment:
      # Container timezone
      TZ: ${DUMBTERM_TZ:-America/Los_Angeles}
      # The title shown in the web interface
      SITE_TITLE: ${DUMBTERM_SITE_TITLE:-DumbTerm}
      # Recommended PIN protection (leave empty to disable)
      DUMBTERM_PIN: ${DUMBTERM_PIN:-1234}
      # The base URL for the application
      BASE_URL: ${DUMBTERM_BASE_URL:-http://localhost:3000}
      ENABLE_STARSHIP: ${ENABLE_STARSHIP:-true}
      LOCKOUT_TIME: ${DUMBTERM_LOCKOUT_TIME:-15} # Minutes
      # Session duration in hours before requiring re-authentication
      MAX_SESSION_AGE: ${DUMBTERM_MAX_SESSION_AGE:-24} # Hours
      # (OPTIONAL) - List of allowed origins for CORS
      # ALLOWED_ORIGINS: ${DUMBTERM_ALLOWED_ORIGINS:-http://localhost:3000}

I think we can pull that into a Kubernetes YAM manifest as before

$ cat term.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dumbterm-config
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: local-path
  resources:
    requests:
      storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dumbterm-data
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: local-path
  resources:
    requests:
      storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dumbterm
spec:
  replicas: 1
  selector:
    matchLabels:
      app: dumbterm
  template:
    metadata:
      labels:
        app: dumbterm
    spec:
      containers:
        - name: dumbterm
          image: dumbwareio/dumbterm:latest
          ports:
            - containerPort: 3000
          env:
            - name: TZ
              value: "America/Chicago"
            - name: SITE_TITLE
              value: "DumbTerm"
            - name: DUMBTERM_PIN
              value: "1234"
            - name: BASE_URL
              value: "dumbterm.tpk.pw"
            - name: ENABLE_STARSHIP
              value: "true"
            - name: LOCKOUT_TIME
              value: "15"
            - name: MAX_SESSION_AGE
              value: "24"
          volumeMounts:
            - name: config
              mountPath: /root/.config
            - name: data
              mountPath: /root/data
      volumes:
        - name: config
          persistentVolumeClaim:
            claimName: dumbterm-config
        - name: data
          persistentVolumeClaim:
            claimName: dumbterm-data
---
apiVersion: v1
kind: Service
metadata:
  name: dumbterm
spec:
  type: ClusterIP
  selector:
    app: dumbterm
  ports:
    - protocol: TCP
      port: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: azuredns-tpkpw
    ingress.kubernetes.io/ssl-redirect: "true"
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.org/client-max-body-size: "0"
    nginx.org/proxy-connect-timeout: "3600"
    nginx.org/proxy-read-timeout: "3600"
    nginx.org/websocket-services: dumbterm
  name: dumbterm-ingress
spec:
  rules:
  - host: dumbterm.tpk.pw
    http:
      paths:
      - backend:
          service:
            name: dumbterm
            port:
              number: 80
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - dumbterm.tpk.pw
    secretName: dumbterm-tls

I just need to first create the A record

$ az account set --subscription "Pay-As-You-Go" && az network dns record-set a add-record -g idjdnsrg -z tpk.pw -a 75.73.224.240 -n dumbterm
{
  "ARecords": [
    {
      "ipv4Address": "75.73.224.240"
    }
  ],
  "TTL": 3600,
  "etag": "a11915d5-d5b6-4d92-bac1-32b9fbdb2caa",
  "fqdn": "dumbterm.tpk.pw.",
  "id": "/subscriptions/d955c0ba-13dc-44cf-a29a-8fed74cbb22d/resourceGroups/idjdnsrg/providers/Microsoft.Network/dnszones/tpk.pw/A/dumbterm",
  "name": "dumbterm",
  "provisioningState": "Succeeded",
  "resourceGroup": "idjdnsrg",
  "targetResource": {},
  "trafficManagementProfile": {},
  "type": "Microsoft.Network/dnszones/A"
}

Then apply

$ kubectl apply -f ./term.yaml
persistentvolumeclaim/dumbterm-config created
persistentvolumeclaim/dumbterm-data created
deployment.apps/dumbterm created
service/dumbterm created
Warning: annotation "kubernetes.io/ingress.class" is deprecated, please use 'spec.ingressClassName' instead
ingress.networking.k8s.io/dumbterm-ingress created

Once I see the cert is ready

$ kubectl get cert dumbterm-tls
NAME           READY   SECRET         AGE
dumbterm-tls   False   dumbterm-tls   29s
$ kubectl get cert dumbterm-tls
NAME           READY   SECRET         AGE
dumbterm-tls   True    dumbterm-tls   2m3s

I can test… looks like something went wrong

/content/images/2025/08/Dumbpad-08.png

The pod is crashing and the logs suggest my URL is not right

$ kubectl logs dumbterm-76b886c644-gg8ml
Starship initialization completed!

> dumbterm@1.1.1 start
> npm run copy-xterm && node server.js


> dumbterm@1.1.1 copy-xterm
> node scripts/copy-xterm.js

Starting to copy xterm files...
Successfully copied all xterm files
node:internal/url:806
    const href = bindingUrl.parse(input, base, raiseException);
                            ^

TypeError: Invalid URL
    at new URL (node:internal/url:806:29)
    at Object.<anonymous> (/app/scripts/pwa-manifest-generator.js:5:42)
    at Module._compile (node:internal/modules/cjs/loader:1529:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1613:10)
    at Module.load (node:internal/modules/cjs/loader:1275:32)
    at Module._load (node:internal/modules/cjs/loader:1096:12)
    at Module.require (node:internal/modules/cjs/loader:1298:19)
    at require (node:internal/modules/helpers:182:18)
    at Object.<anonymous> (/app/server.js:12:33)
    at Module._compile (node:internal/modules/cjs/loader:1529:14) {
  code: 'ERR_INVALID_URL',
  input: 'dumbterm.tpk.pw'
}

Node.js v20.19.2
npm notice
npm notice New major version of npm available! 10.8.2 -> 11.5.2
npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.5.2

I’ll try using https://dumbterm.tpk.pw instead and reapplying the manifest

$ kubectl apply -f term.yaml
persistentvolumeclaim/dumbterm-config unchanged
persistentvolumeclaim/dumbterm-data unchanged
deployment.apps/dumbterm configured
service/dumbterm unchanged
ingress.networking.k8s.io/dumbterm-ingress unchanged

That worked! And after entering a PIN, it seems launch just fine

/content/images/2025/08/dumbpad-09.png

I’ll try using it and installing a more graphical terminal app like btop

Even though we can share a container, we do not share sessions

I was about to say that a 4 digit PIN would just not suffice, but I tried kicking it up to a lot more and it worked

/content/images/2025/08/dumbpad-10.png

While using letters wont crash the pod

            - name: DUMBTERM_PIN
              value: "Password1"

The JavaScript login page will only accept numbers so there is no way to type it in.

However, even on pod restart, I can see where left off in my session

/content/images/2025/08/dumbpad-13.png

ISO dashboard

A few weeks ago I came across ISO which is a nice Open-Source home lab dashboard by Tim Ritter

Let’s give it a try locally first

$ docker run -d \
 -p 3000:3000 \
>   --name iso \
>   -p 3000:3000 \
>   -e AUTH_SECRET="changeme" \
>   -e AUTH_PASSWORD="changeme" \
>   -v ./config:/config \
>   coyann/iso
Unable to find image 'coyann/iso:latest' locally
latest: Pulling from coyann/iso
c19952135643: Pull complete
159d19db919f: Pull complete
b9102aedc8d2: Pull complete
395992a1c37a: Pull complete
a66ada507b72: Pull complete
c5e91758c40d: Pull complete
83194e48f987: Pull complete
28d7da746fa2: Pull complete
2a20028bb2dd: Pull complete
ea2035f268a1: Pull complete
f36b5c28d7a4: Pull complete
e63215e643e4: Pull complete
986ad2432f4c: Pull complete
Digest: sha256:d0bb9816edb7a9c6658c597d30272fcd802caf9816b1fca2846b750e479f9c6a
Status: Downloaded newer image for coyann/iso:latest
ce51fc15633f046934a54babb3fc4fef8e1ca6b30318f56cc6616b3397a57036

It is certainly good looking

/content/images/2025/08/dumbpad-14.png

We have to login and use “changeme” to change any settings

/content/images/2025/08/iso-02.png

Edit lets me just remove the icons. Then I can add them back with the URL I desire

/content/images/2025/08/iso-03.png

I only put in a handful, but it would be a nice dashboard to use - feels very legacy iMac to me.

/content/images/2025/08/iso-04.png

I’m not certain I’m ready to make a big landing page for global sharing, however.

That said, for those that might want to fire this up in Kubernetes:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: iso-config
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: local-path
  resources:
    requests:
      storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: iso
spec:
  replicas: 1
  selector:
    matchLabels:
      app: iso
  template:
    metadata:
      labels:
        app: iso
    spec:
      containers:
        - name: iso
          image: coyann/iso:latest
          ports:
            - containerPort: 3000
          env:
            - name: AUTH_SECRET
              value: "changeme"
            - name: AUTH_PASSWORD
              value: "changeme"
          volumeMounts:
            - name: config
              mountPath: /config
      volumes:
        - name: config
          persistentVolumeClaim:
            claimName: iso-config
---
apiVersion: v1
kind: Service
metadata:
  name: iso
spec:
  type: ClusterIP
  selector:
    app: iso
  ports:
    - protocol: TCP
      port: 80
      targetPort:

Summary

Today we looked at a couple excellent Open-Source apps from the folks at Dumbware.io. The first was Dumbpad which was easy to launch in Docker and Kubernetes.

We can see it fired up at https://pad.tpk.pw/ with password 1234.

The other we explored was Dumbterm which creates a terminal pod in our cluster. Since this could be used to do some rather bad things, I did leave a private PIN on my deployed version. That said, you can use the pin 1234 on their demo version at https://dumbterm.dumbware.io/.

They have a lot more to check out some time like Dumbdo and Dumbkan amongst others.

The other app we took a look at was ISO which is a very elegant small HomeLab dashboard app. Though, I would argue this can just be a nice bookmark site for anyone to use - you can really use any URL you want on those icons. However, I’m still not certain I want to create a public ISO dashboard just yet.

Btw, I have to give credit to Copilot for nailing the ask for my background image.

In case you want the full size:

/content/images/2025/08/Copilot_20250804_155331.png

Dumbpad Dumbterm ISO docker kubernetes

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