Harthelm Docs
Configure

Workspaces

Plans, base images, sleep and wake, scheduling and nodes that are switched off at night.

Last updated 2026-10-19

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:

idKindCPUMemoryStorageReplicas
ide-sworkspace0.51.5 GiB10 GiB
ide-mworkspace12 GiB20 GiB
nanoservice0.1128 MiBup to 3
smallservice0.25512 MiBup to 3
mediumservice11 GiBup to 3

plans replaces the whole list. Keep the ids that existing workspaces and harthelm.yml files use:

my-values.yaml
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:

my-values.yaml
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

ValueDefaultMeaning
workspaces.idleTimeoutMinutes60How 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.maxPerMinute30Wakes that start per minute in the whole installation. 0 pauses waking: every request to a sleeping workspace answers "paused".
wake.capacityGuardtrueBefore a wake, check that the pods fit on a node and answer "no capacity" instead of leaving them Pending.
workspaces.ide.reconnectionGraceSeconds300How 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.

my-values.yaml
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 rwx workspaces that are always running stay off them. Keep core, the router and the databases off them too, with an affinity NotIn the selector's label value.
  • A rwo-colocated workspace 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.

my-values.yaml
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.

ValueMeaning
containers.allowedRegistriesImage prefixes services may use; empty: any.
containers.insecureRegistriesRegistries core reaches over plain HTTP. The nodes' container runtime must allow them too.
containers.platformThe 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.

ModeValuesEffect
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 onlyallowedRegistries: [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 addressegress.registries.to: its ipBlockA rule with a to replaces the public-addresses default, so list every address range core should reach.
Plain HTTP or another portcontainers.insecureRegistries: [host:5000], a rule in networkPolicy.egress.extraCore talks plain HTTP to it. The nodes' runtime must allow it as well; the chart does not configure that.
A login is neededthe 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.

On this page