Skip to main content
Version: v5

Applications

The contents of this page primarily relate to application components. The term "components" in the Operations API and CLI generally refers to applications specifically. See the Components Overview for a full explanation of terminology.

Harper offers several approaches to managing applications that differ between local development and remote Harper instances.

Local Development​

dev and run Commands​

Added in: v4.2.0

The quickest way to run an application locally is with the dev command inside the application directory:

harper dev .

The dev command watches for file changes and restarts Harper worker threads automatically.

The run command is similar but does not watch for changes. Use run when the main thread needs to be restarted (the dev command does not restart the main thread).

Stop either process with SIGINT (Ctrl+C).

Deploying to a Local Harper Instance​

To mimic interaction with a hosted Harper instance locally:

  1. Start Harper: harper

  2. Deploy the application:

    harper deploy \
    project=<name> \
    package=<path-to-project> \
    restart=true
    • Omit target to deploy to the locally running instance.
    • Setting package=<path-to-project> creates a symlink so file changes are picked up automatically between restarts.
    • restart=true restarts worker threads after deploy. Use restart=rolling for a rolling restart.
  3. Use harper restart in another terminal to restart threads at any time.

  4. Remove an application: harper drop_component project=<name>

Not all component operations are available via CLI. When in doubt, use the Operations API via direct HTTP requests to the local Harper instance.

Example:

harper deploy \
project=test-application \
package=/Users/dev/test-application \
restart=true

Use package=$(pwd) if your current directory is the application directory.

Shutdown Cleanup​

Applications with work that must complete, or be acknowledged outside the process, before a thread exits — buffered writes to flush, a registration to withdraw from an external service, a distributed lock to release — need a hook to do it when Harper stops or restarts. Restarts are frequent during local development, since harper dev restarts worker threads on every file change, and deploying with restart=true does the same on a running instance.

Harper signals this by calling scope.close() on each worker thread that loads the application, which emits a 'close' event on the plugin API Scope. Listen for it to run cleanup:

export function handleApplication(scope) {
const service = startService();
scope.once('close', async () => {
await service.close();
});
}

See Cleanup on Shutdown for the full shutdown sequence, including how async cleanup is awaited and the time limits it must finish within. Stopping Harper with SIGTERM, SIGINT or SIGQUIT, including harper stop, runs this cleanup from v5.4.0; earlier versions end the process without closing scopes. Changed in: v5.4.0

Writing Files in the Application Directory​

An application's directory (<componentsRoot>/<name>, or scope.directory) belongs to the deploy. Harper does not protect, version, or coordinate files an application writes there at runtime; if your application does, managing those files is your responsibility:

  • Each deploy replaces the directory, and files written at runtime are not carried over.
  • Workers running the previous version keep running until they restart, and a file they write by path lands in the new version's directory.
  • Worker threads write concurrently, and files on disk are not replicated to other nodes.

Store state in a Harper table instead. For disk caches, write to a directory outside the application directory, namespaced by build or version, and remove old ones yourself. The Next.js plugin does this for Next.js's caches.

Remote Management​

Managing applications on a remote Harper instance uses the same operations as local management. The recommended approach is to log in first using harper login to store an authentication token:

# Log in once
harper login <remote>
# Provide your username and password when prompted

# Subsequently deploy without credentials
harper deploy \
project=<name> \
package=<package> \
target=<remote> \
restart=true \
replicated=true

For CI/CD on GitHub Actions, use workload identity (OIDC) instead: the CLI trades the runner's identity token for an operation token against a trust policy on the cluster, so the pipeline stores no Harper credential, and the policy can name a deploy-only user rather than an administrator. Deploying from a CI/CD Pipeline has a complete workflow. Elsewhere, give the pipeline a refresh token from harper login --for-ci rather than a password.

Dedicated Authentication Parameters​

Added in: v5.2.0

For one-off remote commands, dedicated authentication parameters are also available (not recommended for production):

harper deploy \
project=<name> \
package=<package> \
auth_username=<username> \
auth_password=<password> \
target=<remote> \
restart=true \
replicated=true

Dedicated authentication parameters take precedence over environment variables and saved login tokens. See CLI Authentication for the full order and security guidance.

Package Sources​

When deploying remotely, the package field can be any valid npm dependency value:

  • Omit package to package and deploy the current local directory
  • npm package: package="@harperdb/status-check"
  • GitHub: package="HarperFast/status-check" or package="https://github.com/HarperFast/status-check"
  • Private repo (SSH): package="git+ssh://git@github.com:HarperDB/secret-app.git"
  • Tarball: package="https://example.com/application.tar.gz"

