cloudtty: web terminals as a Kubernetes operator
A Friendly Kubernetes CloudShell (Web Terminal) !
At a glance
- What is it?
- cloudtty is a Go operator that turns a terminal into a Kubernetes resource. It runs ttyd in a pod, creates it from a CloudShell custom resource, and exposes it through NodePort, ClusterIP, Ingress or an Istio VirtualService. The interesting parts are the custom image path and the Kylin based build.
- Who is it for?
- Adopt cloudtty if you run Kubernetes and need a browser based shell for operators who cannot reach nodes over SSH, and you are prepared to operate a CRD and an operator alongside your cluster tooling. Skip it if a single user needs a one-off terminal, since ttyd on its own is lighter, or if you cannot run an operator, since cloudtty is an operator and not a sidecar proxy.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 8 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A terminal treated as a Kubernetes resource
cloudtty is described as an easy-to-use operator to run a web terminal and cloud shell intended for a Kubernetes native environment, and the point of it is that the terminal is not a sidecar process you attach to a pod. It is a resource, created from a custom resource definition and reconciled by a controller running in the cluster.
The terminology in the README explains the naming. cloudtty refers to a virtual console, shell or terminal running on web and in the cloud, and it is used anywhere with an internet connection, automatically connected to the cloud. The TTY part of the name comes from teleprinters and teletypewriters, which the README notes were the origin of the term, and which has since come to mean a text-only console even though that console is now virtual rather than physical.
The project is written in Go, the licence is MIT, and the default branch is main. It carries a CII Best Practices badge and is listed as a Cloud Native Computing Foundation project, which is a reasonable signal for an infrastructure component. The homepage is cloudtty.github.io/cloudtty, and the README is bilingual, with a Simplified Chinese version alongside the English one.
The API surface is a single custom resource, CloudShell, in the group cloudshell.cloudtty.io at version v1alpha1, with a spec that carries an image and a secretRef, plus the operator that watches it and creates the underlying pod and service.
Why the project exists next to ttyd
The README is direct about the motivation. The project ttyd already provides the feature of sharing terminals over the web, and cloudtty is not competing with that. If you use Kubernetes, the README argues, a more cloud-native environment is required to run the web tty the Kubernetes way, running as a pod and generated by CRDs.
That is a narrow and defensible scope. ttyd is a single process you can point at a terminal and share it over the web. cloudtty wraps that process in the machinery a Kubernetes platform already has: a chart, an operator, a custom resource, a status field with the URL, and exposure modes that plug into whatever routing the cluster uses.
The practical difference shows in the operational surface. With ttyd you have a container, a Service, and something to keep it running. With cloudtty you have a declarative resource, a controller, and a status you can watch. The second is heavier and it buys you reconciliation, status reporting and a place in the cluster's own vocabulary.
The three scenarios the README lists are worth repeating, because they define who this is for. Enterprises that manage Kubernetes through a cloud platform often cannot use SSH to connect to node hosts to run kubectl commands, and need a cloud shell capability instead. A running container can be entered, via kubectl exec, from a browser page. And container logs can be displayed in real time, scrolling, in the browser.
The second and third of those are really features of ttyd in a pod. The first is the one that needs an operator.
Installing the operator and creating a CloudShell
The quick start has three steps, and the first is a Helm install of the operator with its CRDs:
helm repo add cloudtty https://cloudtty.github.io/cloudtty
helm repo update
helm install cloudtty-operator --version 0.5.0 cloudtty/cloudttyThe operator deployment is then waited on until it reports available:
kubectl wait deployment cloudtty-operator-controller-manager --for=condition=Available=TrueStep two applies a sample custom resource from the repository:
kubectl apply -f https://raw.githubusercontent.com/cloudtty/cloudtty/v0.5.0/config/samples/local_cluster_v1alpha1_cloudshell.yamlBy default this creates a cloudtty pod and exposes a NodePort service. ClusterIP, Ingress and VirtualService, for Istio, are all supported as exposureMode values, with more examples in config/samples/.
Step three is reading the status, where the URL appears:
kubectl get cloudshell -wThe output the README shows lists the name, the user, the command, the type, the URL, the phase and the age, with entries like cloudshell-sample at a NodePort address and a Ready phase. Once the status is Ready and the URL field is populated, you paste that URL into a browser and land in a shell with kubectl against the cluster.
One detail to notice in the sample resource is the secretRef field, which names a kubeconfig secret. That is how the shell gets its cluster credentials, and it is the part of the spec most worth reviewing before you deploy it.
Two ways to point at a custom image
Most users need more than the basic kubectl tooling, and the README shows how to extend the base image with a Dockerfile example that already adds the karmadactl tool:
FROM ghcr.io/cloudtty/cloudshell:v0.6.0
RUN curl -fsSLO https://github.com/karmada-io/karmada/releases/download/v1.2.0/kubectl-karmada-linux-amd64.tgz \
&& tar -zxf kubectl-karmada-linux-amd64.tgz \
&& chmod +x kubectl-karmada \
&& mv kubectl-karmada /usr/local/bin/kubectl-karmada \
&& which kubectl-karmada
ENTRYPOINT ttydThe ENTRYPOINT line is the informative part, because it shows that the shell container is a ttyd process with your tooling layered on top of it.
The image is then built with:
docker build -t <IMAGE> . -f docker/Dockerfile.exampleAnd there are two ways to make the operator use it. The first is the spec.image field of the custom resource:
apiVersion: cloudshell.cloudtty.io/v1alpha1
kind: CloudShell
metadata:
name: cloudshell-sample
spec:
secretRef:
name: "my-kubeconfig"
image: ghcr.io/cloudtty/customize_cloudshell:latestThe second is the JobTemplate image parameter at install time:
helm install cloudtty-operator --version 0.5.0 cloudtty/cloudtty --set jobTemplate.image.registry=</REGISTRY> --set jobTemplate.image.repository=</REPOSITORY> --set jobTemplate.image.tag=</TAG>The README also notes that on an existing installation you can edit the JobTemplate configMap instead, which is the practical route when the operator is already deployed and the Helm values are not handy.
A Kylin based image for domestic operating systems
The repository ships an official image built on Kylin Server Minimal V10 SP3, with its Dockerfile at docker/cloudshell/Dockerfile.kylin. The README explains the motivation, environments that require a domestic operating system runtime, and the constraint that matters technically: the rz and sz commands are linked only against Kylin glibc.
The image keeps the full CloudShell toolchain, which the README lists as helm, kubectl, yq, ttyd, rz/sz, git, SSH, jq and bash completion. The rz and sz pair is the file transfer mechanism, and linking it against one specific glibc is exactly the kind of detail that breaks when you swap the base image, so the constraint is not incidental.
The build is automated by the Build and Release Image pipeline, with the rules spelled out: a push to main produces ghcr.io/cloudtty/cloudshell-kylin:latest, a version tag produces the same repository with the tag, pull requests that touch the Kylin Dockerfile get a build-only validation, and the workflow can also be triggered manually from the Actions interface.
Locally, the image is built with a Makefile target or straight Docker:
make docker-build-kylin KYLIN_TTY_IMG=<IMAGE>
# or
docker build -t <IMAGE> . -f docker/cloudshell/Dockerfile.kylinAnd it is consumed like any other custom image, through spec.image:
apiVersion: cloudshell.cloudtty.io/v1alpha1
kind: CloudShell
metadata:
name: cloudshell-sample
spec:
image: ghcr.io/cloudtty/cloudshell-kylin:latestThe two architectures, linux/amd64 and linux/arm64, are built by the same pipeline.
The Go module behind the operator
The go.mod shows what the operator is made of. The module is github.com/cloudtty/cloudtty and it targets go 1.21.
The direct dependencies are the Kubernetes controller stack and two adjacent ecosystems. The k8s.io modules are pinned to v0.28.2 across api, apimachinery, apiserver, cli-runtime, client-go, code-generator, component-base, klog and kubectl, and the controller framework is sigs.k8s.io/controller-runtime at v0.14.6. The command line is built with github.com/spf13/cobra, errors are wrapped with github.com/pkg/errors, and the test stack is ginkgo with gomega.
The Istio entries are the interesting dependency for a project that advertises a VirtualService exposure mode: istio.io/api and istio.io/client-go at v1.19.3, so the Istio resources are typed rather than assembled as unstructured YAML.
The repository layout is conventional for a controller project: cmd/ for the binaries, pkg/ for the packages, charts/ for the Helm chart, config/ for the CRDs and samples, docker/ for the image Dockerfiles, html/ for the web assets, hack/ for build scripts, test/ for tests, vendor/ for the vendored dependencies, and docs/ for the documentation. The Makefile derives the image names from the git description, with ghcr.io/cloudtty/cloudshell-operator and ghcr.io/cloudtty/cloudshell as the two targets, plus the Kylin variant, and it points at kubebuilder envtest assets for ENVTEST_K8S_VERSION 1.23.
Golang linting is configured through .golangci.yml, and the project carries the usual governance files, CODE_OF_CONDUCT.md, CONTRIBUTING.md, MAINTAINERS.md and OWNERS.
Release cadence and the version gap in the docs
The releases are tagged with a prefix, cloudtty-0.8.10 published on 2026-09-18, cloudtty-0.8.9 on 2026-01-27 and cloudtty-0.8.8 on 2025-11-17. The last push to the repository was on 2026-10-15.
That sequence points to a maintenance release in the 0.8 line with irregular spacing, which is what you would expect from a project whose releases follow Kubernetes API versions and Istio client versions rather than a calendar.
There is also a version inconsistency inside the documentation that is worth knowing about before you copy a command. The Helm install and the sample resource both reference version 0.5.0, and the sample URL points at the v0.5.0 tag, while the custom image example is based on ghcr.io/cloudtty/cloudshell:v0.6.0. The release list on the repository is in the 0.8 line. In other words, the quick start in the README is pinned to an older operator release than the newest tag, and if you follow it literally you are installing 0.5.0 and then discovering what the current line contains.
For an adoption decision that matters more than it first appears. The CRD apiVersion is a contract, and changing the operator version changes which CRD shape the cluster has, so reading the release notes for the version you intend to install is not optional. Pin the operator version and the image version separately, and check that the CRD in the cluster matches the resource you apply.
The licence is MIT, so the code is easy to take in-house, and the CNCF listing means the project accepts the kind of contribution process that listing implies.
Editorial conclusion
Adopt cloudtty if you run Kubernetes and need a browser based shell for operators who cannot reach nodes over SSH, and you are prepared to operate a CRD and an operator alongside your cluster tooling. Skip it if a single user needs a one-off terminal, since ttyd on its own is lighter, or if you cannot run an operator, since cloudtty is an operator and not a sidecar proxy. Before you install, check the CRD apiVersion cloudshell.cloudtty.io/v1alpha1 against your cluster policy for custom resources, decide which exposureMode you want, and read the spec.secretRef requirements, because the shell gets cluster access through a kubeconfig reference. The last push was on 2026-10-15 and the newest release is cloudtty-0.8.10 from 2026-09-18.
Frequently asked questions
How do I install the cloudtty operator?
With Helm, by adding the cloudtty chart repository and installing the operator, then waiting for the controller manager deployment to become available. The README's quick start uses helm install cloudtty-operator --version 0.5.0 cloudtty/cloudtty.
What is the difference between cloudtty and ttyd?
The README says ttyd already provides sharing terminals over the web, and that a Kubernetes-native way to run the web tty, as a pod generated by CRDs, is what cloudtty adds. cloudtty is the operator and custom resource layer around the terminal.
How do I use a custom container image with cloudtty?
Either set the spec.image field of the CloudShell custom resource, or set the JobTemplate image registry, repository and tag when installing the operator with Helm. On an existing installation you can modify the JobTemplate configMap.
What does the cloudtty Kylin image provide?
It is built on Kylin Server Minimal V10 SP3 and keeps the CloudShell toolchain, which the README lists as helm, kubectl, yq, ttyd, rz/sz, git, SSH, jq and bash completion, with rz and sz linked only against Kylin glibc. It is published for linux/amd64 and linux/arm64.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/cloudtty-cloudtty)Was this useful?