Kubernetes Namespace Layout
Simple Container's Kubernetes deployments automatically derive the target namespace from the stack and environment, so siblings never collide in the same physical namespace. This page documents the naming rule and what to expect on deploy, redeploy, and destroy.
The rule
Given a stack definition with stackName (the stack directory name) and stackEnv (the environment under stacks: in client.yaml), and an optional parentEnv (when the stack inherits from a different parent environment):
| Stack shape | parentEnv |
stackEnv |
Resulting namespace |
|---|---|---|---|
| Standard stack — same env as parent | (unset) or equals stackEnv |
<env> |
<stackName> |
| Custom stack — sub-env under a different parent env | set, differs from stackEnv |
<env> |
<stackName>-<stackEnv> |
The namespace name is sanitized to RFC 1123 (lowercase, _ → -, ≤63 chars with FNV-1a truncation hash if needed), so callers can pass arbitrary stack names without worrying about Kubernetes naming constraints.
Worked example
Given this client.yaml deploying to a parent infrastructure/myproject with environments staging and production:
# .sc/stacks/myapp/client.yaml
stacks:
staging:
type: cloud-compose
parent: infrastructure/myproject
# parentEnv defaults to "staging" → standard stack
config: { ... }
production:
type: cloud-compose
parent: infrastructure/myproject
parentEnv: production # standard stack (parentEnv == stackEnv)
config: { ... }
tenant-a:
type: cloud-compose
parent: infrastructure/myproject
parentEnv: production # custom stack (parentEnv != stackEnv)
config: { ... }
tenant-b:
type: cloud-compose
parent: infrastructure/myproject
parentEnv: production # custom stack (parentEnv != stackEnv)
config: { ... }
Deployment names → namespaces:
sc deploy -s myapp -e ... |
Resulting namespace |
|---|---|
staging |
myapp (in the staging cluster) |
production |
myapp (in the production cluster) |
tenant-a |
myapp-tenant-a (in the production cluster) |
tenant-b |
myapp-tenant-b (in the production cluster) |
tenant-a and tenant-b are isolated from each other and from production, even though they all run under the same parent infrastructure.
Why this matters
Destroy safety. Each custom stack owns its own namespace, so sc destroy -s myapp -e tenant-a only removes resources in myapp-tenant-a. The parent stack and other siblings are untouched.
Tenant isolation. Custom stacks no longer share a namespace, so namespace-scoped RBAC, NetworkPolicy, ResourceQuota, and Secret access are isolated by default.
Caddy routing follows automatically. Simple Container's Caddy ingress watches --all-namespaces and reads the simple-container.com/caddyfile-entry annotation off each Service. New namespaces are picked up on Caddy's next reconcile with no manual config.
Migrating an existing custom stack
If you have a custom stack that was deployed before Simple Container introduced the per-stackEnv namespace, the next sc deploy (or pulumi up) will move it to its dedicated namespace. The flow is automatic:
- Pulumi sees the namespace
metadata.namechange — schedules a Replace. - The new namespace is created (e.g.
myapp-tenant-a). - The old shared namespace is retained (via
RetainOnDelete) — the parent stack and any siblings still on the old namespace keep running. - All namespace-scoped resources owned by this stack (Deployment, Service, Secret, ConfigMap, HPA, VPA, ImagePullSecret, Jobs, and CloudSQL/init secrets) are Replaced in the new namespace.
- Caddy auto-rebuilds its Caddyfile from the moved Service annotation; brief gap during cutover.
You can dry-run the migration with sc deploy -P -s <stack> -e <env> to inspect the diff before applying.
After the last sibling migrates
If you eventually destroy every stack that ever referenced the old shared namespace, the namespace itself lingers (because of RetainOnDelete). Clean it up by hand once you've verified nothing depends on it:
kubectl get all -n <old-shared-namespace> # should be empty
kubectl delete namespace <old-shared-namespace>
PersistentVolumeClaim caveat
If your custom stack uses persistentVolumes, the namespace move triggers a Pulumi Replace on each PVC. Because PVCs are namespace-scoped and not movable, Pulumi creates the new PVC and deletes the old one. If the StorageClass's reclaimPolicy is Delete (the default for most dynamic provisioners on AWS/GCP), the underlying PV and its data are destroyed along with the old PVC.
Before migrating a stateful custom stack:
- Patch the existing PV's reclaim policy to
Retain: - Run
sc deploy -Pto confirm the diff matches expectations. - After the migration, clear
claimRefon the retained PV and re-bind it to the new PVC if you want to preserve the data — or accept the data is dev-only and let it recreate.
Stacks that don't define persistentVolumes (the typical case where state lives in managed services like Cloud SQL, RDS, Memorystore, or external MongoDB Atlas) are unaffected.