Skip to content

Repository files navigation

tinycode-operator

CILicense: MITRelease

An OpenShift Operator that installs and manages tinycode AI coding assistant instances on an OpenShift cluster.

Overview

The operator watches TinycodeInstance custom resources and reconciles them by:

  1. Selecting the appropriate SecurityContextConstraint based on spec
  2. Running helm upgrade --install with values derived from the CR
  3. Creating an OpenShift Route for external access
  4. Updating CR status with the URL and ready condition

Architecture

┌─────────────────────────────────────────────────────────┐
│ OpenShift Cluster │
│ │
│ tinycode-operator-system/ │
│ ├── Deployment: tinycode-operator-manager (UID 1001) │
│ └── ServiceAccount: tinycode-operator-manager │
│ │
│ <user-namespace>/ │
│ ├── TinycodeInstance CR (user creates) │
│ ├── Deployment: <name>-tinycode ──→ Pod (UID 1001) │
│ ├── Service: <name>-tinycode │
│ ├── Route: <name>-tinycode ──→ https://<host> │
│ ├── PVC: <name>-data (1Gi) │
│ └── PVC: <name>-projects (10Gi default) │
│ │
│ Cluster-scoped: │
│ ├── CRD: tinycodeinstances.tinycode.dev │
│ ├── SCC: tinycode-restricted (default) │
│ ├── SCC: tinycode-hostpath (optional) │
│ └── SCC: tinycode-shell (optional, requires review) │
└─────────────────────────────────────────────────────────┘

Prerequisites

  • OpenShift 4.12+ (OCP, ROSA, ARO)
  • cluster-admin access for installation
  • oc CLI and helm 3.x in PATH
  • Operator image built and pushed to a registry accessible to the cluster

Installation

# 1. Clone the operator repo
git clone https://github.com/bobbyjohnstx/tinycode-operator
cd tinycode-operator
# 2. Build and push the operator image
make push IMAGE_ORG=yourorg IMAGE_TAG=v0.1.0
# 3. Install on the cluster (cluster-admin required)
make install OPERATOR_IMAGE=quay.io/yourorg/operator:v0.1.0

Namespace Preparation (Required)

Before creating a TinycodeInstance, a cluster-admin must prepare the target namespace. The operator uses Helm to create Deployments, Services, Routes, PVCs, Roles, and RoleBindings in the target namespace — this requires admin-level access (not just edit, which cannot create RBAC resources).

# 1. Create the target namespace
oc new-project tinycode-dev
# 2. Grant the operator admin in this namespace (cluster-admin required)
oc create rolebinding tinycode-operator-admin \
--clusterrole=admin \
--serviceaccount=tinycode-operator-system:tinycode-operator-manager \
-n tinycode-dev
# 3. Create a password secret for the tinycode web UI
oc create secret generic tinycode-password \
--from-literal=TINYCODE_SERVER_PASSWORD=<your-password> \
-n tinycode-dev

Repeat for each namespace where tinycode instances will be deployed. The admin ClusterRole is namespace-scoped — it does not grant cluster-wide privileges.

Why admin and not edit? The Helm chart creates Roles and RoleBindings for Kubernetes service discovery (finding vLLM endpoints). The edit ClusterRole cannot create RBAC resources; admin adds get/create/update/delete on Roles and RoleBindings.

Environments where cluster-admin is unavailable: If your organization restricts cluster-admin access, request that an administrator run steps 1-3 above for your namespace. The operator itself (installed separately by cluster-admin) handles everything else. Alternatively, install via OLM where the subscription handles RBAC automatically — see docs/olm-bundle.md.

Cross-Namespace Discovery Setup (Optional)

If your vLLM models run in a different namespace than tinycode (common on RHOAI), the operator needs to create ClusterRoles for cross-namespace service listing. The hack/install.sh script handles this automatically via config/rbac/discovery_role.yaml.

To enable discovery for a model service:

# 1. Annotate the model's predictor service (tells tinycode to probe it)
oc annotate svc <predictor-service-name> \
tinycode.dev/discover=vllm \
-n <model-namespace># 2. Add discovery.namespaces to your TinycodeInstance CR# spec:# discovery:# namespaces:# - <model-namespace>

Without the annotation, tinycode ignores the service even if the namespace is listed — this prevents probing every service in the cluster.

Creating a TinycodeInstance

Basic (PVC storage)

# Apply the CR (namespace must be prepared first — see above)
oc apply -f config/samples/tinycode_v1alpha1_basic.yaml
# Get the URL
oc get tinycodeinstance my-tinycode -n tinycode-dev -o jsonpath='{.status.url}'