When using git tags, use the semver directive for reliable versioning:

HarperFast/application-template#semver:v1.0.0

Harper generates a package.json from component configurations and uses a form of npm install to resolve them. This is why specifying a local file path creates a symlink (changes are picked up between restarts without redeploying).

For SSH-based private repos, use the Add SSH Key operation to register keys first.

Deploying by Reference​

Added in: v5.2.3

Omitting package uploads a snapshot of your working directory. The result is an anonymous artifact: nothing records which commit it came from, so reproducing it later — or stepping back to a previous release — means finding those exact files again.

Deploying by reference sends a pinned git reference instead, and the cluster fetches that exact commit. Redeploying the same reference deploys the same source revision.

A pinned SHA fixes the source, not the built artifact. The cluster installs and builds from that source on each node, so unpinned dependency ranges, a mutable registry artifact, install scripts, or a different toolchain can still produce different bytes — or a failure — from the same commit. Commit your lockfile if you need the build itself to be reproducible.

harper deploy by_ref=true builds that reference from the local git repository, so you don't assemble the URL yourself:

harper deploy by_ref=true restart=true replicated=true

This resolves the repository's origin remote and the current commit, then deploys package=git+https://github.com/<owner>/<repo>.git#<full commit SHA>.

Parameters:

  • by_ref - Build the package reference from the local repository.
  • ref (optional) - Deploy a specific commit, tag, or branch instead of HEAD. Resolved to a commit SHA before it is sent to the cluster. Implies by_ref.
  • credential (optional) - Set to true to authenticate the clone with the stored credential for the repository's host. Omit for public repositories.

ref takes a tag or a commit:

harper deploy ref=v1.2.0 restart=true replicated=true
harper deploy ref=9f8c2a1 restart=true replicated=true

To go back to a release a later deploy replaced, activate the kept release by its deployment_id instead (v5.3.0). That puts back the exact installed release with no fetch, rebuild, or reinstall, where deploying an older commit installs it again from source:

harper deploy project=<name> deployment_id=<id> restart=true

list_deployments shows each deployment's id, and Going back to a previous release says which releases are kept.

A reference is pinned to a SHA, not to the name you typed. Tags and branches are resolved to a full commit SHA before the deploy is sent — from your local checkout when it has the ref, and from the remote when it doesn't (a shallow CI clone usually doesn't). Annotated tags resolve to the commit they point at. This matters on a cluster: peers resolve the package independently, so a tag that moves mid-deploy — or a branch that advances — could otherwise leave nodes running different code.

If a ref can't be resolved either way, the deploy stops rather than sending the name for the cluster to resolve. Run git fetch and retry, or pass a full commit SHA — that needs no resolution and is always accepted.

