Workspaces
Plans, base images, sleep and wake, scheduling and nodes that are switched off at night.
Storage has its own page: Storage.
Plans
A plan is a size users pick for a workspace's IDE or for a service. Limits are the plan; requests are the limits times
workspaces.sizing (CPU 0.2, memory 0.5 by default). The defaults:
| id | Kind | CPU | Memory | Storage | Replicas |
|---|---|---|---|---|---|
ide-s | workspace | 0.5 | 1.5 GiB | 10 GiB | |
ide-m | workspace | 1 | 2 GiB | 20 GiB | |
nano | service | 0.1 | 128 MiB | up to 3 | |
small | service | 0.25 | 512 MiB | up to 3 | |
medium | service | 1 | 1 GiB | up to 3 |
plans replaces the whole list. Keep the ids that existing workspaces and harthelm.yml files use:
plans:
- {id: ide-s, kind: workspace, name: IDE S, cpuMillis: 500, memoryMiB: 1536, storageGiB: 10}
- {id: ide-l, kind: workspace, name: IDE L, cpuMillis: 2000, memoryMiB: 4Gi, storageGiB: 40}
- {id: small, kind: service, name: Small, cpuMillis: 250, memoryMiB: 512, maxReplicas: 3}memoryMiB and storageGiB take a number or a size string (4Gi, 4096MiB); storage in whole GiB.
Base images
A base image is what a workspace's IDE runs on. Without baseImages the chart's own harthelm-ide image
(code-server on Ubuntu 26.04 with Nix, mise and hart) is the only one. Your own images are full references, a digest
advised:
baseImages:
- {id: node, name: Node, description: "Node.js 22 and pnpm", image: registry.example.com/ide/node:1.4, default: true}workspaces.serviceImage is the image of services that name none of their own (harthelm-base by default).
Sleep and wake
| Value | Default | Meaning |
|---|---|---|
workspaces.idleTimeoutMinutes | 60 | How long an IDE or service that sleeps when idle is idle before core scales it to 0. Volume and settings stay; the next request wakes it. A workspace or service can set its own. |
wake.maxPerMinute | 30 | Wakes that start per minute in the whole installation. 0 pauses waking: every request to a sleeping workspace answers "paused". |
wake.capacityGuard | true | Before a wake, check that the pods fit on a node and answer "no capacity" instead of leaving them Pending. |
workspaces.ide.reconnectionGraceSeconds | 300 | How long the IDE waits for a lost browser connection. |
Scheduling
scheduling.workspaces (node selector, tolerations, affinity) says where workspace pods may run;
scheduling.platform where core, the router, the backup jobs and the bundled database run. Node selectors are hard
requirements; use affinity for a preference.
scheduling:
platform:
nodeSelector: {example.com/pool: platform}
workspaces:
nodeSelector: {example.com/pool: workspaces}Users' databases have their own: marketplace.builtin.postgres.scheduling.
Nodes that are switched off
For nodes that are drained on a schedule (powered off at night, spot nodes), name them with
scheduling.ephemeralNodeSelector:
- Cordoning such a node puts the workspaces on it to sleep after
scheduling.cordonGraceSeconds(the agents stop their steps first). An eviction does the same at once. Uncordoning wakes nothing; the next request does. - Workspace pods prefer ephemeral nodes; services of
rwxworkspaces that are always running stay off them. Keep core, the router and the databases off them too, with an affinityNotInthe selector's label value. - A
rwo-colocatedworkspace is not recreated elsewhere: its volume cannot move. - Stopped services (never run, or stopped by a user) that still have a pod on such a node sleep too, so nothing sits Pending all night. They still show as stopped, and nothing wakes them in the morning; the next Run or Start does Since 1.0.3.
scheduling.powerWindows says when those nodes are on, so a wake while they are off answers "sleeps until 06:00"
instead of "no capacity". It is informational; nothing switches a node.
scheduling:
ephemeralNodeSelector: {example.com/power-schedule: daytime}
powerWindows:
- nodeSelector: {example.com/power-schedule: daytime}
timeZone: Europe/Berlin
periods:
- {days: [mon, tue, wed, thu, fri], from: "06:00", to: "23:00"}
- {days: [sat, sun], from: "08:00", to: "23:00"}Write periods, not on: YAML reads an unquoted on as a boolean.
Container services
Services in harthelm.yml can run an image. Core resolves it to a digest with the team's registry credentials.
| Value | Meaning |
|---|---|
containers.allowedRegistries | Image prefixes services may use; empty: any. |
containers.insecureRegistries | Registries core reaches over plain HTTP. The nodes' container runtime must allow them too. |
containers.platform | The platform an image index resolves to, for clusters with mixed CPU architectures. |
Core's egress to registries is networkPolicy.egress.registries: without to, public addresses only.
Registries for container services
Container services run whatever image their team names, so which registries are allowed is a choice. Two values decide:
containers.allowedRegistries (which image names core accepts) and networkPolicy.egress.registries (which addresses
core may reach to resolve an image to its digest). The nodes' container runtime pulls the image itself and needs its own
route to the registry.
| Mode | Values | Effect |
|---|---|---|
| Open (default) | allowedRegistries: [], egress.registries.to: [] | Any registry name is accepted. Core resolves digests on public addresses only (every address except private, CGNAT, loopback and link-local ranges) on port 443: Docker Hub, GHCR, Quay or a public Harbor work, a registry on a private address does not. |
| Mirror only | allowedRegistries: [registry.example.internal/mirror], egress.registries.to: [{ipBlock: {cidr: 203.0.113.5/32}}] | Only image names under that prefix (whole path segments) are accepted, and core reaches the mirror and nothing else. For the air-gapped install. |
| A registry on a private address | egress.registries.to: its ipBlock | A rule with a to replaces the public-addresses default, so list every address range core should reach. |
| Plain HTTP or another port | containers.insecureRegistries: [host:5000], a rule in networkPolicy.egress.extra | Core talks plain HTTP to it. The nodes' runtime must allow it as well; the chart does not configure that. |
| A login is needed | the team's registry credentials (container services), global.imagePullSecrets (the chart's images) | The team's credentials are for its own services; the chart's images pull with global.imagePullSecrets, which core also copies into the team namespaces. |
The address check that keeps core away from private addresses whatever the policy says is described in Security.