Skip to main content
Sinas ships a Helm chart at charts/sinas that deploys the full platform — backend, queue workers, scheduler, CDC worker, console — plus bundled PostgreSQL, Redis, and ClickHouse. Untrusted code (untrusted functions and agent code execution) runs in ephemeral hardened Pods: one pod per execution, created through the Kubernetes API and deleted afterwards. No Docker socket, no privileged Docker-in-Docker. Admin-approved (shared_pool) functions run in-process in the queue workers. This works on any conformant cluster — kind, k3s, Scaleway Kapsule, GKE, AWS EKS (EKS nodes run containerd and have no Docker socket, which is exactly why the pod-based executor exists).

Install

The chart is published with every release — no checkout required. Install it straight from the registry (replace 0.3.0 with the version you want):
Every release also attaches a packaged sinas-<version>.tgz you can install from directly, and a checkout works too:
The backend runs database migrations on startup; first boot takes a minute. Watch sandbox pods appear during executions:

How sandbox pods are secured

Each execution pod is created with the same hardening as the Docker sandbox:
  • all capabilities dropped (CHOWN/SETUID/SETGID added back), no privilege escalation, RuntimeDefault seccomp profile
  • memory / CPU / ephemeral-storage limits from MAX_FUNCTION_*
  • in-memory /tmp (100Mi), single-use, deleted after the execution, activeDeadlineSeconds as a leak backstop
  • no ServiceAccount token — sandbox pods cannot talk to the Kubernetes API
  • a NetworkPolicy that allows DNS and internet egress only: sandbox pods can never reach cluster-internal services (PostgreSQL, Redis, other namespaces)
The services themselves use a ServiceAccount whose Role is limited to pods + pods/exec in the release namespace — that is the entire privilege surface replacing the Docker socket.
NetworkPolicies require a CNI that enforces them (Cilium, Calico, kindnetd on recent kind, VPC CNI with a policy engine on EKS). Without enforcement the deployment still works, but sandbox pods are not network-isolated.

Executor image and cold-start latency

Sandbox pods run executor.image. By default, platform dependencies (the admin-managed package list) are pip installed into each pod at creation, which adds seconds to every sandbox execution. For production, bake them in:

Advanced: per-client node scheduling

These aren’t exposed as values in the bundled charts/sinas chart — they’re env vars for operators building their own multi-tenant deployment tooling around the k8s_pod executor (e.g. a chart that provisions one release per customer). Sinas doesn’t decide scheduling policy itself; it just applies whatever it’s given, so the same knobs work whether you want every customer’s sandbox pods spread across dedicated nodes or packed together on shared ones — that choice lives entirely in your own tooling, not here. All four are no-ops by default, matching the behavior described above with no scheduling constraint at all.

Sizing under namespace quotas

Sandbox pods are created on demand, per execution, in the release namespace — so they compete with the platform’s own pods for any namespace ResourceQuota. Plan the quota headroom explicitly:
  • Sandbox pod size is set by the executor, with requests == limits: MAX_FUNCTION_MEMORY (MiB, default 512) and MAX_FUNCTION_CPU (cores, default 1.0), plus an ephemeral-storage limit from MAX_FUNCTION_STORAGE. Pass them via the chart’s extraEnv to shrink each execution’s footprint, e.g. MAX_FUNCTION_MEMORY=192, MAX_FUNCTION_CPU=0.5.
  • Concurrency ceiling ≈ what’s left of the quota after the platform pods, divided by one sandbox pod’s request. Example: with a requests.memory: 3Gi quota and the default chart components requesting ~1.4Gi, a 192Mi sandbox pod allows ~8 concurrent executions; a 512Mi one allows ~3. Check requests.cpu and the pods: count the same way — the binding constraint is whichever runs out first.
  • Failure mode: when the quota is exhausted, pod creation is rejected and the execution fails with a quota error — reduce sandbox size, raise the quota, or lower worker concurrency (queueWorker/queueAgent values) to bound simultaneous executions.
  • Trusted (inprocess) code has no per-function memory cap — it runs inside the queue-worker process, so the worker’s resources.limits.memory is the tenant’s burst envelope for shared_pool functions (worker baseline + concurrency × per-call allocation). Size it accordingly.
  • Datastores (postgres, redis, pgbouncer) set no container resources in the chart; on clusters with a LimitRange they inherit its defaults, which count against the quota too. Give them explicit values in your own manifest patches if you need deterministic accounting.
The chart’s per-component resources are values — override any of them per deployment.

Compact profile (density deployments)

For packing several small instances onto one box (e.g. 3 instances on a 4GB node), drop ClickHouse and size requests near measured idle usage (backend idles ~175MB, workers ~90–120MB):
The math for 3 × on a 4GB node: 3 instances × ~700MB ≈ 2.1GB, k3s host overhead ~0.7GB → ~2.8GB idle, leaving ~700MB shared burst headroom — enough for a few concurrent 192Mi sandbox pods across tenants. Keep queueWorker.concurrency low so tenants can’t burst past it, or set SANDBOX_EXECUTOR=disabled for trusted-only tiers. With clickhouse.enabled: false the backend detects the empty CLICKHOUSE_HOST and disables logging cleanly (no reconnect attempts); the Logs pages return empty results.

Key values

Limitations

  • input() / human-in-the-loop pauses are unavailable: sandbox pods are single-use (sandbox mode has always rejected input()), and the inprocess trusted executor is run-to-completion. Durable pause/resume is tracked in issue #79.
  • The bundled datastores are convenience-grade. For production, point DATABASE_URL-family settings at managed services (e.g. RDS, ElastiCache) and disable the bundled StatefulSets.