A full commit SHA is accepted directly. An object ID is already immutable, so there is nothing to pin it to and no resolution is attempted — SHA-based rollbacks are always valid. Every other ref must name something a clone can fetch: refs/heads/* or refs/tags/* if you qualify it, or a bare branch or tag name that resolves locally or on the remote. A qualified ref outside those two namespaces — refs/pull/123/head, say — is rejected up front, even if your own checkout can resolve it, because the cluster could resolve that commit and still never check it out.

Commit and push first. The cluster clones from the remote, so it only sees commits that have been pushed. by_ref warns in both directions: when the working tree is dirty (those changes won't be part of the deploy) and when the commit being deployed isn't on any remote branch (the cluster won't be able to clone it). The second check reads your local remote-tracking refs, so run git fetch if you get it for a commit you know you pushed.

The unpushed-commit check is skipped under GitHub Actions, where the runner's checkout is not a branch a git branch -r --contains can see; the dirty-tree warning still applies. On a pull_request run the commit is resolved from the event payload instead, as described below.

In GitHub Actions, by_ref deploys the commit the workflow is running on. On a pull_request run that is the pull request's head commit rather than the merge commit the runner checks out: the merge commit lives under refs/pull/<n>/merge, which a plain clone can't fetch, so the cluster would have no way to resolve it. For a pull request from a fork, the head repository is the fork, and the CLI names it before deploying. If the event payload isn't readable, the deploy stops and asks for the commit explicitly:

harper deploy ref=${{ github.event.pull_request.head.sha }} restart=true replicated=true

Private repositories​

Pass credential=true for a private repository. The CLI attaches a credentials reference naming a secret that the cluster resolves in memory at clone time, so no token travels in the operation body or lands on disk:

harper deploy by_ref=true credential=true restart=true replicated=true

The host comes from the package being deployed, so the credential always matches the clone it authenticates. Naming the host explicitly (credential=github.com) still works, but one that doesn't match the package's host is rejected instead of deployed — the clone would never ask for it, and the deploy would fail as though no credential were configured.

Provision that credential once with harper deploy setup=true. See Private-source deploy credentials for how the secret is named and resolved, and add_ssh_key for the SSH-key alternative.

note

Deploying by reference means the cluster installs and builds the component from source. If your application needs a build step that can't run on the node, keep shipping the built output as a payload deploy instead.

Provisioning a Deploy Credential​

Added in: v5.2.3

harper deploy setup=true provisions the credential a private deploy needs. It's interactive, and runs once per component and source. It calls get_secrets_public_key, set_secret, and grant_secret, all of which require super_user, so run it with an administrative credential rather than the CI identity it provisions for:

harper deploy setup=true

It asks which private source needs a credential (a GitHub repository or an npm registry), sources a token, and then:

  1. Fetches the cluster's public key with get_secrets_public_key.
  2. Encrypts the token locally into an enc:v1: envelope.
  3. Stores only the ciphertext with set_secret, in the component-scoped tier.
  4. Grants this component permission to resolve it with grant_secret.
  5. Prints the credentials reference for the deploy to use.

The plaintext never leaves your machine: the operations API, its logs, and replication only ever carry the envelope, and the cluster decrypts it in memory at deploy time. This requires a cluster with secrets custody (Harper Pro / Fabric) — see Client-side encryption.

Prefer a fine-grained PAT. For a GitHub repository the prompt offers, and defaults to, pasting a fine-grained personal access token with Contents: Read-only on that one repository. If you have the gh CLI authenticated it also offers its session token, which is one keypress cheaper but typically carries repo, read:org, gist, and workflow scopes across your whole account; choosing it prints a warning. What this flow seals is durable and replayed on every cold deploy and rollback, so it is worth being the narrowest credential that does the job.

The secret is stored scoped to the component, never in the global processEnv tier that every component and child process can read. If a global secret already exists at the derived name, it is converted to the scoped tier — the name is derived from the component, so a global secret there was never serving anything the scoped one doesn't. Existing grants on the row are preserved.

Because the stored token is durable, later deploys — including re-fetching an older reference — reuse it without re-entering anything.

Dependency Management​

Harper uses npm and package.json for dependency management.

During application loading, Harper follows this resolution order to determine how to install dependencies:

  1. If node_modules exists, or if package.json is absent — skip installation
  2. Check the application's harper-config.yaml for install: { command, timeout } fields
  3. Derive the package manager from package.json#devEngines#packageManager
  4. Default to npm install

The add_component and deploy_component operations support install_command and install_timeout fields for customizing this behavior.

Example harper-config.yaml with Custom Install​

myApp:
package: ./my-app
install:
command: yarn install
timeout: 600000 # 10 minutes
allowInstallScripts: true

Example package.json with devEngines​

{
"name": "my-app",
"version": "1.0.0",
"devEngines": {
"packageManager": {
"name": "pnpm",
"onFail": "error"
}
}
}

If you plan to use an alternative package manager, ensure it is installed on the host machine. Harper does not support the "onFail": "download" option and falls back to "onFail": "error" behavior.

Advanced: Direct harper-config.yaml Configuration​

Applications can be added to Harper by adding them directly to harper-config.yaml (located in the Harper rootPath, typically ~/hdb).

status-check:
package: '@harperdb/status-check'

The entry name does not need to match a package.json dependency. Harper transforms these entries into a package.json and runs npm install.

Any valid npm dependency specifier works:

myGithubComponent:
package: HarperDB-Add-Ons/package#v2.2.0
myNPMComponent:
package: harper
myTarBall:
package: /Users/harper/cool-component.tar
myLocal:
package: /Users/harper/local
myWebsite:
package: https://harperdb-component

Harper generates a package.json and installs all components into <componentsRoot> (default: ~/hdb/components). A symlink back to <rootPath>/node_modules is created for dependency resolution.

Use harper get_configuration to find the rootPath and componentsRoot values on your instance.

Isolated Applications and Branched Databases​

Added in: v5.3.0

Two keys on an application's root-config entry run it apart from the other applications on the instance:

  • isolated: true runs the application in a worker thread of its own.
  • branchedDatabases gives the application a private fork of one or more databases.
# <rootPath>/harper-config.yaml
shop-preview:
package: my-org/shop#3f9c2e1
host: preview.shop.example.com
isolated: true

Like host and urlPath, these keys say how one deployment of the application runs, so they belong on its entry in the root config and not in the application's own config.yaml. An application that declares branchedDatabases in its own config.yaml fails to load, with an error saying the key belongs in the root config entry.

Setting them when you deploy​

A deploy can set host, urlPath, isolated, and branchedDatabases in the same call. A package deploy, including harper deploy by_ref=true, builds the component's root-config entry from the request, and from v5.4.0 a payload deploy, such as harper deploy from a directory, can set them too. The CLI parses isolated=true as a boolean and branchedDatabases='["data"]' as a list:

harper deploy project=shop-preview by_ref=true \
host=preview.shop.example.com isolated=true restart=true
  • A later package deploy that omits isolated keeps the current setting, and isolated=false removes it.
  • Changed in: v5.4.0 A payload deploy (from a directory, or with payload) that sets any of the four keys sets all four: the ones it leaves out keep their current values, except that isolated=false removes isolation. One that sets none of them keeps the keys already on the entry. Before v5.4.0, a payload deploy refused isolated, host and urlPath with a 400, and accepted branchedDatabases but dropped it.
  • From v5.3.2, a package deploy that sets branchedDatabases gives the application a fork, and from v5.4.0 so does a payload deploy.
  • On v5.3.0 and v5.3.1, no deploy request gives an application a fork. A package deploy writes branchedDatabases into the entry, but the application ignores it when it loads (harper#3071). A payload deploy accepts branchedDatabases and drops it (harper#3044). The manual steps under Branched databases do work.

How a deploy updates the root config has the full rules for the entry.

Isolated applications​

By default, every worker thread loads every application. An application whose entry sets isolated: true is loaded by one dedicated worker thread instead, that thread loads no other application, and no other thread loads it. Its module state, its globals, and its thread's copy of process.env are not shared with another application, and a redeploy that keeps it isolated restarts only its own thread.

Its REST export registry also belongs to that worker: exports registered by an isolated application are not registered in the shared workers. See Deploying from CI for isolated deployments and a preview for each pull request.

Isolation is about the thread, not the data. The application still reads and writes the same databases as everything else on the instance, unless it also declares branchedDatabases. Users, roles, and sessions stay instance-wide.

Reaching an isolated application​

The dedicated worker does not listen on Harper's HTTP or MQTT ports. A request to those ports never reaches the application, even one addressed to its host. (Changed in: v5.4.0) The shared workers answer a request for the host of an isolated application with 503, Retry-After: 5, and Service unavailable: <host> is served by an isolated application whose worker is not running, and close a WebSocket for it with 1013 (try again later), rather than with another application's routes. The operations API port is not affected. Before v5.4.0 they answered it like any other request. The worker listens only on Unix domain sockets of its own, one for each secure port:

<rootPath>/sockets/app-<application>-<port>.sock

In the socket name, every character of the application name outside A–Z, a–z, 0–9, . and _ is percent-encoded, so shop-preview becomes app-shop%2Dpreview-<port>.sock. The proxy in front terminates TLS. The socket for http.securePort serves plain HTTP, and the socket for the MQTT secure port serves MQTT. A .yaml file beside each socket names the application, its host, and the TLS certificates the proxy needs.

  • On Harper Fabric, the platform's proxy routes the application's host to its socket, by TLS SNI. It does so for a host name the cluster claims as a custom domain, and for a preview host one label under app. and the cluster's host name. Because it picks the socket by host name alone, give each isolated application its own host, used by no other application. An isolated application with no host, or with a host it shares with another application split by urlPath, loads and reports healthy, but no request reaches it. Harper does not refuse these configurations yet (harper#2757).
  • On a self-managed instance, Harper does not route to the socket. Run a proxy that terminates TLS and forwards the application's requests to its socket, by host name or however your proxy routes.

Requirements and refusals​

Harper never loads an isolated application in the shared workers. It refuses the application when the instance cannot give it a reachable worker of its own:

  • threads.count is 0, so there is no worker thread to dedicate.
  • No http.securePort is configured.
  • tls.unixDomainSockets is not enabled.
  • The instance runs on Windows.
  • The socket path would be longer than the platform allows.
  • It would not get one of the threads.maxIsolated dedicated workers (default 8). Applications with a running dedicated worker keep it. Any dedicated worker still running counts against the cap until it stops, including one left running by a drop_component without restart or by turning isolation off. The rest get one in root-config order, the order startup admits them, until the cap is reached. As of v5.4.0, an application whose live release failed closed gets no worker and does not count against the cap: for example, a first release its canary rejected, which stays on disk until it is redeployed or dropped.

The node that receives a deploy_component checks these before it deploys, and answers 409 with the reason, such as Cannot deploy 'shop-preview' as an isolated application: the instance already runs 8 isolated application(s) (threads.maxIsolated). For the cap, it places the application where startup would, so a redeploy of an application that is on disk but has no running worker is refused while the running applications and those ahead of it in root-config order fill the cap. Before v5.4.0, such a deploy could succeed and then never start a worker. The same check runs when a refused release is activated again by its own deployment_id. The node answers 503 if it cannot read which isolated applications are running or which releases are refused. The other nodes of a replicated deploy do not check: they record the entry, and a node that cannot run the application reports it as failed in get_status instead.

At startup or restart, an isolated application that is refused is not loaded anywhere. Harper logs Application '<name>' is isolated but gets no dedicated worker: <reason>; it is not loaded anywhere.

Restarting and dropping​

  • A new isolated application starts with its first restart=true deploy, and nothing else restarts. Changed in: v5.4.0 Only its own worker starts, and that worker is the canary: a release that fails to load fails the deploy with 400 and leaves no worker and no socket. A release deployed from a local directory (package: file:<dir>) is linked rather than copied, so the canary does not check it. The exception is a name the shared workers still have loaded, such as an application of that name dropped without a restart: they restart too. Without restart, the application starts at the next restart. Before v5.4.0, that first restart also restarted the shared workers.
  • A deploy that turns isolation off, or turns it on for an application the shared workers had loaded, restarts the shared workers as well as starting or stopping the dedicated worker.
  • A redeploy of an application that stays isolated restarts only its own worker. The other applications keep running. The exception is retrying an activation whose release is already live, which restarts every worker.
  • restart_service can target one isolated application. {"operation": "restart_service", "service": "http", "scope": "<application>"} restarts only that application's worker.
  • Dropping an isolated application stops its worker, with or without restart. Changed in: v5.4.0 This is a drop_component of the whole project; dropping one file leaves the worker running, and with restart=true restarts it. The shared workers keep running, unless they may still have the application loaded, as after turning isolation on without a restart: then a drop with restart=true restarts them too. Dropping normally reclaims retained installs; inspect node logs if storage cleanup fails. On a node that does not have the application (no component directory and no root config entry), the drop restarts nothing; it only stops a dedicated worker, or removes forks, that an earlier drop left behind. On Pro and Fabric, the drop goes to every node, and the response reports each node's outcome; see drop_component. Before v5.4.0, the worker stopped only with restart=true, and dropping an absent application with restart=true could restart the shared workers.
  • system_information shows the dedicated worker. In its threads list, the dedicated worker's entry carries application: '<name>'.

Each isolated application adds a worker thread on top of threads.count. See threads.maxIsolated for how that affects memory.

Branched databases​

branchedDatabases gives an application a private, durable fork of the databases it names. The application addresses them by their usual names. Its reads and writes go to its fork, and every other application keeps using the base database.

# <rootPath>/harper-config.yaml
shop-preview:
branchedDatabases: [data]

The value is a list of database names, or true for every database except system that exists when the application loads. A database created later is not branched.

Known issue in v5.3.0 and v5.3.1

branchedDatabases takes effect only on an entry without package. That is an application in the components root, such as one deployed with a payload. On an entry that has package (which every package and by_ref deploy writes), it is ignored. The application then runs on the base databases, and no error is reported (harper#3071).

Until that is fixed, use these manual steps for a new application:

  1. Deploy it with a payload, without restart.
  2. Add branchedDatabases to its entry in harper-config.yaml on every node. The root config is per node. A node without this key runs the application on the base database. Writes made on that node replicate to the whole cluster.
  3. Restart each node.
  4. On each node, confirm the application's fork directory exists before you write through the application.

What the fork is​

  • A snapshot of the base, taken the first time the application loads with the key. Harper takes a RocksDB checkpoint of the base database, which uses hard links when the fork is on the same filesystem as the base. It captures blob files separately, using hard links too; this is not an atomic snapshot of records and blobs together. Blobs still being written or reclaimed before capture can leave markers in the fork, with warnings in the log and errors when those records are read. Later base writes do not refresh the fork.
  • Durable. The fork survives restarts and redeploys, and is never refreshed from the base. To start again from the current base, drop the application, confirm its fork has been removed on every node, then deploy it again. Reusing the name before the fork is removed reuses that fork.
  • Stored beside the base database, at <storage path>/`branches`/<application>/<database>. With the default storage path, that is <rootPath>/database/`branches`/<application>/<database>. The backticks are part of the directory name, so quote the path with single quotes in a shell, as in ls '<rootPath>/database/`branches`/'; inside double quotes, the shell runs the backticks as a command.
  • Private. The fork is not added to the instance's list of databases, so other applications, describe_all, analytics, and replication do not see it.
  • Local to each node. In a Harper Pro cluster, each node creates its own fork from its own copy of the base when the application first loads there. Writes to a fork stay on the node that took them. The fork's path is the same on every node.
  • Owns its tables. A table the application declares in a branched database, through a schema's @table, ensureTable, or defineTable, is created in the fork.

Reaching the fork from code​

Import databases from harper, and each branched name on it resolves to the fork. tables from harper is a shortcut for the default database, data, so it reaches a fork only when data itself is branched; for any other branched database, go through databases.

Changed in: v5.4.0 The bare databases and tables globals reach the fork too in an isolated application, whose worker is its own, and under the vm and compartment module loaders, which give each application its own globals. In the shared workers under the default vm-current-context loader, or native, the globals are shared by every application in the thread, so code that uses them reads and writes the base database without any warning. Before v5.4.0 that was so in every case (harper#3053). Importing from harper reaches the fork everywhere.

With branchedDatabases: [inventory], this is the fork's Product table:

import { databases } from 'harper';

const { Product } = databases.inventory;

Requirements and failure modes​

Harper never falls back to the base. An application whose fork cannot be created fails to load instead, with an error that names the reason:

  • storage.engine is lmdb. Branching requires RocksDB.
  • applications.moduleLoader is native, which cannot give the application its own databases.
  • A named database does not exist when the application loads.
  • storage.blobPaths is configured as an empty list, which leaves the fork nowhere to keep blobs.
  • <length of application name>_<application>__<database> is longer than 250 characters.
  • An existing fork directory is damaged. Harper refuses to serve it or rebuild it, since rebuilding would discard the fork's data. Delete the directory to have it recreated from the base.

The value itself is checked when you deploy, and again when the application loads. It must be true or a list of distinct database names. system cannot be branched, and a name cannot contain / or \ or be . or ... A deploy_component that breaks these rules is refused with a 400.

Removing a fork​

Changed in: v5.4.0

drop_component removes the application's forks by default, once nothing has them open: after its dedicated worker stops, or, for an application the shared workers run, after the restart that restart=true asks for. A replicated drop does this on every node, each removing its own fork, and the response reports branchesRemoved for each node.

The forks stay, and the response message says why, when:

  • the drop sets keepBranches: true;
  • the shared workers run the application and the drop has no restart, so they still have it loaded;
  • the restart did not finish replacing every worker;
  • the main thread serves the application as the node's only worker.

Except with keepBranches, the drop records the removal as owed before it changes anything, in a `removal-pending` file in the application's `branches`/<application> directory, and Harper finishes it without another drop: once no worker has the forks loaded, such as after the shared workers next restart, or, when the main thread serves the application, at Harper's next start. A keepBranches drop cancels a removal an earlier drop left owed. Until the forks are removed, deploying an application under the same name picks up the old fork, with its data, and the fork is then removed at that application's next drop.

keepBranches: true cannot be combined with restart: true; that request is refused with 400. A node running a version before v5.4.0 ignores keepBranches, and with restart: true it would remove its forks. To keep the forks of an application the shared workers run, drop it with keepBranches: true, then restart with restart_service.

If a fork cannot be removed, the drop fails with an error saying the storage was left in place, and Harper retries the removal until it succeeds: after each later drop, at its next start, and on a timer that starts at one minute and doubles up to an hour. A drop that a worker thread handles and then stops answers before the worker stops, with branchesRemoved: null: an isolated application dropping itself, or a drop handled in a shared worker that restart=true restarts (harper#3091). A removal failure after that answer is logged and retried the same way (harper#3600). A drop sent to the Operations API waits for every step, since the Operations API runs on the main thread. To confirm that a fork is gone, check its directory on each node.

Before v5.4.0, only a drop with restart=true removes the forks, once its restart has completed. Without restart, they stay, and the message says to drop again with restart: true.

Operations API​

Component operations require super_user, unless a role's operations allowlist lists them, which is how a deploy-only CI role gets deploy_component. A role's component_deploy grant limits deploy_component, drop_component and get_components to the components it names (Added in: v5.4.0). A few cannot be granted that way, such as get_deployment_payload, and a deploy that passes a literal token in credentials still needs super_user.

add_component​

Creates a new component project in the component root directory using a template.

  • project (required) — Name of the project to create
  • template (optional) — Git URL of a template repository. Defaults to https://github.com/HarperFast/application-template
  • install_command (optional) — Install command. Defaults to npm install
  • install_timeout (optional) — Install timeout in milliseconds. Defaults to 300000 (5 minutes)
  • install_allow_scripts (optional) — Allow install scripts to run. Defaults to false, which causes --ignore-scripts to be passed to the install command (this is ignored with install_command).
  • replicated (optional) — On Harper Pro and Fabric, replicate to all cluster nodes by default; pass false to apply on this node only. Harper core on its own does not replicate
{
"operation": "add_component",
"project": "my-component"
}

deploy_component​

Deploys a component using a package reference or a base64-encoded .tar payload.

  • project (required) — Name of the project
  • package (optional) — Any valid npm reference (GitHub, npm, tarball, local path, URL)
  • payload (optional) — Base64-encoded .tar file content
  • force (optional) — Allow deploying over protected core components. Defaults to false
  • restart (optional) — true for immediate restart, 'rolling' for sequential cluster restart. Either one certifies the release in a canary worker before rolling it out (v5.4.0), unless the response's certification says it went out unchecked
  • replicated (optional) — On Harper Pro and Fabric, a deploy goes to every node in the cluster unless this is false. Harper core on its own does not replicate
  • install_command (optional) — Install command override
  • install_timeout (optional) — Install timeout override in milliseconds
  • install_allow_scripts (optional) — Allow install scripts to run. Defaults to false, which causes --ignore-scripts to be passed to the install command (this is ignored with install_command).
  • host, urlPath, isolated, branchedDatabases (optional) — Set the component's root-config entry, as in Setting them when you deploy.
  • preview (optional) Added in: v5.4.0 — Marks the deploy as a pull request's preview, which expires. See Preview deployments.
{
"operation": "deploy_component",
"project": "my-component",
"package": "HarperFast/application-template#semver:v1.0.0",
"replicated": true,
"restart": "rolling"
}

drop_component​

Deletes a component project or a specific file within it.

  • project (required) — Project name
  • file (optional) — Path relative to project folder. If omitted, deletes the entire project
  • replicated (optional) — On Harper Pro and Fabric, deletion goes to every cluster node unless this is false. The response's replicated results report each peer's outcome
  • restart (optional) — true also restarts the shared workers, when they run the application. Dropping a whole isolated application stops its worker either way, and a node that does not have the application restarts nothing (v5.4.0)
  • keepBranches (optional) — true keeps the application's forks. Defaults to false. Cannot be combined with "restart": true (v5.4.0)
  • ifDeploymentId (optional) Added in: v5.4.0 — Drop the component only where its live release is this deployment. Each node checks its own, and one whose live release is another answers componentRemoved: false with Did not drop <project>: its live release is not deployment <id>, not an error. A UUID, and only with no file. Previews' expiry and disable_previews use it, so a preview redeployed in the meantime stays
{
"operation": "drop_component",
"project": "my-component"
}
Added in: v5.4.0

The response reports this node's outcome, and each peer's in replicated:

{
"message": "Successfully dropped: shop-preview",
"node": "node-a",
"componentRemoved": true,
"workerStopped": true,
"branchesRemoved": true,
"replicated": [
{ "node": "node-b", "componentRemoved": true, "workerStopped": true, "branchesRemoved": true, "error": null },
{ "node": "node-c", "componentRemoved": false, "workerStopped": false, "branchesRemoved": false, "error": null }
]
}
  • componentRemoved — the node had the component and removed it. false on a node that did not have it.
  • workerStopped — the node stopped a running dedicated worker for the application.
  • branchesRemoved — the node removed the application's forks.
  • error — the drop failed on that peer. The other fields are then null.

A null field also means a peer that did not say, such as one running a version before v5.4.0, or a step that had not finished when the node answered (see Removing a fork).

package_component​

Packages a project folder as a base64-encoded .tar string.

  • project (required) — Project name
  • skip_node_modules (optional) — Exclude node_modules from the package
{
"operation": "package_component",
"project": "my-component",
"skip_node_modules": true
}

get_components​

Returns all local component files, folders, and configuration from harper-config.yaml. The response includes an entries array; each direct child's name identifies a component. CI cleanup can inspect entries[].name to check whether the project is present on this node.

Added in: v5.2.0 An entry for a .env file carries protected: true, unless it is a template such as .env.example. See Environment File Operations.

{
"operation": "get_components"
}

Added in: v5.4.0 Each component entry also says which release is live on this node and when it last loaded:

  • deployment_id — the deployment whose release is live here, the id deploy_component with deployment_id accepts. Absent for a tree installed before v5.3.0, by add_component, or from a file: link.
  • deployed_at — when the deployment that made that release live finished, from its deployment record. A node that rejected a newer rolling deploy reports the release it still serves. A tree without deployment_id reports its project's newest deployment that went live.
  • restarted_at — the latest time a worker thread on this node finished loading the component, whether or not the load succeeded; status says how it went. A worker restart refreshes it.

Both times are in milliseconds since the epoch, and all three describe the node that answers. list_releases lists the releases each node keeps, for the whole cluster with replicated: true.

Added in: v5.4.0 The entry of a preview deployment also carries its preview: app, pr, expiresAt, and the branch, repository and sha it was deployed with. For a role with a component_deploy grant, entries lists only the components the grant covers.

get_component_file​

Returns the contents of a file within a component project.

Added in: v5.2.0 A .env file is returned masked: one KEY=******** line per key, never a value. Template files such as .env.example are returned as they are. See Environment File Operations.

  • project (required) — Project name
  • file (required) — Path relative to project folder
  • encoding (optional) — File encoding. Defaults to utf8
{
"operation": "get_component_file",
"project": "my-component",
"file": "resources.js"
}

set_component_file​

Creates or updates a file within a component project.

On a .env file it replaces the whole file with the payload, so saving back the masked body from get_component_file sets every value to ********. To change one key and keep the others, use set_env_value; see Environment File Operations.

  • project (required) — Project name
  • file (required) — Path relative to project folder
  • payload (required) — File content to write
  • encoding (optional) — File encoding. Defaults to utf8
  • replicated (optional) — On Harper Pro and Fabric, replicate the update to all cluster nodes by default; pass false to apply on this node only. Harper core on its own does not replicate
{
"operation": "set_component_file",
"project": "my-component",
"file": "test.js",
"payload": "console.log('hello world')"
}

SSH Key Management​

For deploying from private repositories, SSH keys must be registered on the Harper instance.

add_ssh_key​

  • name (required) — Key name
  • key Changed in: v5.3.0 (required unless generate is true) — Private key contents, with \n for line breaks: an unencrypted OpenSSH or PEM private key (Ed25519, ECDSA, or RSA of at least 1024 bits)
  • generate Added in: v5.2.4 (optional) — true to have Harper mint an ed25519 keypair in place of key, returning only the public key; see Server-side key generation
  • host Changed in: v5.3.0 (required) — Host alias for SSH config (used in package URL): a single alias, not a pattern
  • hostname Changed in: v5.3.0 (required) — Actual domain (e.g., github.com)
  • known_hosts (optional) — Public SSH keys of the host. Auto-retrieved for github.com
  • replicated (optional) — On Harper Pro and Fabric, replicate to all cluster nodes by default; pass false to apply on this node only. Harper core on its own does not replicate

A key, host or hostname that ssh couldn't use is refused with a 400 naming the problem; see what key, host and hostname must be. Each key's settings live in a block of <rootPath>/ssh/config that Harper owns; you can edit the file outside those blocks (see the key's block in the ssh config).

{
"operation": "add_ssh_key",
"name": "my-key",
"key": "-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----\n",
"host": "my-key.github.com",
"hostname": "github.com"
}

After adding a key, use the configured host in deploy package URLs:

"package": "git+ssh://git@my-key.github.com:my-org/my-repo.git#semver:v1.0.0"

Additional SSH key operations: update_ssh_key, delete_ssh_key, list_ssh_keys, set_ssh_known_hosts, get_ssh_known_hosts.