With Ollama (local LLM)

Deploy Ollama separately (or use an existing instance), then set spec.ollama.host:

spec:
ollama:
host: "http://ollama.ollama-system.svc.cluster.local:11434"

With Host Filesystem Access

spec:
storage:
hostPath:
path: /home/developer/projectsreadOnly: false

Security: Requires cluster-admin to pre-approve the tinycode-hostpath SCC binding. The operator will bind it automatically if you have permission.

With Host Shell Execution

spec:
shell:
enabled: true

Security: Requires cluster-admin review. Grants hostPID which allows the tinycode shell tool to run commands on the host via nsenter. Only use in dedicated namespaces with trusted users.

SecurityContextConstraints

Three SCCs are installed, applied automatically based on spec:

SCCWhen UsedhostPathhostPIDCaps
tinycode-restrictedDefaultnonoALL dropped
tinycode-hostpathspec.storage.hostPath setyesnoALL dropped
tinycode-shellspec.shell.enabled=truenoyesSYS_PTRACE only

All SCCs run as UID 1001 (non-root), GID 0, with allowPrivilegedContainer: false.

TinycodeInstance Spec Reference

FieldTypeDefaultDescription
spec.imagestringquay.io/bjohns/tinycode-container:latestContainer image
spec.replicasinteger1Number of pods (1–10)
spec.resources.limits.cpustring2CPU limit
spec.resources.limits.memorystring2GiMemory limit
spec.resources.requests.cpustring200mCPU request
spec.resources.requests.memorystring512MiMemory request
spec.storage.dataSizestring1GiPVC size for SQLite DB and config
spec.storage.projectsSizestring10GiPVC size for project workspace
spec.storage.projectsAccessModestringReadWriteOnceAccess mode for projects PVC (ReadWriteOnce or ReadWriteMany for multi-replica shared workspaces)
spec.storage.storageClassNamestringcluster defaultStorageClass for PVCs
spec.storage.hostPath.pathstringAbsolute path on host node to mount at /projects (mutually exclusive with spec.git.url)
spec.storage.hostPath.readOnlyboolfalseMount host path as read-only
spec.hostnamestringautoCustom hostname for the tinycode Route
spec.tlsTerminationstringedgeRoute TLS mode: edge, passthrough, or reencrypt
spec.ollama.enabledboolfalseDeploy an Ollama sidecar
spec.ollama.hoststringExternal Ollama host URL (when enabled is false)
spec.ollama.modelsarrayOllama model names to pre-pull on startup
spec.auth.passwordSecretstringSecret name containing TINYCODE_SERVER_PASSWORD
spec.shell.enabledboolfalseEnable host shell access (grants hostPID for nsenter-based commands)
spec.shell.allowedCommandsarrayRestrict shell commands (future admission webhook enforcement)
spec.nodeSelectorobjectNode selection constraints
spec.tolerationsarrayPod tolerations
spec.modelstringDefault model ID (e.g., qwen/Qwen2.5-Coder-32B-Instruct-AWQ). Written to generated config.
spec.clusterAdmin.enabledboolfalseEnable cluster-admin mode (mounts kubeconfig, downloads oc CLI)
spec.clusterAdmin.kubeconfigSecretNamestringSecret name containing kubeconfig (required when enabled=true)
spec.clusterAdmin.kubeconfigSecretKeystringkubeconfigKey within the Secret containing the kubeconfig file
spec.clusterAdmin.ocVersionstringstableoc CLI version (e.g., 4.17 for reproducibility)
spec.clusterAdmin.kubeconfigNamespacestringNamespace where kubeconfig Secret resides (for cross-namespace mounting)
spec.clusterAdmin.clusterRolestringAuto-provision ServiceAccount with this ClusterRole (cannot be admin or cluster-admin)
spec.vllmarrayArray of vLLM endpoints to configure as tinycode providers
spec.vllm[].namestringProvider name (must be unique, lowercase alphanumeric + dashes)
spec.vllm[].urlstringBase URL of vLLM instance (e.g., http://vllm-qwen.vllm:8000)
spec.vllm[].modelsobjectPer-model overrides with contextLimit and outputLimit (auto-probed if omitted)
spec.discovery.namespacesarrayNamespaces to search for vLLM services (enables cross-namespace discovery)
spec.git.urlstringGit repository URL to clone into /projects (validated against URL scheme; mutually exclusive with spec.storage.hostPath.path)
spec.git.branchstringBranch to clone (validated to prevent injection); defaults to repository's default branch
spec.git.credentialsSecretstringSecret name with git credentials (keys: username/password for HTTPS, ssh-privatekey for SSH)
spec.git.pullOnRestartboolfalsePull latest changes from repository on pod restart
spec.git.depthinteger1Clone depth (shallow clone by default)

CRD Security Constraints

The TinycodeInstance CRD enforces validation patterns for security-sensitive fields:

  • Image Registry Restriction (spec.image): Must be from explicitly allowed registries (defaults to quay.io, ghcr.io). Prevents arbitrary image injection.
  • Git URL Validation (spec.git.url): Validated to ensure proper URL format. Allowed schemes: http://, https://, ssh://, git://. HTTPS recommended for credential safety. Prevents command injection via shell metacharacter blocking.
  • Git Branch Validation (spec.git.branch): Alphanumeric, /, -, ., and _ only; prevents shell injection during clone operations.
  • ClusterRole Allowlist (spec.clusterAdmin.clusterRole): Cannot be admin, cluster-admin, or any ClusterRole that would escalate privileges. Prevents privilege escalation in cluster-admin mode.
  • SSRF Prevention (spec.vllm[].url): URL validation prevents internal service discovery attacks. Private IP ranges and localhost are allowed by default but can be restricted via NetworkPolicy.

Security Features

  • NetworkPolicy: Recommended to restrict ingress/egress traffic to required services
  • Read-Only Root Filesystem: Can be enabled in PodSecurityPolicy via readOnlyRootFilesystem: true for additional hardening
  • Security Context: All pods run as UID 1001 (non-root) with dropped Linux capabilities (ALL dropped by default)
  • Audit Logging: Operator actions are logged to cluster audit logs for compliance tracking

Status

oc get tinycodeinstances -n tinycode-dev
NAME READY URL AGE
my-tinycode True https://my-tinycode-tinycode-dev... 5m

Status phases: PendingDeployingRunning (or Failed / Terminating).

Uninstall

make uninstall

Deploying Without the Operator

The tinycode-operator is OpenShift-specific: it creates OpenShift Route objects (not standard Ingress) and manages SecurityContextConstraints (SCCs), which are OpenShift-only resources. However, the underlying container image (tinycode-container) is portable and runs on any Kubernetes cluster.

Option 1: Raw Kustomize Manifests

The tinycode-container repo includes Kustomize manifests for vanilla Kubernetes:

  • k8s/base/ — Deployment, Service, PVC (works on any cluster)
  • k8s/overlays/ingress/ — Adds a standard Ingress instead of OpenShift Route

Deploy with:

# Clone the container repo
git clone https://github.com/bobbyjohnstx/tinycode-container
cd tinycode-container
# Deploy base resources + standard Ingress
kubectl apply -k k8s/overlays/ingress

This approach is lightweight and requires no CRD or operator installation. You manage Kustomize overlays directly for customization.

Option 2: Tekton + Argo CD

For a fully Kubernetes-native CI/CD pipeline that replaces both the operator and GitHub Actions:

  • Tekton — Replaces GitHub Actions. Builds the container image from the ContainerFile and pushes to your registry, triggered by git pushes.
  • Argo CD — Replaces the operator's deployment and reconciliation. Watches the Kustomize manifests (or a Helm chart) in the tinycode-container repo and auto-syncs changes to the cluster.

Together, Tekton + Argo CD cover everything the operator + GitHub Actions do today. The tradeoff: you lose the TinycodeInstance CRD abstraction and instead manage Kustomize overlays or Helm values directly — arguably simpler for single-instance deployments.

Future Enhancement

Making the operator itself Kubernetes-portable (auto-detecting OpenShift vs vanilla Kubernetes and falling back to Ingress when SCCs are unavailable) is a potential enhancement for future releases.

Multi-User Self-Service Provisioning

Primary deployment model: One TinycodeInstance CR per user, managed centrally by a cluster-admin in the "Creating a TinycodeInstance" section above.

Alternative for self-service teams: Users can provision their own TinycodeInstance CRs if your team prefers a self-service model. No code changes to the operator are required — it already watches all namespaces and scopes all resources (Deployments, Services, Routes, PVCs, ServiceAccounts, SCC bindings) by CR name + namespace. Two users in different namespaces get completely isolated resources with no collisions.

Setup (Cluster-Admin, One-Time)

  1. Create a ClusterRole granting users permission to manage their own TinycodeInstance CRs:
---
apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata:
name: tinycode-user-rolerules:
- apiGroups:
- tinycode.devresources:
- tinycodeinstancesverbs:
- create
- get
- list
- watch
- delete

Save this as tinycode-user-role.yaml and apply once:

oc apply -f tinycode-user-role.yaml

Per-User Setup (Cluster-Admin or Delegated)

For each user who will self-provision, create a namespace and bind the ClusterRole:

---
apiVersion: v1kind: Namespacemetadata:
name: tinycode-alice
---
apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata:
name: alice-tinycode-usernamespace: tinycode-aliceroleRef:
apiGroup: rbac.authorization.k8s.iokind: ClusterRolename: tinycode-user-rolesubjects:
- kind: Username: alice@example.comapiGroup: rbac.authorization.k8s.io

Save as alice-tinycode-rolebinding.yaml and apply:

oc apply -f alice-tinycode-rolebinding.yaml

The user also needs to create Secrets (password) in their namespace. Grant that permission by adding a namespace Role:

---
apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata:
name: tinycode-secret-creatornamespace: tinycode-alicerules:
- apiGroups:
- ""resources:
- secretsverbs:
- create
- get
---
apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata:
name: alice-secret-creatornamespace: tinycode-aliceroleRef:
apiGroup: rbac.authorization.k8s.iokind: Rolename: tinycode-secret-creatorsubjects:
- kind: Username: alice@example.comapiGroup: rbac.authorization.k8s.io

User Workflow (Self-Service)

Once the cluster-admin has set up the namespace and RoleBinding, the user can:

# 1. Create a password secret in their namespace
oc create secret generic tinycode-password \
--from-literal=TINYCODE_SERVER_PASSWORD=mypassword \
-n tinycode-alice
# 2. Create their TinycodeInstance CR
oc apply -f - <<EOF---apiVersion: tinycode.dev/v1alpha1kind: TinycodeInstancemetadata: name: alice-dev namespace: tinycode-alicespec: image: quay.io/bjohns/tinycode-container:latest replicas: 1 resources: limits: cpu: "2" memory: "2Gi" requests: cpu: "200m" memory: "512Mi" storage: dataSize: "1Gi" projectsSize: "10Gi" auth: passwordSecret: tinycode-password tlsTermination: edgeEOF# 3. Wait for the instance to be ready
oc get tinycodeinstance alice-dev -n tinycode-alice -w
# 4. Get the URL
oc get tinycodeinstance alice-dev -n tinycode-alice -o jsonpath='{.status.url}'

Resource Consumption

Each user gets their own pod, PVCs (data and projects), and OpenShift Route. This is instance-per-user isolation, not shared multi-tenancy. Resource consumption scales linearly with users:

  • Default limits: 2 CPU, 2Gi memory per instance
  • Default requests: 200m CPU, 512Mi memory per instance
  • Storage: 1Gi (config/DB) + 10Gi (projects) per instance

For 10 users with default settings, the cluster needs capacity for at least 2 CPU cores and 5Gi memory across all tinycode instances, plus infrastructure and other workloads.

Cleanup

A user can delete their instance:

oc delete tinycodeinstance alice-dev -n tinycode-alice

Cluster-admin can revoke access by removing the RoleBinding:

oc delete rolebinding alice-tinycode-user -n tinycode-alice

Project Structure

tinycode-operator/
├── Dockerfile # Operator container image
├── Makefile # Build/install targets
├── README.md
├── CONTAINER.md # Container interface contract
├── operator/
│ ├── main.py # Operator controller (kopf)
│ └── requirements.txt
├── helm-charts/
│ └── tinycode/ # Helm chart deployed per CR
│ ├── Chart.yaml
│ ├── values.yaml
│ └── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── route.yaml
│ ├── serviceaccount.yaml
│ └── pvc.yaml
├── config/
│ ├── crd/ # CRD definition
│ ├── rbac/ # Operator RBAC
│ ├── scc/ # SecurityContextConstraints
│ ├── manager/ # Operator Deployment
│ └── samples/ # Example CRs
├── bundle/
│ └── manifests/ # OLM ClusterServiceVersion
└── hack/
├── install.sh # Cluster install script
├── uninstall.sh # Cluster uninstall script
└── build-push.sh # Image build/push script

Ecosystem

ProjectDescriptionRepository
tinycodeCore AI coding assistant — server, TUI, web UIgithub.com/bobbyjohnstx/tinycode
tinycode-containerContainer image packaging tinycode + oh-my-tinygithub.com/bobbyjohnstx/tinycode-container

License

MIT — see LICENSE.

About

Kubernetes Operator for managing tinycode instances on OpenShift. Declarative vLLM config, cross-namespace discovery, GitOps, shared workspaces.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages