Harthelm Docs

harthelm.yml

Every key of harthelm.yml, the file that describes a landscape: types, required keys, defaults and allowed values.

Generated from schemas/harthelm.schema.json at v1.5.0

A Harthelm landscape: the steps that prepare and test a workspace and the services that run its app (schema harthelm/v1). Profiles are complete files named harthelm.<profile>.yml. Keys starting with x- are ignored everywhere; any other unknown key is an error.

The schema's id is urn:harthelm:schema:harthelm-v1. Every installation serves it without credentials at GET /api/v1/schemas/harthelm.json, so editors can validate the file.

harthelm.yml lives in /data/app, at the root of the workspace's code. The console's editor checks it as you type; hart validate checks it in a terminal or in CI. A file per profile, harthelm.<profile>.yml, describes a variant of the same landscape.

A complete example

harthelm.yml
schema: harthelm/v1

prepare:
  - name: Install dependencies
    run: |
      mise install node@22
      npm ci

test:
  - name: Unit tests
    run: npm test

services:
  web:
    type: steps
    steps:
      - name: Serve
        run: npm start --workspace web
    env:
      PORT: 3000
      API_URL: https://${{ workspace.devDomain }}/api
    ports:
      - port: 3000
        routes:
          - path: /
    needs: [api]

  api:
    type: steps
    plan: small
    replicas: 2
    steps:
      - name: Serve
        run: npm start --workspace api
    env:
      PORT: 8080
      API_KEY: ${{ secrets.API_KEY }}
      DATABASE_URL: ${{ services.db.dsn }}
    ports:
      - port: 8080
        routes:
          - path: /api
            strip: true
    health:
      port: 8080
      path: /health

  db:
    type: managed
    provider: postgres
    config: {postgresVersion: "18", database: app, user: app}
    secrets:
      password: ${{ secrets.DB_PASSWORD }}

The dev URL https://<id>-3000.<workspace domain>/ reaches web; /api/health reaches api as /health, balanced over two replicas; api connects to the PostgreSQL database db. The guides explain each part: configure services, secrets, add a database, path routing.

How to read this reference

Each table lists the keys of one object: its type, whether it is required, and what it does with its default and allowed values. Keys starting with x- are ignored everywhere, so you can keep notes or YAML anchors under them; any other unknown key is an error. Values of ${{ … }} templates are filled in at Sync.

A file that breaks a rule is refused with a code and the place of each problem: see harthelm.yml errors. The variables your services see without setting them are on Workspace environment.

Top level

KeyTypeRequiredDescription
schemaharthelm/v1yesThe schema version. Changes inside harthelm/v1 are additive.
preparelist of StepSteps that set up the workspace (install dependencies, build). They run in sequence in the IDE pod in /data/app with the workspace env vars but no secrets; the first failing step fails the stage.
testlist of StepSteps that test the workspace, run like prepare.
servicesmap of name to ServiceThe services of the landscape, by name. Sync creates, changes and removes them; Run starts their steps.

Keys starting with x- are ignored.

Step

One shell step, run with bash -lc in /data/app.

KeyTypeRequiredDescription
namestringA label for the Run panel and the logs. At least 1 character. At most 100 characters.
runstringyesThe command; multi-line blocks are fine. At least 1 character.

Keys starting with x- are ignored.

Service

The type key picks the kind of service; each kind has its own keys:

steps service

A service that runs steps in every replica: all but the last run once in sequence, the last one is the server and is restarted with backoff.

KeyTypeRequiredDescription
typestepsyes
stepslist of StepyesAt least 1 entry.
imagestringA custom base image built FROM harthelm-base; the installation's service image when absent. At most 512 characters. Pattern ^[a-z0-9][A-Za-z0-9._:/@-]*[A-Za-z0-9]$.
nixWritablebooleanMount /nix read-write instead of read-only. Default false.
planstringA service plan id of the installation (GET /api/v1/plans, kind service); the installation's default service plan when absent. Pattern ^[a-z0-9][a-z0-9-]{0,62}$.
replicasintegerHow many pods run the service; at most maxReplicas. Default 1. 1 to 100.
maxReplicasintegerThe most replicas the service may be scaled to without a Sync; at most the plan's maximum, which is also the default. 1 to 100.
availabilityalways, on-demandalways keeps the service running; on-demand lets it sleep after idleTimeoutMinutes without requests and wakes it on the next one. The default comes from the plan.
idleTimeoutMinutesintegerMinutes without requests before an on-demand service sleeps; the installation's default when absent. 1 to 1440.
envmap of name to string or number or booleanEnvironment variables of the service. Values may use ${{ workspace.id }}, ${{ workspace.devDomain }}, ${{ team.id }}, ${{ env.K }}, ${{ secrets.K }} and, for a managed service of this file, ${{ services.<name>.host }}, .port, .dsn (with the password), .secret (the password alone) and .details.<field>; numbers and booleans become strings. Names match ^[A-Za-z_][A-Za-z0-9_]{0,127}$. Names must not match ^HART_.
portslist of PortThe ports the service listens on.
healthHealth checkHow the agent checks from inside the pod that a replica is ready.
mountslist of MountFiles or directories of /data/app mounted at another path in the container.
needslist of stringServices this one needs: waking it wakes them too, transitively and in parallel, and the router holds the request until all are ready; none of them sleeps while this one is awake; Run starts them first. Managed and forward services may be named (nothing to wake). Unknown names and cycles are errors. At most 64 entries. No duplicates. Pattern ^[a-z]([-a-z0-9]{0,30}[a-z0-9])?$. Not ide.

Keys starting with x- are ignored.

container service

A service from your own OCI image; the agent runs its ENTRYPOINT and CMD under the workspace's random UID, group 0. Sync pins the image's digest; private images use the team's container registries.

KeyTypeRequiredDescription
typecontaineryes
imagestringyesAn image reference; pin it by digest for reproducible Syncs. At most 512 characters. Pattern ^[a-z0-9][A-Za-z0-9._:/@-]*[A-Za-z0-9]$.
commandlist of stringReplaces the image's ENTRYPOINT.
argslist of stringReplaces the image's CMD.
runAsUserintegerOnly for teams a cluster admin allowed to run as root or a fixed user. 0 to 2147483647.
planstringA service plan id of the installation (GET /api/v1/plans, kind service); the installation's default service plan when absent. Pattern ^[a-z0-9][a-z0-9-]{0,62}$.
replicasintegerHow many pods run the service; at most maxReplicas. Default 1. 1 to 100.
maxReplicasintegerThe most replicas the service may be scaled to without a Sync; at most the plan's maximum, which is also the default. 1 to 100.
availabilityalways, on-demandalways keeps the service running; on-demand lets it sleep after idleTimeoutMinutes without requests and wakes it on the next one. The default comes from the plan.
idleTimeoutMinutesintegerMinutes without requests before an on-demand service sleeps; the installation's default when absent. 1 to 1440.
envmap of name to string or number or booleanEnvironment variables of the service. Values may use ${{ workspace.id }}, ${{ workspace.devDomain }}, ${{ team.id }}, ${{ env.K }}, ${{ secrets.K }} and, for a managed service of this file, ${{ services.<name>.host }}, .port, .dsn (with the password), .secret (the password alone) and .details.<field>; numbers and booleans become strings. Names match ^[A-Za-z_][A-Za-z0-9_]{0,127}$. Names must not match ^HART_.
portslist of PortThe ports the service listens on.
healthHealth checkHow the agent checks from inside the pod that a replica is ready.
mountslist of MountFiles or directories of /data/app mounted at another path in the container.
needslist of stringServices this one needs: waking it wakes them too, transitively and in parallel, and the router holds the request until all are ready; none of them sleeps while this one is awake; Run starts them first. Managed and forward services may be named (nothing to wake). Unknown names and cycles are errors. At most 64 entries. No duplicates. Pattern ^[a-z]([-a-z0-9]{0,30}[a-z0-9])?$. Not ide.

Keys starting with x- are ignored.

managed service

A managed service bound to this landscape, e.g. a PostgreSQL database: Sync creates or changes it, Teardown pauses it, the next Sync resumes it, removing it from the file deletes it and its data. Other services reach it through ${{ services.<name>.host|port|dsn|details.<field> }} in their env.

KeyTypeRequiredDescription
typemanagedyes
providerstringyesProvider name from the installation's catalog (GET /api/v1/providers), e.g. postgres. Pattern ^[a-z][a-z0-9-]{0,62}$.
providerVersionstringThe provider's version; the newest enabled one when absent. An instance keeps the version it was created with. At least 1 character. At most 64 characters.
planstringOne of the provider's plans, e.g. tiny; the provider's default plan when absent. Pattern ^[a-z0-9][a-z0-9-]{0,62}$.
resourcesmap of name to integer or stringOverrides of the plan's resource values the provider lets you change, e.g. {storage: 2048} or {storage: 2Gi}; checked against the provider's bounds and update constraints. Sizes (the provider's memory and storage resources) are MiB as a number, or a size with a binary unit: 2048MiB, 2048Mi, 2Gi, 2GiB, 1.5Gi. Names match ^[A-Za-z_][A-Za-z0-9_]{0,63}$.
configobjectProvider settings, checked against the provider's schema incl. update constraints; strings may use templates except secrets and services.
secretsmap of name to stringProvider secrets, each exactly one ${{ secrets.K }} reference: the value lives in the workspace's vault, never in this file. Sync asks for missing ones. Names match ^[A-Za-z_][A-Za-z0-9_]{0,127}$.
backupsbooleanDump it nightly (providers that can); default true.

Keys starting with x- are ignored.

forward service

Routes paths of the dev domain to a URL: a Service of your team's namespace or an address the installation allows (the router's SSRF guard). No pods run.

KeyTypeRequiredDescription
typeforwardyes
targetstringyesAn http(s) URL; may use templates. Pattern ^(https?://|\$\{\{).
routeslist of RouteyesA path prefix on the dev domain; the longest matching prefix wins, on segment boundaries. At least 1 entry.

Keys starting with x- are ignored.

Port

KeyTypeRequiredDescription
portintegeryes1 to 65535.
publicbooleanAlso serve this port on its own host <workspace>-<port>.<workspace domain>; public ports are unique in a landscape. Default false.
routeslist of RoutePaths of the dev domain that go to this port.

Keys starting with x- are ignored.

Route

A path prefix on the dev domain; the longest matching prefix wins, on segment boundaries.

KeyTypeRequiredDescription
pathstringyesAt most 256 characters. Pattern ^/[A-Za-z0-9._~!$&'()*+,;=:@%/-]*$.
stripbooleanRemove the prefix before the request reaches the service (/api/items arrives as /items). Default false.

Keys starting with x- are ignored.

Health check

How the agent checks from inside the pod that a replica is ready.

KeyTypeRequiredDescription
portintegeryesOne of the service's ports. 1 to 65535.
pathstringDefault "/". At most 256 characters. Pattern ^/.
modeany-http, http-2xxany-http: any HTTP answer counts as healthy; http-2xx: only 2xx does. Default "any-http".

Keys starting with x- are ignored.

Mount

KeyTypeRequiredDescription
fromstringyesPath relative to /data/app. At least 1 character. At most 1024 characters. Pattern ^[^/].
tostringyesAbsolute path in the container. At most 1024 characters. Pattern ^/..

Keys starting with x- are ignored.

On this page